blob: c384788e9b6b66c1032c8058cb54d3a097be844d [file] [log] [blame]
Éric Araujo3a9f58f2011-06-01 20:42:49 +02001:mod:`packaging.depgraph` --- Dependency graph builder
2======================================================
3
4.. module:: packaging.depgraph
5 :synopsis: Graph builder for dependencies between releases.
6
7
8This module provides the means to analyse the dependencies between various
9distributions and to create a graph representing these dependency relationships.
10In 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
23The :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
70Auxiliary 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
98Example Usage
99-------------
100
101Depict all dependenciess in the system
102^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
103
104First, we shall generate a graph of all the distributions on the system
105and 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
114It would be interesting to print out the missing requirements. This can be done
115as 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
122Example 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
132Now, we proceed with generating a graphical representation of the graph. First
133we 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
141We can create the final picture using:
142
143.. code-block:: sh
144
145 $ dot -Tpng output.dot > output.png
146
147An example result is:
148
149.. figure:: depgraph-output.png
150 :alt: Example PNG output from packaging.depgraph and dot
151
152If you want to include egg distributions as well, then the code requires only
153one change, namely the line::
154
155 dists = list(packaging.database.get_distributions())
156
157has to be replaced with::
158
159 dists = list(packaging.database.get_distributions(use_egg_info=True))
160
161On many platforms, a richer graph is obtained because at the moment most
162distributions 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
173List all dependent distributions
174^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
175
176We will list all distributions that are dependent on some given distibution.
177This 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
192And 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'