Éric Araujo | 3a9f58f | 2011-06-01 20:42:49 +0200 | [diff] [blame] | 1 | :mod:`packaging.depgraph` --- Dependency graph builder |
| 2 | ====================================================== |
| 3 | |
| 4 | .. module:: packaging.depgraph |
| 5 | :synopsis: Graph builder for dependencies between releases. |
| 6 | |
| 7 | |
| 8 | This module provides the means to analyse the dependencies between various |
| 9 | distributions and to create a graph representing these dependency relationships. |
| 10 | In this document, "distribution" refers to an instance of |
| 11 | :class:`packaging.database.Distribution` or |
| 12 | :class:`packaging.database.EggInfoDistribution`. |
| 13 | |
| 14 | .. XXX terminology problem with dist vs. release: dists are installed, but deps |
| 15 | use releases |
| 16 | |
| 17 | .. XXX explain how to use it with dists not installed: Distribution can only be |
| 18 | instantiated with a path, but this module is useful for remote dist too |
| 19 | |
| 20 | .. XXX functions should accept and return iterators, not lists |
| 21 | |
| 22 | |
| 23 | The :class:`DependencyGraph` class |
| 24 | ---------------------------------- |
| 25 | |
| 26 | .. class:: DependencyGraph |
| 27 | |
| 28 | Represent a dependency graph between releases. The nodes are distribution |
| 29 | instances; the edge model dependencies. An edge from ``a`` to ``b`` means |
| 30 | that ``a`` depends on ``b``. |
| 31 | |
| 32 | .. method:: add_distribution(distribution) |
| 33 | |
| 34 | Add *distribution* to the graph. |
| 35 | |
| 36 | .. method:: add_edge(x, y, label=None) |
| 37 | |
| 38 | Add an edge from distribution *x* to distribution *y* with the given |
| 39 | *label* (string). |
| 40 | |
| 41 | .. method:: add_missing(distribution, requirement) |
| 42 | |
| 43 | Add a missing *requirement* (string) for the given *distribution*. |
| 44 | |
| 45 | .. method:: repr_node(dist, level=1) |
| 46 | |
| 47 | Print a subgraph starting from *dist*. *level* gives the depth of the |
| 48 | subgraph. |
| 49 | |
| 50 | Direct access to the graph nodes and edges is provided through these |
| 51 | attributes: |
| 52 | |
| 53 | .. attribute:: adjacency_list |
| 54 | |
| 55 | Dictionary mapping distributions to a list of ``(other, label)`` tuples |
| 56 | where ``other`` is a distribution and the edge is labeled with ``label`` |
| 57 | (i.e. the version specifier, if such was provided). |
| 58 | |
| 59 | .. attribute:: reverse_list |
| 60 | |
| 61 | Dictionary mapping distributions to a list of predecessors. This allows |
| 62 | efficient traversal. |
| 63 | |
| 64 | .. attribute:: missing |
| 65 | |
| 66 | Dictionary mapping distributions to a list of requirements that were not |
| 67 | provided by any distribution. |
| 68 | |
| 69 | |
| 70 | Auxiliary functions |
| 71 | ------------------- |
| 72 | |
| 73 | .. function:: dependent_dists(dists, dist) |
| 74 | |
| 75 | Recursively generate a list of distributions from *dists* that are dependent |
| 76 | on *dist*. |
| 77 | |
| 78 | .. XXX what does member mean here: "dist is a member of *dists* for which we |
| 79 | are interested" |
| 80 | |
| 81 | .. function:: generate_graph(dists) |
| 82 | |
| 83 | Generate a :class:`DependencyGraph` from the given list of distributions. |
| 84 | |
| 85 | .. XXX make this alternate constructor a DepGraph classmethod or rename; |
| 86 | 'generate' can suggest it creates a file or an image, use 'make' |
| 87 | |
| 88 | .. function:: graph_to_dot(graph, f, skip_disconnected=True) |
| 89 | |
| 90 | Write a DOT output for the graph to the file-like object *f*. |
| 91 | |
| 92 | If *skip_disconnected* is true, all distributions that are not dependent on |
| 93 | any other distribution are skipped. |
| 94 | |
| 95 | .. XXX why is this not a DepGraph method? |
| 96 | |
| 97 | |
| 98 | Example Usage |
| 99 | ------------- |
| 100 | |
| 101 | Depict all dependenciess in the system |
| 102 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| 103 | |
| 104 | First, we shall generate a graph of all the distributions on the system |
| 105 | and then create an image out of it using the tools provided by |
| 106 | `Graphviz <http://www.graphviz.org/>`_:: |
| 107 | |
| 108 | from packaging.database import get_distributions |
| 109 | from packaging.depgraph import generate_graph |
| 110 | |
| 111 | dists = list(get_distributions()) |
| 112 | graph = generate_graph(dists) |
| 113 | |
| 114 | It would be interesting to print out the missing requirements. This can be done |
| 115 | as follows:: |
| 116 | |
| 117 | for dist, reqs in graph.missing.items(): |
| 118 | if reqs: |
| 119 | reqs = ' ,'.join(repr(req) for req in reqs) |
| 120 | print('Missing dependencies for %r: %s' % (dist.name, reqs)) |
| 121 | |
| 122 | Example output is: |
| 123 | |
| 124 | .. code-block:: none |
| 125 | |
| 126 | Missing dependencies for 'TurboCheetah': 'Cheetah' |
| 127 | Missing dependencies for 'TurboGears': 'ConfigObj', 'DecoratorTools', 'RuleDispatch' |
| 128 | Missing dependencies for 'jockey': 'PyKDE4.kdecore', 'PyKDE4.kdeui', 'PyQt4.QtCore', 'PyQt4.QtGui' |
| 129 | Missing dependencies for 'TurboKid': 'kid' |
| 130 | Missing dependencies for 'TurboJson: 'DecoratorTools', 'RuleDispatch' |
| 131 | |
| 132 | Now, we proceed with generating a graphical representation of the graph. First |
| 133 | we write it to a file, and then we generate a PNG image using the |
| 134 | :program:`dot` command-line tool:: |
| 135 | |
| 136 | from packaging.depgraph import graph_to_dot |
| 137 | with open('output.dot', 'w') as f: |
| 138 | # only show the interesting distributions, skipping the disconnected ones |
| 139 | graph_to_dot(graph, f, skip_disconnected=True) |
| 140 | |
| 141 | We can create the final picture using: |
| 142 | |
| 143 | .. code-block:: sh |
| 144 | |
| 145 | $ dot -Tpng output.dot > output.png |
| 146 | |
| 147 | An example result is: |
| 148 | |
| 149 | .. figure:: depgraph-output.png |
| 150 | :alt: Example PNG output from packaging.depgraph and dot |
| 151 | |
| 152 | If you want to include egg distributions as well, then the code requires only |
| 153 | one change, namely the line:: |
| 154 | |
| 155 | dists = list(packaging.database.get_distributions()) |
| 156 | |
| 157 | has to be replaced with:: |
| 158 | |
| 159 | dists = list(packaging.database.get_distributions(use_egg_info=True)) |
| 160 | |
| 161 | On many platforms, a richer graph is obtained because at the moment most |
| 162 | distributions are provided in the egg rather than the new standard |
| 163 | ``.dist-info`` format. |
| 164 | |
| 165 | .. XXX missing image |
| 166 | |
| 167 | An example of a more involved graph for illustrative reasons can be seen |
| 168 | here: |
| 169 | |
| 170 | .. image:: depgraph_big.png |
| 171 | |
| 172 | |
| 173 | List all dependent distributions |
| 174 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| 175 | |
| 176 | We will list all distributions that are dependent on some given distibution. |
| 177 | This time, egg distributions will be considered as well:: |
| 178 | |
| 179 | import sys |
| 180 | from packaging.database import get_distribution, get_distributions |
| 181 | from packaging.depgraph import dependent_dists |
| 182 | |
| 183 | dists = list(get_distributions(use_egg_info=True)) |
| 184 | dist = get_distribution('bacon', use_egg_info=True) |
| 185 | if dist is None: |
| 186 | sys.exit('No such distribution in the system') |
| 187 | |
| 188 | deps = dependent_dists(dists, dist) |
| 189 | deps = ', '.join(repr(x.name) for x in deps) |
| 190 | print('Distributions depending on %r: %s' % (dist.name, deps)) |
| 191 | |
| 192 | And this is example output: |
| 193 | |
| 194 | .. with the dependency relationships as in the previous section |
| 195 | (depgraph_big) |
| 196 | |
| 197 | .. code-block:: none |
| 198 | |
| 199 | Distributions depending on 'bacon': 'towel-stuff', 'choxie', 'grammar' |