mirror of https://github.com/python/cpython
200 lines
6.1 KiB
ReStructuredText
200 lines
6.1 KiB
ReStructuredText
|
:mod:`packaging.depgraph` --- Dependency graph builder
|
||
|
======================================================
|
||
|
|
||
|
.. module:: packaging.depgraph
|
||
|
:synopsis: Graph builder for dependencies between releases.
|
||
|
|
||
|
|
||
|
This module provides the means to analyse the dependencies between various
|
||
|
distributions and to create a graph representing these dependency relationships.
|
||
|
In this document, "distribution" refers to an instance of
|
||
|
:class:`packaging.database.Distribution` or
|
||
|
:class:`packaging.database.EggInfoDistribution`.
|
||
|
|
||
|
.. XXX terminology problem with dist vs. release: dists are installed, but deps
|
||
|
use releases
|
||
|
|
||
|
.. XXX explain how to use it with dists not installed: Distribution can only be
|
||
|
instantiated with a path, but this module is useful for remote dist too
|
||
|
|
||
|
.. XXX functions should accept and return iterators, not lists
|
||
|
|
||
|
|
||
|
The :class:`DependencyGraph` class
|
||
|
----------------------------------
|
||
|
|
||
|
.. class:: DependencyGraph
|
||
|
|
||
|
Represent a dependency graph between releases. The nodes are distribution
|
||
|
instances; the edge model dependencies. An edge from ``a`` to ``b`` means
|
||
|
that ``a`` depends on ``b``.
|
||
|
|
||
|
.. method:: add_distribution(distribution)
|
||
|
|
||
|
Add *distribution* to the graph.
|
||
|
|
||
|
.. method:: add_edge(x, y, label=None)
|
||
|
|
||
|
Add an edge from distribution *x* to distribution *y* with the given
|
||
|
*label* (string).
|
||
|
|
||
|
.. method:: add_missing(distribution, requirement)
|
||
|
|
||
|
Add a missing *requirement* (string) for the given *distribution*.
|
||
|
|
||
|
.. method:: repr_node(dist, level=1)
|
||
|
|
||
|
Print a subgraph starting from *dist*. *level* gives the depth of the
|
||
|
subgraph.
|
||
|
|
||
|
Direct access to the graph nodes and edges is provided through these
|
||
|
attributes:
|
||
|
|
||
|
.. attribute:: adjacency_list
|
||
|
|
||
|
Dictionary mapping distributions to a list of ``(other, label)`` tuples
|
||
|
where ``other`` is a distribution and the edge is labeled with ``label``
|
||
|
(i.e. the version specifier, if such was provided).
|
||
|
|
||
|
.. attribute:: reverse_list
|
||
|
|
||
|
Dictionary mapping distributions to a list of predecessors. This allows
|
||
|
efficient traversal.
|
||
|
|
||
|
.. attribute:: missing
|
||
|
|
||
|
Dictionary mapping distributions to a list of requirements that were not
|
||
|
provided by any distribution.
|
||
|
|
||
|
|
||
|
Auxiliary functions
|
||
|
-------------------
|
||
|
|
||
|
.. function:: dependent_dists(dists, dist)
|
||
|
|
||
|
Recursively generate a list of distributions from *dists* that are dependent
|
||
|
on *dist*.
|
||
|
|
||
|
.. XXX what does member mean here: "dist is a member of *dists* for which we
|
||
|
are interested"
|
||
|
|
||
|
.. function:: generate_graph(dists)
|
||
|
|
||
|
Generate a :class:`DependencyGraph` from the given list of distributions.
|
||
|
|
||
|
.. XXX make this alternate constructor a DepGraph classmethod or rename;
|
||
|
'generate' can suggest it creates a file or an image, use 'make'
|
||
|
|
||
|
.. function:: graph_to_dot(graph, f, skip_disconnected=True)
|
||
|
|
||
|
Write a DOT output for the graph to the file-like object *f*.
|
||
|
|
||
|
If *skip_disconnected* is true, all distributions that are not dependent on
|
||
|
any other distribution are skipped.
|
||
|
|
||
|
.. XXX why is this not a DepGraph method?
|
||
|
|
||
|
|
||
|
Example Usage
|
||
|
-------------
|
||
|
|
||
|
Depict all dependenciess in the system
|
||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
|
||
|
First, we shall generate a graph of all the distributions on the system
|
||
|
and then create an image out of it using the tools provided by
|
||
|
`Graphviz <http://www.graphviz.org/>`_::
|
||
|
|
||
|
from packaging.database import get_distributions
|
||
|
from packaging.depgraph import generate_graph
|
||
|
|
||
|
dists = list(get_distributions())
|
||
|
graph = generate_graph(dists)
|
||
|
|
||
|
It would be interesting to print out the missing requirements. This can be done
|
||
|
as follows::
|
||
|
|
||
|
for dist, reqs in graph.missing.items():
|
||
|
if reqs:
|
||
|
reqs = ' ,'.join(repr(req) for req in reqs)
|
||
|
print('Missing dependencies for %r: %s' % (dist.name, reqs))
|
||
|
|
||
|
Example output is:
|
||
|
|
||
|
.. code-block:: none
|
||
|
|
||
|
Missing dependencies for 'TurboCheetah': 'Cheetah'
|
||
|
Missing dependencies for 'TurboGears': 'ConfigObj', 'DecoratorTools', 'RuleDispatch'
|
||
|
Missing dependencies for 'jockey': 'PyKDE4.kdecore', 'PyKDE4.kdeui', 'PyQt4.QtCore', 'PyQt4.QtGui'
|
||
|
Missing dependencies for 'TurboKid': 'kid'
|
||
|
Missing dependencies for 'TurboJson: 'DecoratorTools', 'RuleDispatch'
|
||
|
|
||
|
Now, we proceed with generating a graphical representation of the graph. First
|
||
|
we write it to a file, and then we generate a PNG image using the
|
||
|
:program:`dot` command-line tool::
|
||
|
|
||
|
from packaging.depgraph import graph_to_dot
|
||
|
with open('output.dot', 'w') as f:
|
||
|
# only show the interesting distributions, skipping the disconnected ones
|
||
|
graph_to_dot(graph, f, skip_disconnected=True)
|
||
|
|
||
|
We can create the final picture using:
|
||
|
|
||
|
.. code-block:: sh
|
||
|
|
||
|
$ dot -Tpng output.dot > output.png
|
||
|
|
||
|
An example result is:
|
||
|
|
||
|
.. figure:: depgraph-output.png
|
||
|
:alt: Example PNG output from packaging.depgraph and dot
|
||
|
|
||
|
If you want to include egg distributions as well, then the code requires only
|
||
|
one change, namely the line::
|
||
|
|
||
|
dists = list(packaging.database.get_distributions())
|
||
|
|
||
|
has to be replaced with::
|
||
|
|
||
|
dists = list(packaging.database.get_distributions(use_egg_info=True))
|
||
|
|
||
|
On many platforms, a richer graph is obtained because at the moment most
|
||
|
distributions are provided in the egg rather than the new standard
|
||
|
``.dist-info`` format.
|
||
|
|
||
|
.. XXX missing image
|
||
|
|
||
|
An example of a more involved graph for illustrative reasons can be seen
|
||
|
here:
|
||
|
|
||
|
.. image:: depgraph_big.png
|
||
|
|
||
|
|
||
|
List all dependent distributions
|
||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
|
||
|
We will list all distributions that are dependent on some given distibution.
|
||
|
This time, egg distributions will be considered as well::
|
||
|
|
||
|
import sys
|
||
|
from packaging.database import get_distribution, get_distributions
|
||
|
from packaging.depgraph import dependent_dists
|
||
|
|
||
|
dists = list(get_distributions(use_egg_info=True))
|
||
|
dist = get_distribution('bacon', use_egg_info=True)
|
||
|
if dist is None:
|
||
|
sys.exit('No such distribution in the system')
|
||
|
|
||
|
deps = dependent_dists(dists, dist)
|
||
|
deps = ', '.join(repr(x.name) for x in deps)
|
||
|
print('Distributions depending on %r: %s' % (dist.name, deps))
|
||
|
|
||
|
And this is example output:
|
||
|
|
||
|
.. with the dependency relationships as in the previous section
|
||
|
(depgraph_big)
|
||
|
|
||
|
.. code-block:: none
|
||
|
|
||
|
Distributions depending on 'bacon': 'towel-stuff', 'choxie', 'grammar'
|