Sat, 31 Dec 2022 16:23:21 +0100
Updated copyright for 2023.
9218 | 1 | #!/usr/bin/env python |
2 | # -*- coding: utf-8 -*- | |
3 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
4 | # |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
5 | # eric-ide modification: Copyright from file "LICENSE" |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
6 | # |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
7 | |
9218 | 8 | """ |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
9 | Copyright (c) The pipdeptree developers |
9218 | 10 | |
11 | Permission is hereby granted, free of charge, to any person obtaining | |
12 | a copy of this software and associated documentation files (the | |
13 | "Software"), to deal in the Software without restriction, including | |
14 | without limitation the rights to use, copy, modify, merge, publish, | |
15 | distribute, sublicense, and/or sell copies of the Software, and to | |
16 | permit persons to whom the Software is furnished to do so, subject to | |
17 | the following conditions: | |
18 | ||
19 | The above copyright notice and this permission notice shall be | |
20 | included in all copies or substantial portions of the Software. | |
21 | ||
22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
23 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
24 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
25 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
26 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
27 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
28 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
29 | """ | |
30 | ||
31 | # | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
32 | # Slightly modified to be used within the eric-ide project. |
9218 | 33 | # |
9653
e67609152c5e
Updated copyright for 2023.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9589
diff
changeset
|
34 | # Copyright (c) 2022 - 2023 Detlev Offenbach <detlev@die-offenbachs.de> |
9218 | 35 | # |
36 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
37 | import argparse |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
38 | import inspect |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
39 | import json |
9218 | 40 | import os |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
41 | import shutil |
9218 | 42 | import subprocess |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
43 | import sys |
9218 | 44 | import tempfile |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
45 | from collections import defaultdict, deque |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
46 | from collections.abc import Mapping |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
47 | from importlib import import_module |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
48 | from itertools import chain |
9218 | 49 | |
50 | from pip._vendor import pkg_resources | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
51 | |
9218 | 52 | try: |
53 | from pip._internal.operations.freeze import FrozenRequirement | |
54 | except ImportError: | |
55 | from pip import FrozenRequirement | |
56 | ||
57 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
58 | __version__ = '2.3.3' # eric-ide modification: from version.py |
9218 | 59 | |
60 | ||
61 | flatten = chain.from_iterable | |
62 | ||
63 | ||
64 | def sorted_tree(tree): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
65 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
66 | Sorts the dict representation of the tree. The root packages as well as the intermediate packages are sorted in the |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
67 | alphabetical order of the package names. |
9218 | 68 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
69 | :param dict tree: the pkg dependency tree obtained by calling `construct_tree` function |
9218 | 70 | :returns: sorted tree |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
71 | :rtype: dict |
9218 | 72 | """ |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
73 | return {k: sorted(v) for k, v in sorted(tree.items())} |
9218 | 74 | |
75 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
76 | def guess_version(pkg_key, default="?"): |
9218 | 77 | """Guess the version of a pkg when pip doesn't provide it |
78 | ||
79 | :param str pkg_key: key of the package | |
80 | :param str default: default version to return if unable to find | |
81 | :returns: version | |
82 | :rtype: string | |
83 | """ | |
84 | try: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
85 | if sys.version_info >= (3, 8): # pragma: >=3.8 cover |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
86 | import importlib.metadata as importlib_metadata |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
87 | else: # pragma: <3.8 cover |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
88 | import importlib_metadata |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
89 | return importlib_metadata.version(pkg_key) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
90 | except ImportError: |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
91 | pass |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
92 | # Avoid AssertionError with setuptools, see https://github.com/tox-dev/pipdeptree/issues/162 |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
93 | if pkg_key in {"setuptools"}: |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
94 | return default |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
95 | try: |
9218 | 96 | m = import_module(pkg_key) |
97 | except ImportError: | |
98 | return default | |
99 | else: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
100 | v = getattr(m, "__version__", default) |
9218 | 101 | if inspect.ismodule(v): |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
102 | return getattr(v, "__version__", default) |
9218 | 103 | else: |
104 | return v | |
105 | ||
106 | ||
107 | def frozen_req_from_dist(dist): | |
108 | # The `pip._internal.metadata` modules were introduced in 21.1.1 | |
109 | # and the `pip._internal.operations.freeze.FrozenRequirement` | |
110 | # class now expects dist to be a subclass of | |
111 | # `pip._internal.metadata.BaseDistribution`, however the | |
112 | # `pip._internal.utils.misc.get_installed_distributions` continues | |
113 | # to return objects of type | |
114 | # pip._vendor.pkg_resources.DistInfoDistribution. | |
115 | # | |
116 | # This is a hacky backward compatible (with older versions of pip) | |
117 | # fix. | |
118 | try: | |
119 | from pip._internal import metadata | |
120 | except ImportError: | |
121 | pass | |
122 | else: | |
123 | dist = metadata.pkg_resources.Distribution(dist) | |
124 | ||
125 | try: | |
126 | return FrozenRequirement.from_dist(dist) | |
127 | except TypeError: | |
128 | return FrozenRequirement.from_dist(dist, []) | |
129 | ||
130 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
131 | class Package: |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
132 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
133 | Abstract class for wrappers around objects that pip returns. This class needs to be subclassed with implementations |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
134 | for `render_as_root` and `render_as_branch` methods. |
9218 | 135 | """ |
136 | ||
137 | def __init__(self, obj): | |
138 | self._obj = obj | |
139 | self.project_name = obj.project_name | |
140 | self.key = obj.key | |
141 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
142 | def render_as_root(self, frozen): # noqa: U100 |
9218 | 143 | return NotImplementedError |
144 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
145 | def render_as_branch(self, frozen): # noqa: U100 |
9218 | 146 | return NotImplementedError |
147 | ||
148 | def render(self, parent=None, frozen=False): | |
149 | if not parent: | |
150 | return self.render_as_root(frozen) | |
151 | else: | |
152 | return self.render_as_branch(frozen) | |
153 | ||
154 | @staticmethod | |
155 | def frozen_repr(obj): | |
156 | fr = frozen_req_from_dist(obj) | |
157 | return str(fr).strip() | |
158 | ||
159 | def __getattr__(self, key): | |
160 | return getattr(self._obj, key) | |
161 | ||
162 | def __repr__(self): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
163 | return f'<{self.__class__.__name__}("{self.key}")>' |
9218 | 164 | |
165 | def __lt__(self, rhs): | |
166 | return self.key < rhs.key | |
167 | ||
168 | ||
169 | class DistPackage(Package): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
170 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
171 | Wrapper class for pkg_resources.Distribution instances |
9218 | 172 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
173 | :param obj: pkg_resources.Distribution to wrap over |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
174 | :param req: optional ReqPackage object to associate this DistPackage with. This is useful for displaying the tree |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
175 | in reverse |
9218 | 176 | """ |
177 | ||
178 | def __init__(self, obj, req=None): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
179 | super().__init__(obj) |
9218 | 180 | self.version_spec = None |
181 | self.req = req | |
182 | ||
183 | def render_as_root(self, frozen): | |
184 | if not frozen: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
185 | return f"{self.project_name}=={self.version}" |
9218 | 186 | else: |
187 | return self.__class__.frozen_repr(self._obj) | |
188 | ||
189 | def render_as_branch(self, frozen): | |
190 | assert self.req is not None | |
191 | if not frozen: | |
192 | parent_ver_spec = self.req.version_spec | |
193 | parent_str = self.req.project_name | |
194 | if parent_ver_spec: | |
195 | parent_str += parent_ver_spec | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
196 | return f"{self.project_name}=={self.version} [requires: {parent_str}]" |
9218 | 197 | else: |
198 | return self.render_as_root(frozen) | |
199 | ||
200 | def as_requirement(self): | |
201 | """Return a ReqPackage representation of this DistPackage""" | |
202 | return ReqPackage(self._obj.as_requirement(), dist=self) | |
203 | ||
204 | def as_parent_of(self, req): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
205 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
206 | Return a DistPackage instance associated to a requirement. This association is necessary for reversing the |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
207 | PackageDAG. |
9218 | 208 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
209 | If `req` is None, and the `req` attribute of the current instance is also None, then the same instance will be |
9218 | 210 | returned. |
211 | ||
212 | :param ReqPackage req: the requirement to associate with | |
213 | :returns: DistPackage instance | |
214 | """ | |
215 | if req is None and self.req is None: | |
216 | return self | |
217 | return self.__class__(self._obj, req) | |
218 | ||
219 | def as_dict(self): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
220 | return {"key": self.key, "package_name": self.project_name, "installed_version": self.version} |
9218 | 221 | |
222 | ||
223 | class ReqPackage(Package): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
224 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
225 | Wrapper class for Requirements instance |
9218 | 226 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
227 | :param obj: The `Requirements` instance to wrap over |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
228 | :param dist: optional `pkg_resources.Distribution` instance for this requirement |
9218 | 229 | """ |
230 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
231 | UNKNOWN_VERSION = "?" |
9218 | 232 | |
233 | def __init__(self, obj, dist=None): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
234 | super().__init__(obj) |
9218 | 235 | self.dist = dist |
236 | ||
237 | @property | |
238 | def version_spec(self): | |
239 | specs = sorted(self._obj.specs, reverse=True) # `reverse` makes '>' prior to '<' | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
240 | return ",".join(["".join(sp) for sp in specs]) if specs else None |
9218 | 241 | |
242 | @property | |
243 | def installed_version(self): | |
244 | if not self.dist: | |
245 | return guess_version(self.key, self.UNKNOWN_VERSION) | |
246 | return self.dist.version | |
247 | ||
248 | @property | |
249 | def is_missing(self): | |
250 | return self.installed_version == self.UNKNOWN_VERSION | |
251 | ||
252 | def is_conflicting(self): | |
253 | """If installed version conflicts with required version""" | |
254 | # unknown installed version is also considered conflicting | |
255 | if self.installed_version == self.UNKNOWN_VERSION: | |
256 | return True | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
257 | ver_spec = self.version_spec if self.version_spec else "" |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
258 | req_version_str = f"{self.project_name}{ver_spec}" |
9218 | 259 | req_obj = pkg_resources.Requirement.parse(req_version_str) |
260 | return self.installed_version not in req_obj | |
261 | ||
262 | def render_as_root(self, frozen): | |
263 | if not frozen: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
264 | return f"{self.project_name}=={self.installed_version}" |
9218 | 265 | elif self.dist: |
266 | return self.__class__.frozen_repr(self.dist._obj) | |
267 | else: | |
268 | return self.project_name | |
269 | ||
270 | def render_as_branch(self, frozen): | |
271 | if not frozen: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
272 | req_ver = self.version_spec if self.version_spec else "Any" |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
273 | return f"{self.project_name} [required: {req_ver}, installed: {self.installed_version}]" |
9218 | 274 | else: |
275 | return self.render_as_root(frozen) | |
276 | ||
277 | def as_dict(self): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
278 | return { |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
279 | "key": self.key, |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
280 | "package_name": self.project_name, |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
281 | "installed_version": self.installed_version, |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
282 | "required_version": self.version_spec, |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
283 | } |
9218 | 284 | |
285 | ||
286 | class PackageDAG(Mapping): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
287 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
288 | Representation of Package dependencies as directed acyclic graph using a dict (Mapping) as the underlying |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
289 | datastructure. |
9218 | 290 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
291 | The nodes and their relationships (edges) are internally stored using a map as follows, |
9218 | 292 | |
293 | {a: [b, c], | |
294 | b: [d], | |
295 | c: [d, e], | |
296 | d: [e], | |
297 | e: [], | |
298 | f: [b], | |
299 | g: [e, f]} | |
300 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
301 | Here, node `a` has 2 children nodes `b` and `c`. Consider edge direction from `a` -> `b` and `a` -> `c` |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
302 | respectively. |
9218 | 303 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
304 | A node is expected to be an instance of a subclass of `Package`. The keys are must be of class `DistPackage` and |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
305 | each item in values must be of class `ReqPackage`. (See also ReversedPackageDAG where the key and value types are |
9218 | 306 | interchanged). |
307 | """ | |
308 | ||
309 | @classmethod | |
310 | def from_pkgs(cls, pkgs): | |
311 | pkgs = [DistPackage(p) for p in pkgs] | |
312 | idx = {p.key: p for p in pkgs} | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
313 | m = {p: [ReqPackage(r, idx.get(r.key)) for r in p.requires()] for p in pkgs} |
9218 | 314 | return cls(m) |
315 | ||
316 | def __init__(self, m): | |
317 | """Initialize the PackageDAG object | |
318 | ||
319 | :param dict m: dict of node objects (refer class docstring) | |
320 | :returns: None | |
321 | :rtype: NoneType | |
322 | ||
323 | """ | |
324 | self._obj = m | |
325 | self._index = {p.key: p for p in list(self._obj)} | |
326 | ||
327 | def get_node_as_parent(self, node_key): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
328 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
329 | Get the node from the keys of the dict representing the DAG. |
9218 | 330 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
331 | This method is useful if the dict representing the DAG contains different kind of objects in keys and values. |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
332 | Use this method to look up a node obj as a parent (from the keys of the dict) given a node key. |
9218 | 333 | |
334 | :param node_key: identifier corresponding to key attr of node obj | |
335 | :returns: node obj (as present in the keys of the dict) | |
336 | :rtype: Object | |
337 | """ | |
338 | try: | |
339 | return self._index[node_key] | |
340 | except KeyError: | |
341 | return None | |
342 | ||
343 | def get_children(self, node_key): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
344 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
345 | Get child nodes for a node by its key |
9218 | 346 | |
347 | :param str node_key: key of the node to get children of | |
348 | :returns: list of child nodes | |
349 | :rtype: ReqPackage[] | |
350 | """ | |
351 | node = self.get_node_as_parent(node_key) | |
352 | return self._obj[node] if node else [] | |
353 | ||
354 | def filter(self, include, exclude): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
355 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
356 | Filters nodes in a graph by given parameters |
9218 | 357 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
358 | If a node is included, then all it's children are also included. |
9218 | 359 | |
360 | :param set include: set of node keys to include (or None) | |
361 | :param set exclude: set of node keys to exclude (or None) | |
362 | :returns: filtered version of the graph | |
363 | :rtype: PackageDAG | |
364 | """ | |
365 | # If neither of the filters are specified, short circuit | |
366 | if include is None and exclude is None: | |
367 | return self | |
368 | ||
369 | # Note: In following comparisons, we use lower cased values so | |
370 | # that user may specify `key` or `project_name`. As per the | |
371 | # documentation, `key` is simply | |
372 | # `project_name.lower()`. Refer: | |
373 | # https://setuptools.readthedocs.io/en/latest/pkg_resources.html#distribution-objects | |
374 | if include: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
375 | include = {s.lower() for s in include} |
9218 | 376 | if exclude: |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
377 | exclude = {s.lower() for s in exclude} |
9218 | 378 | else: |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
379 | exclude = set() |
9218 | 380 | |
381 | # Check for mutual exclusion of show_only and exclude sets | |
382 | # after normalizing the values to lowercase | |
383 | if include and exclude: | |
384 | assert not (include & exclude) | |
385 | ||
386 | # Traverse the graph in a depth first manner and filter the | |
387 | # nodes according to `show_only` and `exclude` sets | |
388 | stack = deque() | |
389 | m = {} | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
390 | seen = set() |
9218 | 391 | for node in self._obj.keys(): |
392 | if node.key in exclude: | |
393 | continue | |
394 | if include is None or node.key in include: | |
395 | stack.append(node) | |
396 | while True: | |
397 | if len(stack) > 0: | |
398 | n = stack.pop() | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
399 | cldn = [c for c in self._obj[n] if c.key not in exclude] |
9218 | 400 | m[n] = cldn |
401 | seen.add(n.key) | |
402 | for c in cldn: | |
403 | if c.key not in seen: | |
404 | cld_node = self.get_node_as_parent(c.key) | |
405 | if cld_node: | |
406 | stack.append(cld_node) | |
407 | else: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
408 | # It means there's no root node corresponding to the child node i.e. |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
409 | # a dependency is missing |
9218 | 410 | continue |
411 | else: | |
412 | break | |
413 | ||
414 | return self.__class__(m) | |
415 | ||
416 | def reverse(self): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
417 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
418 | Reverse the DAG, or turn it upside-down. |
9218 | 419 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
420 | In other words, the directions of edges of the nodes in the DAG will be reversed. |
9218 | 421 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
422 | Note that this function purely works on the nodes in the graph. This implies that to perform a combination of |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
423 | filtering and reversing, the order in which `filter` and `reverse` methods should be applied is important. For |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
424 | e.g., if reverse is called on a filtered graph, then only the filtered nodes and it's children will be |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
425 | considered when reversing. On the other hand, if filter is called on reversed DAG, then the definition of |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
426 | "child" nodes is as per the reversed DAG. |
9218 | 427 | |
428 | :returns: DAG in the reversed form | |
429 | :rtype: ReversedPackageDAG | |
430 | """ | |
431 | m = defaultdict(list) | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
432 | child_keys = {r.key for r in chain.from_iterable(self._obj.values())} |
9218 | 433 | for k, vs in self._obj.items(): |
434 | for v in vs: | |
435 | # if v is already added to the dict, then ensure that | |
436 | # we are using the same object. This check is required | |
437 | # as we're using array mutation | |
438 | try: | |
439 | node = [p for p in m.keys() if p.key == v.key][0] | |
440 | except IndexError: | |
441 | node = v | |
442 | m[node].append(k.as_parent_of(v)) | |
443 | if k.key not in child_keys: | |
444 | m[k.as_requirement()] = [] | |
445 | return ReversedPackageDAG(dict(m)) | |
446 | ||
447 | def sort(self): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
448 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
449 | Return sorted tree in which the underlying _obj dict is an dict, sorted alphabetically by the keys. |
9218 | 450 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
451 | :returns: Instance of same class with dict |
9218 | 452 | """ |
453 | return self.__class__(sorted_tree(self._obj)) | |
454 | ||
455 | # Methods required by the abstract base class Mapping | |
456 | def __getitem__(self, *args): | |
457 | return self._obj.get(*args) | |
458 | ||
459 | def __iter__(self): | |
460 | return self._obj.__iter__() | |
461 | ||
462 | def __len__(self): | |
463 | return len(self._obj) | |
464 | ||
465 | ||
466 | class ReversedPackageDAG(PackageDAG): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
467 | """Representation of Package dependencies in the reverse order. |
9218 | 468 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
469 | Similar to it's super class `PackageDAG`, the underlying datastructure is a dict, but here the keys are expected to |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
470 | be of type `ReqPackage` and each item in the values of type `DistPackage`. |
9218 | 471 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
472 | Typically, this object will be obtained by calling `PackageDAG.reverse`. |
9218 | 473 | """ |
474 | ||
475 | def reverse(self): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
476 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
477 | Reverse the already reversed DAG to get the PackageDAG again |
9218 | 478 | |
479 | :returns: reverse of the reversed DAG | |
480 | :rtype: PackageDAG | |
481 | """ | |
482 | m = defaultdict(list) | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
483 | child_keys = {r.key for r in chain.from_iterable(self._obj.values())} |
9218 | 484 | for k, vs in self._obj.items(): |
485 | for v in vs: | |
486 | try: | |
487 | node = [p for p in m.keys() if p.key == v.key][0] | |
488 | except IndexError: | |
489 | node = v.as_parent_of(None) | |
490 | m[node].append(k) | |
491 | if k.key not in child_keys: | |
492 | m[k.dist] = [] | |
493 | return PackageDAG(dict(m)) | |
494 | ||
495 | ||
496 | def render_text(tree, list_all=True, frozen=False): | |
497 | """Print tree as text on console | |
498 | ||
499 | :param dict tree: the package tree | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
500 | :param bool list_all: whether to list all the pgks at the root level or only those that are the sub-dependencies |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
501 | :param bool frozen: show the names of the pkgs in the output that's favourable to pip --freeze |
9218 | 502 | :returns: None |
503 | ||
504 | """ | |
505 | tree = tree.sort() | |
506 | nodes = tree.keys() | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
507 | branch_keys = {r.key for r in chain.from_iterable(tree.values())} |
9218 | 508 | use_bullets = not frozen |
509 | ||
510 | if not list_all: | |
511 | nodes = [p for p in nodes if p.key not in branch_keys] | |
512 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
513 | def aux(node, parent=None, indent=0, cur_chain=None): |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
514 | cur_chain = cur_chain or [] |
9218 | 515 | node_str = node.render(parent, frozen) |
516 | if parent: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
517 | prefix = " " * indent + ("- " if use_bullets else "") |
9218 | 518 | node_str = prefix + node_str |
519 | result = [node_str] | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
520 | children = [ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
521 | aux(c, node, indent=indent + 2, cur_chain=cur_chain + [c.project_name]) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
522 | for c in tree.get_children(node.key) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
523 | if c.project_name not in cur_chain |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
524 | ] |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
525 | result += list(chain.from_iterable(children)) |
9218 | 526 | return result |
527 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
528 | lines = chain.from_iterable([aux(p) for p in nodes]) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
529 | print("\n".join(lines)) |
9218 | 530 | |
531 | ||
532 | def render_json(tree, indent): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
533 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
534 | Converts the tree into a flat json representation. |
9218 | 535 | |
536 | The json repr will be a list of hashes, each hash having 2 fields: | |
537 | - package | |
538 | - dependencies: list of dependencies | |
539 | ||
540 | :param dict tree: dependency tree | |
541 | :param int indent: no. of spaces to indent json | |
542 | :returns: json representation of the tree | |
543 | :rtype: str | |
544 | """ | |
545 | tree = tree.sort() | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
546 | return json.dumps( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
547 | [{"package": k.as_dict(), "dependencies": [v.as_dict() for v in vs]} for k, vs in tree.items()], indent=indent |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
548 | ) |
9218 | 549 | |
550 | ||
551 | def render_json_tree(tree, indent): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
552 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
553 | Converts the tree into a nested json representation. |
9218 | 554 | |
555 | The json repr will be a list of hashes, each hash having the following fields: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
556 | |
9218 | 557 | - package_name |
558 | - key | |
559 | - required_version | |
560 | - installed_version | |
561 | - dependencies: list of dependencies | |
562 | ||
563 | :param dict tree: dependency tree | |
564 | :param int indent: no. of spaces to indent json | |
565 | :returns: json representation of the tree | |
566 | :rtype: str | |
567 | """ | |
568 | tree = tree.sort() | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
569 | branch_keys = {r.key for r in chain.from_iterable(tree.values())} |
9218 | 570 | nodes = [p for p in tree.keys() if p.key not in branch_keys] |
571 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
572 | def aux(node, parent=None, cur_chain=None): |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
573 | if cur_chain is None: |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
574 | cur_chain = [node.project_name] |
9218 | 575 | |
576 | d = node.as_dict() | |
577 | if parent: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
578 | d["required_version"] = node.version_spec if node.version_spec else "Any" |
9218 | 579 | else: |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
580 | d["required_version"] = d["installed_version"] |
9218 | 581 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
582 | d["dependencies"] = [ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
583 | aux(c, parent=node, cur_chain=cur_chain + [c.project_name]) |
9218 | 584 | for c in tree.get_children(node.key) |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
585 | if c.project_name not in cur_chain |
9218 | 586 | ] |
587 | ||
588 | return d | |
589 | ||
590 | return json.dumps([aux(p) for p in nodes], indent=indent) | |
591 | ||
592 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
593 | def dump_graphviz(tree, output_format="dot", is_reverse=False): |
9218 | 594 | """Output dependency graph as one of the supported GraphViz output formats. |
595 | ||
596 | :param dict tree: dependency graph | |
597 | :param string output_format: output format | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
598 | :param bool is_reverse: reverse or not |
9218 | 599 | :returns: representation of tree in the specified output format |
600 | :rtype: str or binary representation depending on the output format | |
601 | ||
602 | """ | |
603 | try: | |
604 | from graphviz import Digraph | |
605 | except ImportError: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
606 | print("graphviz is not available, but necessary for the output " "option. Please install it.", file=sys.stderr) |
9218 | 607 | sys.exit(1) |
608 | ||
609 | try: | |
610 | from graphviz import parameters | |
611 | except ImportError: | |
612 | from graphviz import backend | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
613 | |
9218 | 614 | valid_formats = backend.FORMATS |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
615 | print( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
616 | "Deprecation warning! Please upgrade graphviz to version >=0.18.0 " |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
617 | "Support for older versions will be removed in upcoming release", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
618 | file=sys.stderr, |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
619 | ) |
9218 | 620 | else: |
621 | valid_formats = parameters.FORMATS | |
622 | ||
623 | if output_format not in valid_formats: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
624 | print(f"{output_format} is not a supported output format.", file=sys.stderr) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
625 | print(f"Supported formats are: {', '.join(sorted(valid_formats))}", file=sys.stderr) |
9218 | 626 | sys.exit(1) |
627 | ||
628 | graph = Digraph(format=output_format) | |
629 | ||
630 | if not is_reverse: | |
631 | for pkg, deps in tree.items(): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
632 | pkg_label = f"{pkg.project_name}\\n{pkg.version}" |
9218 | 633 | graph.node(pkg.key, label=pkg_label) |
634 | for dep in deps: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
635 | edge_label = dep.version_spec or "any" |
9218 | 636 | if dep.is_missing: |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
637 | dep_label = f"{dep.project_name}\\n(missing)" |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
638 | graph.node(dep.key, label=dep_label, style="dashed") |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
639 | graph.edge(pkg.key, dep.key, style="dashed") |
9218 | 640 | else: |
641 | graph.edge(pkg.key, dep.key, label=edge_label) | |
642 | else: | |
643 | for dep, parents in tree.items(): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
644 | dep_label = f"{dep.project_name}\\n{dep.installed_version}" |
9218 | 645 | graph.node(dep.key, label=dep_label) |
646 | for parent in parents: | |
647 | # req reference of the dep associated with this | |
648 | # particular parent package | |
649 | req_ref = parent.req | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
650 | edge_label = req_ref.version_spec or "any" |
9218 | 651 | graph.edge(dep.key, parent.key, label=edge_label) |
652 | ||
653 | # Allow output of dot format, even if GraphViz isn't installed. | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
654 | if output_format == "dot": |
9218 | 655 | return graph.source |
656 | ||
657 | # As it's unknown if the selected output format is binary or not, try to | |
658 | # decode it as UTF8 and only print it out in binary if that's not possible. | |
659 | try: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
660 | return graph.pipe().decode("utf-8") |
9218 | 661 | except UnicodeDecodeError: |
662 | return graph.pipe() | |
663 | ||
664 | ||
665 | def print_graphviz(dump_output): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
666 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
667 | Dump the data generated by GraphViz to stdout. |
9218 | 668 | |
669 | :param dump_output: The output from dump_graphviz | |
670 | """ | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
671 | if hasattr(dump_output, "encode"): |
9218 | 672 | print(dump_output) |
673 | else: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
674 | with os.fdopen(sys.stdout.fileno(), "wb") as bytestream: |
9218 | 675 | bytestream.write(dump_output) |
676 | ||
677 | ||
678 | def conflicting_deps(tree): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
679 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
680 | Returns dependencies which are not present or conflict with the requirements of other packages. |
9218 | 681 | |
682 | e.g. will warn if pkg1 requires pkg2==2.0 and pkg2==1.0 is installed | |
683 | ||
684 | :param tree: the requirements tree (dict) | |
685 | :returns: dict of DistPackage -> list of unsatisfied/unknown ReqPackage | |
686 | :rtype: dict | |
687 | """ | |
688 | conflicting = defaultdict(list) | |
689 | for p, rs in tree.items(): | |
690 | for req in rs: | |
691 | if req.is_conflicting(): | |
692 | conflicting[p].append(req) | |
693 | return conflicting | |
694 | ||
695 | ||
696 | def render_conflicts_text(conflicts): | |
697 | if conflicts: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
698 | print("Warning!!! Possibly conflicting dependencies found:", file=sys.stderr) |
9218 | 699 | # Enforce alphabetical order when listing conflicts |
700 | pkgs = sorted(conflicts.keys()) | |
701 | for p in pkgs: | |
702 | pkg = p.render_as_root(False) | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
703 | print(f"* {pkg}", file=sys.stderr) |
9218 | 704 | for req in conflicts[p]: |
705 | req_str = req.render_as_branch(False) | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
706 | print(f" - {req_str}", file=sys.stderr) |
9218 | 707 | |
708 | ||
709 | def cyclic_deps(tree): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
710 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
711 | Return cyclic dependencies as list of tuples |
9218 | 712 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
713 | :param PackageDAG tree: package tree/dag |
9218 | 714 | :returns: list of tuples representing cyclic dependencies |
715 | :rtype: list | |
716 | """ | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
717 | index = {p.key: {r.key for r in rs} for p, rs in tree.items()} |
9218 | 718 | cyclic = [] |
719 | for p, rs in tree.items(): | |
720 | for r in rs: | |
721 | if p.key in index.get(r.key, []): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
722 | p_as_dep_of_r = [x for x in tree.get(tree.get_node_as_parent(r.key)) if x.key == p.key][0] |
9218 | 723 | cyclic.append((p, r, p_as_dep_of_r)) |
724 | return cyclic | |
725 | ||
726 | ||
727 | def render_cycles_text(cycles): | |
728 | if cycles: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
729 | print("Warning!! Cyclic dependencies found:", file=sys.stderr) |
9218 | 730 | # List in alphabetical order of the dependency that's cycling |
731 | # (2nd item in the tuple) | |
732 | cycles = sorted(cycles, key=lambda xs: xs[1].key) | |
733 | for a, b, c in cycles: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
734 | print(f"* {a.project_name} => {b.project_name} => {c.project_name}", file=sys.stderr) |
9218 | 735 | |
736 | ||
737 | def get_parser(): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
738 | parser = argparse.ArgumentParser(description="Dependency tree of the installed python packages") |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
739 | parser.add_argument("-v", "--version", action="version", version=f"{__version__}") |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
740 | parser.add_argument("-f", "--freeze", action="store_true", help="Print names so as to write freeze files") |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
741 | parser.add_argument( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
742 | "--python", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
743 | default=sys.executable, |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
744 | help="Python to use to look for packages in it (default: where" " installed)", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
745 | ) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
746 | parser.add_argument("-a", "--all", action="store_true", help="list all deps at top level") |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
747 | parser.add_argument( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
748 | "-l", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
749 | "--local-only", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
750 | action="store_true", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
751 | help="If in a virtualenv that has global access " "do not show globally installed packages", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
752 | ) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
753 | parser.add_argument("-u", "--user-only", action="store_true", help="Only show installations in the user site dir") |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
754 | parser.add_argument( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
755 | "-w", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
756 | "--warn", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
757 | action="store", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
758 | dest="warn", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
759 | nargs="?", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
760 | default="suppress", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
761 | choices=("silence", "suppress", "fail"), |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
762 | help=( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
763 | 'Warning control. "suppress" will show warnings ' |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
764 | "but return 0 whether or not they are present. " |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
765 | '"silence" will not show warnings at all and ' |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
766 | 'always return 0. "fail" will show warnings and ' |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
767 | "return 1 if any are present. The default is " |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
768 | '"suppress".' |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
769 | ), |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
770 | ) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
771 | parser.add_argument( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
772 | "-r", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
773 | "--reverse", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
774 | action="store_true", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
775 | default=False, |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
776 | help=( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
777 | "Shows the dependency tree in the reverse fashion " |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
778 | "ie. the sub-dependencies are listed with the " |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
779 | "list of packages that need them under them." |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
780 | ), |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
781 | ) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
782 | parser.add_argument( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
783 | "-p", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
784 | "--packages", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
785 | help="Comma separated list of select packages to show " "in the output. If set, --all will be ignored.", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
786 | ) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
787 | parser.add_argument( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
788 | "-e", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
789 | "--exclude", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
790 | help="Comma separated list of select packages to exclude " "from the output. If set, --all will be ignored.", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
791 | metavar="PACKAGES", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
792 | ) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
793 | parser.add_argument( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
794 | "-j", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
795 | "--json", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
796 | action="store_true", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
797 | default=False, |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
798 | help=( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
799 | "Display dependency tree as json. This will yield " |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
800 | '"raw" output that may be used by external tools. ' |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
801 | "This option overrides all other options." |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
802 | ), |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
803 | ) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
804 | parser.add_argument( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
805 | "--json-tree", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
806 | action="store_true", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
807 | default=False, |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
808 | help=( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
809 | "Display dependency tree as json which is nested " |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
810 | "the same way as the plain text output printed by default. " |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
811 | "This option overrides all other options (except --json)." |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
812 | ), |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
813 | ) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
814 | parser.add_argument( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
815 | "--graph-output", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
816 | dest="output_format", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
817 | help=( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
818 | "Print a dependency graph in the specified output " |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
819 | "format. Available are all formats supported by " |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
820 | "GraphViz, e.g.: dot, jpeg, pdf, png, svg" |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
821 | ), |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
822 | ) |
9218 | 823 | return parser |
824 | ||
825 | ||
826 | def _get_args(): | |
827 | parser = get_parser() | |
828 | return parser.parse_args() | |
829 | ||
830 | ||
831 | def handle_non_host_target(args): | |
832 | of_python = os.path.abspath(args.python) | |
833 | # if target is not current python re-invoke it under the actual host | |
834 | if of_python != os.path.abspath(sys.executable): | |
835 | # there's no way to guarantee that graphviz is available, so refuse | |
836 | if args.output_format: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
837 | print("graphviz functionality is not supported when querying" " non-host python", file=sys.stderr) |
9218 | 838 | raise SystemExit(1) |
839 | argv = sys.argv[1:] # remove current python executable | |
840 | for py_at, value in enumerate(argv): | |
841 | if value == "--python": | |
842 | del argv[py_at] | |
843 | del argv[py_at] | |
844 | elif value.startswith("--python"): | |
845 | del argv[py_at] | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
846 | |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
847 | main_file = inspect.getsourcefile(sys.modules[__name__]) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
848 | with tempfile.TemporaryDirectory() as project: |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
849 | dest = os.path.join(project, "pipdeptree") |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
850 | shutil.copytree(os.path.dirname(main_file), dest) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
851 | # invoke from an empty folder to avoid cwd altering sys.path |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
852 | env = os.environ.copy() |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
853 | env["PYTHONPATH"] = project |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
854 | cmd = [of_python, "-m", "pipdeptree"] |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
855 | cmd.extend(argv) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
856 | return subprocess.call(cmd, cwd=project, env=env) |
9218 | 857 | return None |
858 | ||
859 | ||
860 | def get_installed_distributions(local_only=False, user_only=False): | |
861 | try: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
862 | from pip._internal.metadata import pkg_resources |
9218 | 863 | except ImportError: |
864 | # For backward compatibility with python ver. 2.7 and pip | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
865 | # version 20.3.4 (the latest pip version that works with python |
9218 | 866 | # version 2.7) |
867 | from pip._internal.utils import misc | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
868 | |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
869 | return misc.get_installed_distributions(local_only=local_only, user_only=user_only) |
9218 | 870 | else: |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
871 | dists = pkg_resources.Environment.from_paths(None).iter_installed_distributions( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
872 | local_only=local_only, skip=(), user_only=user_only |
9218 | 873 | ) |
874 | return [d._dist for d in dists] | |
875 | ||
876 | ||
877 | def main(): | |
878 | args = _get_args() | |
879 | result = handle_non_host_target(args) | |
880 | if result is not None: | |
881 | return result | |
882 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
883 | pkgs = get_installed_distributions(local_only=args.local_only, user_only=args.user_only) |
9218 | 884 | |
885 | tree = PackageDAG.from_pkgs(pkgs) | |
886 | ||
887 | is_text_output = not any([args.json, args.json_tree, args.output_format]) | |
888 | ||
889 | return_code = 0 | |
890 | ||
891 | # Before any reversing or filtering, show warnings to console | |
892 | # about possibly conflicting or cyclic deps if found and warnings | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
893 | # are enabled (i.e. only if output is to be printed to console) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
894 | if is_text_output and args.warn != "silence": |
9218 | 895 | conflicts = conflicting_deps(tree) |
896 | if conflicts: | |
897 | render_conflicts_text(conflicts) | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
898 | print("-" * 72, file=sys.stderr) |
9218 | 899 | |
900 | cycles = cyclic_deps(tree) | |
901 | if cycles: | |
902 | render_cycles_text(cycles) | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
903 | print("-" * 72, file=sys.stderr) |
9218 | 904 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
905 | if args.warn == "fail" and (conflicts or cycles): |
9218 | 906 | return_code = 1 |
907 | ||
908 | # Reverse the tree (if applicable) before filtering, thus ensuring | |
909 | # that the filter will be applied on ReverseTree | |
910 | if args.reverse: | |
911 | tree = tree.reverse() | |
912 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
913 | show_only = set(args.packages.split(",")) if args.packages else None |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
914 | exclude = set(args.exclude.split(",")) if args.exclude else None |
9218 | 915 | |
916 | if show_only is not None or exclude is not None: | |
917 | tree = tree.filter(show_only, exclude) | |
918 | ||
919 | if args.json: | |
920 | print(render_json(tree, indent=4)) | |
921 | elif args.json_tree: | |
922 | print(render_json_tree(tree, indent=4)) | |
923 | elif args.output_format: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
924 | output = dump_graphviz(tree, output_format=args.output_format, is_reverse=args.reverse) |
9218 | 925 | print_graphviz(output) |
926 | else: | |
927 | render_text(tree, args.all, args.freeze) | |
928 | ||
929 | return return_code | |
930 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
931 | # |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
932 | # eric-ide modification: entry point to get one self-contained script |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
933 | # |
9218 | 934 | |
935 | if __name__ == '__main__': | |
936 | sys.exit(main()) |