Sun, 05 Mar 2023 12:26:12 +0100
Third Party packages
- Upgraded eradicate to version 2.2.0.
- Upgraded pipdeptree to version 2.5.2.
- Upgraded pip-licenses to version 4.1.0.
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 |
9849
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
49 | from textwrap import dedent |
9218 | 50 | |
51 | from pip._vendor import pkg_resources | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
52 | |
9218 | 53 | try: |
54 | from pip._internal.operations.freeze import FrozenRequirement | |
55 | except ImportError: | |
56 | from pip import FrozenRequirement | |
57 | ||
58 | ||
9849
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
59 | __version__ = '2.5.2' # eric-ide modification: from version.py |
9218 | 60 | |
61 | ||
62 | flatten = chain.from_iterable | |
63 | ||
64 | ||
65 | def sorted_tree(tree): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
66 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
67 | 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
|
68 | alphabetical order of the package names. |
9218 | 69 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
70 | :param dict tree: the pkg dependency tree obtained by calling `construct_tree` function |
9218 | 71 | :returns: sorted tree |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
72 | :rtype: dict |
9218 | 73 | """ |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
74 | return {k: sorted(v) for k, v in sorted(tree.items())} |
9218 | 75 | |
76 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
77 | def guess_version(pkg_key, default="?"): |
9218 | 78 | """Guess the version of a pkg when pip doesn't provide it |
79 | ||
80 | :param str pkg_key: key of the package | |
81 | :param str default: default version to return if unable to find | |
82 | :returns: version | |
83 | :rtype: string | |
84 | """ | |
85 | try: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
86 | if sys.version_info >= (3, 8): # pragma: >=3.8 cover |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
87 | import importlib.metadata as importlib_metadata |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
88 | else: # pragma: <3.8 cover |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
89 | import importlib_metadata |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
90 | return importlib_metadata.version(pkg_key) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
91 | except ImportError: |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
92 | pass |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
93 | # 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
|
94 | if pkg_key in {"setuptools"}: |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
95 | return default |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
96 | try: |
9218 | 97 | m = import_module(pkg_key) |
98 | except ImportError: | |
99 | return default | |
100 | else: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
101 | v = getattr(m, "__version__", default) |
9218 | 102 | if inspect.ismodule(v): |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
103 | return getattr(v, "__version__", default) |
9218 | 104 | else: |
105 | return v | |
106 | ||
107 | ||
108 | def frozen_req_from_dist(dist): | |
109 | # The `pip._internal.metadata` modules were introduced in 21.1.1 | |
110 | # and the `pip._internal.operations.freeze.FrozenRequirement` | |
111 | # class now expects dist to be a subclass of | |
112 | # `pip._internal.metadata.BaseDistribution`, however the | |
113 | # `pip._internal.utils.misc.get_installed_distributions` continues | |
114 | # to return objects of type | |
115 | # pip._vendor.pkg_resources.DistInfoDistribution. | |
116 | # | |
117 | # This is a hacky backward compatible (with older versions of pip) | |
118 | # fix. | |
119 | try: | |
120 | from pip._internal import metadata | |
121 | except ImportError: | |
122 | pass | |
123 | else: | |
124 | dist = metadata.pkg_resources.Distribution(dist) | |
125 | ||
126 | try: | |
127 | return FrozenRequirement.from_dist(dist) | |
128 | except TypeError: | |
129 | return FrozenRequirement.from_dist(dist, []) | |
130 | ||
131 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
132 | class Package: |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
133 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
134 | 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
|
135 | for `render_as_root` and `render_as_branch` methods. |
9218 | 136 | """ |
137 | ||
138 | def __init__(self, obj): | |
139 | self._obj = obj | |
140 | self.project_name = obj.project_name | |
141 | self.key = obj.key | |
142 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
143 | def render_as_root(self, frozen): # noqa: U100 |
9218 | 144 | return NotImplementedError |
145 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
146 | def render_as_branch(self, frozen): # noqa: U100 |
9218 | 147 | return NotImplementedError |
148 | ||
149 | def render(self, parent=None, frozen=False): | |
150 | if not parent: | |
151 | return self.render_as_root(frozen) | |
152 | else: | |
153 | return self.render_as_branch(frozen) | |
154 | ||
155 | @staticmethod | |
156 | def frozen_repr(obj): | |
157 | fr = frozen_req_from_dist(obj) | |
158 | return str(fr).strip() | |
159 | ||
160 | def __getattr__(self, key): | |
161 | return getattr(self._obj, key) | |
162 | ||
163 | def __repr__(self): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
164 | return f'<{self.__class__.__name__}("{self.key}")>' |
9218 | 165 | |
166 | def __lt__(self, rhs): | |
167 | return self.key < rhs.key | |
168 | ||
169 | ||
170 | class DistPackage(Package): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
171 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
172 | Wrapper class for pkg_resources.Distribution instances |
9218 | 173 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
174 | :param obj: pkg_resources.Distribution to wrap over |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
175 | :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
|
176 | in reverse |
9218 | 177 | """ |
178 | ||
179 | def __init__(self, obj, req=None): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
180 | super().__init__(obj) |
9218 | 181 | self.version_spec = None |
182 | self.req = req | |
183 | ||
184 | def render_as_root(self, frozen): | |
185 | if not frozen: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
186 | return f"{self.project_name}=={self.version}" |
9218 | 187 | else: |
188 | return self.__class__.frozen_repr(self._obj) | |
189 | ||
190 | def render_as_branch(self, frozen): | |
191 | assert self.req is not None | |
192 | if not frozen: | |
193 | parent_ver_spec = self.req.version_spec | |
194 | parent_str = self.req.project_name | |
195 | if parent_ver_spec: | |
196 | parent_str += parent_ver_spec | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
197 | return f"{self.project_name}=={self.version} [requires: {parent_str}]" |
9218 | 198 | else: |
199 | return self.render_as_root(frozen) | |
200 | ||
201 | def as_requirement(self): | |
202 | """Return a ReqPackage representation of this DistPackage""" | |
203 | return ReqPackage(self._obj.as_requirement(), dist=self) | |
204 | ||
205 | def as_parent_of(self, req): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
206 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
207 | 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
|
208 | PackageDAG. |
9218 | 209 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
210 | If `req` is None, and the `req` attribute of the current instance is also None, then the same instance will be |
9218 | 211 | returned. |
212 | ||
213 | :param ReqPackage req: the requirement to associate with | |
214 | :returns: DistPackage instance | |
215 | """ | |
216 | if req is None and self.req is None: | |
217 | return self | |
218 | return self.__class__(self._obj, req) | |
219 | ||
220 | def as_dict(self): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
221 | return {"key": self.key, "package_name": self.project_name, "installed_version": self.version} |
9218 | 222 | |
223 | ||
224 | class ReqPackage(Package): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
225 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
226 | Wrapper class for Requirements instance |
9218 | 227 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
228 | :param obj: The `Requirements` instance to wrap over |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
229 | :param dist: optional `pkg_resources.Distribution` instance for this requirement |
9218 | 230 | """ |
231 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
232 | UNKNOWN_VERSION = "?" |
9218 | 233 | |
234 | def __init__(self, obj, dist=None): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
235 | super().__init__(obj) |
9218 | 236 | self.dist = dist |
237 | ||
238 | @property | |
239 | def version_spec(self): | |
240 | 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
|
241 | return ",".join(["".join(sp) for sp in specs]) if specs else None |
9218 | 242 | |
243 | @property | |
244 | def installed_version(self): | |
245 | if not self.dist: | |
246 | return guess_version(self.key, self.UNKNOWN_VERSION) | |
247 | return self.dist.version | |
248 | ||
249 | @property | |
250 | def is_missing(self): | |
251 | return self.installed_version == self.UNKNOWN_VERSION | |
252 | ||
253 | def is_conflicting(self): | |
254 | """If installed version conflicts with required version""" | |
255 | # unknown installed version is also considered conflicting | |
256 | if self.installed_version == self.UNKNOWN_VERSION: | |
257 | return True | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
258 | ver_spec = self.version_spec if self.version_spec else "" |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
259 | req_version_str = f"{self.project_name}{ver_spec}" |
9218 | 260 | req_obj = pkg_resources.Requirement.parse(req_version_str) |
261 | return self.installed_version not in req_obj | |
262 | ||
263 | def render_as_root(self, frozen): | |
264 | if not frozen: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
265 | return f"{self.project_name}=={self.installed_version}" |
9218 | 266 | elif self.dist: |
267 | return self.__class__.frozen_repr(self.dist._obj) | |
268 | else: | |
269 | return self.project_name | |
270 | ||
271 | def render_as_branch(self, frozen): | |
272 | if not frozen: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
273 | 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
|
274 | return f"{self.project_name} [required: {req_ver}, installed: {self.installed_version}]" |
9218 | 275 | else: |
276 | return self.render_as_root(frozen) | |
277 | ||
278 | def as_dict(self): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
279 | return { |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
280 | "key": self.key, |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
281 | "package_name": self.project_name, |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
282 | "installed_version": self.installed_version, |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
283 | "required_version": self.version_spec, |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
284 | } |
9218 | 285 | |
286 | ||
287 | class PackageDAG(Mapping): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
288 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
289 | 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
|
290 | datastructure. |
9218 | 291 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
292 | The nodes and their relationships (edges) are internally stored using a map as follows, |
9218 | 293 | |
294 | {a: [b, c], | |
295 | b: [d], | |
296 | c: [d, e], | |
297 | d: [e], | |
298 | e: [], | |
299 | f: [b], | |
300 | g: [e, f]} | |
301 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
302 | 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
|
303 | respectively. |
9218 | 304 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
305 | 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
|
306 | each item in values must be of class `ReqPackage`. (See also ReversedPackageDAG where the key and value types are |
9218 | 307 | interchanged). |
308 | """ | |
309 | ||
310 | @classmethod | |
311 | def from_pkgs(cls, pkgs): | |
312 | pkgs = [DistPackage(p) for p in pkgs] | |
313 | idx = {p.key: p for p in pkgs} | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
314 | m = {p: [ReqPackage(r, idx.get(r.key)) for r in p.requires()] for p in pkgs} |
9218 | 315 | return cls(m) |
316 | ||
317 | def __init__(self, m): | |
318 | """Initialize the PackageDAG object | |
319 | ||
320 | :param dict m: dict of node objects (refer class docstring) | |
321 | :returns: None | |
322 | :rtype: NoneType | |
323 | ||
324 | """ | |
325 | self._obj = m | |
326 | self._index = {p.key: p for p in list(self._obj)} | |
327 | ||
328 | def get_node_as_parent(self, node_key): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
329 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
330 | Get the node from the keys of the dict representing the DAG. |
9218 | 331 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
332 | 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
|
333 | Use this method to look up a node obj as a parent (from the keys of the dict) given a node key. |
9218 | 334 | |
335 | :param node_key: identifier corresponding to key attr of node obj | |
336 | :returns: node obj (as present in the keys of the dict) | |
337 | :rtype: Object | |
338 | """ | |
339 | try: | |
340 | return self._index[node_key] | |
341 | except KeyError: | |
342 | return None | |
343 | ||
344 | def get_children(self, node_key): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
345 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
346 | Get child nodes for a node by its key |
9218 | 347 | |
348 | :param str node_key: key of the node to get children of | |
349 | :returns: list of child nodes | |
350 | :rtype: ReqPackage[] | |
351 | """ | |
352 | node = self.get_node_as_parent(node_key) | |
353 | return self._obj[node] if node else [] | |
354 | ||
355 | def filter(self, include, exclude): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
356 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
357 | Filters nodes in a graph by given parameters |
9218 | 358 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
359 | If a node is included, then all it's children are also included. |
9218 | 360 | |
361 | :param set include: set of node keys to include (or None) | |
362 | :param set exclude: set of node keys to exclude (or None) | |
363 | :returns: filtered version of the graph | |
364 | :rtype: PackageDAG | |
365 | """ | |
366 | # If neither of the filters are specified, short circuit | |
367 | if include is None and exclude is None: | |
368 | return self | |
369 | ||
370 | # Note: In following comparisons, we use lower cased values so | |
371 | # that user may specify `key` or `project_name`. As per the | |
372 | # documentation, `key` is simply | |
373 | # `project_name.lower()`. Refer: | |
374 | # https://setuptools.readthedocs.io/en/latest/pkg_resources.html#distribution-objects | |
375 | if include: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
376 | include = {s.lower() for s in include} |
9218 | 377 | if exclude: |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
378 | exclude = {s.lower() for s in exclude} |
9218 | 379 | else: |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
380 | exclude = set() |
9218 | 381 | |
382 | # Check for mutual exclusion of show_only and exclude sets | |
383 | # after normalizing the values to lowercase | |
384 | if include and exclude: | |
385 | assert not (include & exclude) | |
386 | ||
387 | # Traverse the graph in a depth first manner and filter the | |
388 | # nodes according to `show_only` and `exclude` sets | |
389 | stack = deque() | |
390 | m = {} | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
391 | seen = set() |
9218 | 392 | for node in self._obj.keys(): |
393 | if node.key in exclude: | |
394 | continue | |
395 | if include is None or node.key in include: | |
396 | stack.append(node) | |
397 | while True: | |
398 | if len(stack) > 0: | |
399 | n = stack.pop() | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
400 | cldn = [c for c in self._obj[n] if c.key not in exclude] |
9218 | 401 | m[n] = cldn |
402 | seen.add(n.key) | |
403 | for c in cldn: | |
404 | if c.key not in seen: | |
405 | cld_node = self.get_node_as_parent(c.key) | |
406 | if cld_node: | |
407 | stack.append(cld_node) | |
408 | else: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
409 | # 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
|
410 | # a dependency is missing |
9218 | 411 | continue |
412 | else: | |
413 | break | |
414 | ||
415 | return self.__class__(m) | |
416 | ||
417 | def reverse(self): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
418 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
419 | Reverse the DAG, or turn it upside-down. |
9218 | 420 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
421 | In other words, the directions of edges of the nodes in the DAG will be reversed. |
9218 | 422 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
423 | 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
|
424 | 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
|
425 | 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
|
426 | 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
|
427 | "child" nodes is as per the reversed DAG. |
9218 | 428 | |
429 | :returns: DAG in the reversed form | |
430 | :rtype: ReversedPackageDAG | |
431 | """ | |
432 | m = defaultdict(list) | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
433 | child_keys = {r.key for r in chain.from_iterable(self._obj.values())} |
9218 | 434 | for k, vs in self._obj.items(): |
435 | for v in vs: | |
436 | # if v is already added to the dict, then ensure that | |
437 | # we are using the same object. This check is required | |
438 | # as we're using array mutation | |
439 | try: | |
440 | node = [p for p in m.keys() if p.key == v.key][0] | |
441 | except IndexError: | |
442 | node = v | |
443 | m[node].append(k.as_parent_of(v)) | |
444 | if k.key not in child_keys: | |
445 | m[k.as_requirement()] = [] | |
446 | return ReversedPackageDAG(dict(m)) | |
447 | ||
448 | def sort(self): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
449 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
450 | Return sorted tree in which the underlying _obj dict is an dict, sorted alphabetically by the keys. |
9218 | 451 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
452 | :returns: Instance of same class with dict |
9218 | 453 | """ |
454 | return self.__class__(sorted_tree(self._obj)) | |
455 | ||
456 | # Methods required by the abstract base class Mapping | |
457 | def __getitem__(self, *args): | |
458 | return self._obj.get(*args) | |
459 | ||
460 | def __iter__(self): | |
461 | return self._obj.__iter__() | |
462 | ||
463 | def __len__(self): | |
464 | return len(self._obj) | |
465 | ||
466 | ||
467 | class ReversedPackageDAG(PackageDAG): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
468 | """Representation of Package dependencies in the reverse order. |
9218 | 469 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
470 | 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
|
471 | be of type `ReqPackage` and each item in the values of type `DistPackage`. |
9218 | 472 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
473 | Typically, this object will be obtained by calling `PackageDAG.reverse`. |
9218 | 474 | """ |
475 | ||
476 | def reverse(self): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
477 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
478 | Reverse the already reversed DAG to get the PackageDAG again |
9218 | 479 | |
480 | :returns: reverse of the reversed DAG | |
481 | :rtype: PackageDAG | |
482 | """ | |
483 | m = defaultdict(list) | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
484 | child_keys = {r.key for r in chain.from_iterable(self._obj.values())} |
9218 | 485 | for k, vs in self._obj.items(): |
486 | for v in vs: | |
487 | try: | |
488 | node = [p for p in m.keys() if p.key == v.key][0] | |
489 | except IndexError: | |
490 | node = v.as_parent_of(None) | |
491 | m[node].append(k) | |
492 | if k.key not in child_keys: | |
493 | m[k.dist] = [] | |
494 | return PackageDAG(dict(m)) | |
495 | ||
496 | ||
497 | def render_text(tree, list_all=True, frozen=False): | |
498 | """Print tree as text on console | |
499 | ||
500 | :param dict tree: the package tree | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
501 | :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
|
502 | :param bool frozen: show the names of the pkgs in the output that's favourable to pip --freeze |
9218 | 503 | :returns: None |
504 | ||
505 | """ | |
506 | tree = tree.sort() | |
507 | nodes = tree.keys() | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
508 | branch_keys = {r.key for r in chain.from_iterable(tree.values())} |
9218 | 509 | use_bullets = not frozen |
510 | ||
511 | if not list_all: | |
512 | nodes = [p for p in nodes if p.key not in branch_keys] | |
513 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
514 | def aux(node, parent=None, indent=0, cur_chain=None): |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
515 | cur_chain = cur_chain or [] |
9218 | 516 | node_str = node.render(parent, frozen) |
517 | if parent: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
518 | prefix = " " * indent + ("- " if use_bullets else "") |
9218 | 519 | node_str = prefix + node_str |
520 | result = [node_str] | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
521 | children = [ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
522 | 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
|
523 | for c in tree.get_children(node.key) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
524 | if c.project_name not in cur_chain |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
525 | ] |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
526 | result += list(chain.from_iterable(children)) |
9218 | 527 | return result |
528 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
529 | lines = chain.from_iterable([aux(p) for p in nodes]) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
530 | print("\n".join(lines)) |
9218 | 531 | |
532 | ||
533 | def render_json(tree, indent): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
534 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
535 | Converts the tree into a flat json representation. |
9218 | 536 | |
537 | The json repr will be a list of hashes, each hash having 2 fields: | |
538 | - package | |
539 | - dependencies: list of dependencies | |
540 | ||
541 | :param dict tree: dependency tree | |
542 | :param int indent: no. of spaces to indent json | |
543 | :returns: json representation of the tree | |
544 | :rtype: str | |
545 | """ | |
546 | tree = tree.sort() | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
547 | return json.dumps( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
548 | [{"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
|
549 | ) |
9218 | 550 | |
551 | ||
552 | def render_json_tree(tree, indent): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
553 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
554 | Converts the tree into a nested json representation. |
9218 | 555 | |
556 | 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
|
557 | |
9218 | 558 | - package_name |
559 | - key | |
560 | - required_version | |
561 | - installed_version | |
562 | - dependencies: list of dependencies | |
563 | ||
564 | :param dict tree: dependency tree | |
565 | :param int indent: no. of spaces to indent json | |
566 | :returns: json representation of the tree | |
567 | :rtype: str | |
568 | """ | |
569 | tree = tree.sort() | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
570 | branch_keys = {r.key for r in chain.from_iterable(tree.values())} |
9218 | 571 | nodes = [p for p in tree.keys() if p.key not in branch_keys] |
572 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
573 | def aux(node, parent=None, cur_chain=None): |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
574 | if cur_chain is None: |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
575 | cur_chain = [node.project_name] |
9218 | 576 | |
577 | d = node.as_dict() | |
578 | if parent: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
579 | d["required_version"] = node.version_spec if node.version_spec else "Any" |
9218 | 580 | else: |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
581 | d["required_version"] = d["installed_version"] |
9218 | 582 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
583 | d["dependencies"] = [ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
584 | aux(c, parent=node, cur_chain=cur_chain + [c.project_name]) |
9218 | 585 | for c in tree.get_children(node.key) |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
586 | if c.project_name not in cur_chain |
9218 | 587 | ] |
588 | ||
589 | return d | |
590 | ||
591 | return json.dumps([aux(p) for p in nodes], indent=indent) | |
592 | ||
593 | ||
9849
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
594 | def render_mermaid(tree) -> str: |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
595 | """Produce a Mermaid flowchart from the dependency graph. |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
596 | |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
597 | :param dict tree: dependency graph |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
598 | """ |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
599 | # List of reserved keywords in Mermaid that cannot be used as node names. |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
600 | # See: https://github.com/mermaid-js/mermaid/issues/4182#issuecomment-1454787806 |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
601 | reserved_ids: set[str] = { |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
602 | "C4Component", |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
603 | "C4Container", |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
604 | "C4Deployment", |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
605 | "C4Dynamic", |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
606 | "_blank", |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
607 | "_parent", |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
608 | "_self", |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
609 | "_top", |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
610 | "call", |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
611 | "class", |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
612 | "classDef", |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
613 | "click", |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
614 | "end", |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
615 | "flowchart", |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
616 | "flowchart-v2", |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
617 | "graph", |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
618 | "interpolate", |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
619 | "linkStyle", |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
620 | "style", |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
621 | "subgraph", |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
622 | } |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
623 | node_ids_map: dict[str:str] = {} |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
624 | |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
625 | def mermaid_id(key: str) -> str: |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
626 | """Returns a valid Mermaid node ID from a string.""" |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
627 | # If we have already seen this key, return the canonical ID. |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
628 | canonical_id = node_ids_map.get(key) |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
629 | if canonical_id is not None: |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
630 | return canonical_id |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
631 | # If the key is not a reserved keyword, return it as is, and update the map. |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
632 | if key not in reserved_ids: |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
633 | node_ids_map[key] = key |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
634 | return key |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
635 | # If the key is a reserved keyword, append a number to it. |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
636 | number = 0 |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
637 | while True: |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
638 | new_id = f"{key}_{number}" |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
639 | if new_id not in node_ids_map: |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
640 | node_ids_map[key] = new_id |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
641 | return new_id |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
642 | number += 1 |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
643 | |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
644 | # Use a sets to avoid duplicate entries. |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
645 | nodes: set[str] = set() |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
646 | edges: set[str] = set() |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
647 | |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
648 | for pkg, deps in tree.items(): |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
649 | pkg_label = f"{pkg.project_name}\\n{pkg.version}" |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
650 | pkg_key = mermaid_id(pkg.key) |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
651 | nodes.add(f'{pkg_key}["{pkg_label}"]') |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
652 | for dep in deps: |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
653 | edge_label = dep.version_spec or "any" |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
654 | dep_key = mermaid_id(dep.key) |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
655 | if dep.is_missing: |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
656 | dep_label = f"{dep.project_name}\\n(missing)" |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
657 | nodes.add(f'{dep_key}["{dep_label}"]:::missing') |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
658 | edges.add(f"{pkg_key} -.-> {dep_key}") |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
659 | else: |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
660 | edges.add(f'{pkg_key} -- "{edge_label}" --> {dep_key}') |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
661 | |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
662 | # Produce the Mermaid Markdown. |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
663 | indent = " " * 4 |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
664 | output = dedent( |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
665 | f"""\ |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
666 | flowchart TD |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
667 | {indent}classDef missing stroke-dasharray: 5 |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
668 | """ |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
669 | ) |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
670 | # Sort the nodes and edges to make the output deterministic. |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
671 | output += indent |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
672 | output += f"\n{indent}".join(node for node in sorted(nodes)) |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
673 | output += "\n" + indent |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
674 | output += f"\n{indent}".join(edge for edge in sorted(edges)) |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
675 | output += "\n" |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
676 | return output |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
677 | |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
678 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
679 | def dump_graphviz(tree, output_format="dot", is_reverse=False): |
9218 | 680 | """Output dependency graph as one of the supported GraphViz output formats. |
681 | ||
682 | :param dict tree: dependency graph | |
683 | :param string output_format: output format | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
684 | :param bool is_reverse: reverse or not |
9218 | 685 | :returns: representation of tree in the specified output format |
686 | :rtype: str or binary representation depending on the output format | |
687 | ||
688 | """ | |
689 | try: | |
690 | from graphviz import Digraph | |
691 | except ImportError: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
692 | print("graphviz is not available, but necessary for the output " "option. Please install it.", file=sys.stderr) |
9218 | 693 | sys.exit(1) |
694 | ||
695 | try: | |
696 | from graphviz import parameters | |
697 | except ImportError: | |
698 | from graphviz import backend | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
699 | |
9218 | 700 | valid_formats = backend.FORMATS |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
701 | print( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
702 | "Deprecation warning! Please upgrade graphviz to version >=0.18.0 " |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
703 | "Support for older versions will be removed in upcoming release", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
704 | file=sys.stderr, |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
705 | ) |
9218 | 706 | else: |
707 | valid_formats = parameters.FORMATS | |
708 | ||
709 | if output_format not in valid_formats: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
710 | 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
|
711 | print(f"Supported formats are: {', '.join(sorted(valid_formats))}", file=sys.stderr) |
9218 | 712 | sys.exit(1) |
713 | ||
714 | graph = Digraph(format=output_format) | |
715 | ||
716 | if not is_reverse: | |
717 | for pkg, deps in tree.items(): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
718 | pkg_label = f"{pkg.project_name}\\n{pkg.version}" |
9218 | 719 | graph.node(pkg.key, label=pkg_label) |
720 | for dep in deps: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
721 | edge_label = dep.version_spec or "any" |
9218 | 722 | if dep.is_missing: |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
723 | dep_label = f"{dep.project_name}\\n(missing)" |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
724 | graph.node(dep.key, label=dep_label, style="dashed") |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
725 | graph.edge(pkg.key, dep.key, style="dashed") |
9218 | 726 | else: |
727 | graph.edge(pkg.key, dep.key, label=edge_label) | |
728 | else: | |
729 | for dep, parents in tree.items(): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
730 | dep_label = f"{dep.project_name}\\n{dep.installed_version}" |
9218 | 731 | graph.node(dep.key, label=dep_label) |
732 | for parent in parents: | |
733 | # req reference of the dep associated with this | |
734 | # particular parent package | |
735 | req_ref = parent.req | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
736 | edge_label = req_ref.version_spec or "any" |
9218 | 737 | graph.edge(dep.key, parent.key, label=edge_label) |
738 | ||
739 | # 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
|
740 | if output_format == "dot": |
9849
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
741 | # Emulates graphviz.dot.Dot.__iter__() to force the sorting of graph.body. |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
742 | # Fixes https://github.com/tox-dev/pipdeptree/issues/188 |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
743 | # That way we can guarantee the output of the dot format is deterministic |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
744 | # and stable. |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
745 | return "".join([tuple(graph)[0]] + sorted(graph.body) + [graph._tail]) |
9218 | 746 | |
747 | # As it's unknown if the selected output format is binary or not, try to | |
748 | # decode it as UTF8 and only print it out in binary if that's not possible. | |
749 | try: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
750 | return graph.pipe().decode("utf-8") |
9218 | 751 | except UnicodeDecodeError: |
752 | return graph.pipe() | |
753 | ||
754 | ||
755 | def print_graphviz(dump_output): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
756 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
757 | Dump the data generated by GraphViz to stdout. |
9218 | 758 | |
759 | :param dump_output: The output from dump_graphviz | |
760 | """ | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
761 | if hasattr(dump_output, "encode"): |
9218 | 762 | print(dump_output) |
763 | else: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
764 | with os.fdopen(sys.stdout.fileno(), "wb") as bytestream: |
9218 | 765 | bytestream.write(dump_output) |
766 | ||
767 | ||
768 | def conflicting_deps(tree): | |
9589
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 | Returns dependencies which are not present or conflict with the requirements of other packages. |
9218 | 771 | |
772 | e.g. will warn if pkg1 requires pkg2==2.0 and pkg2==1.0 is installed | |
773 | ||
774 | :param tree: the requirements tree (dict) | |
775 | :returns: dict of DistPackage -> list of unsatisfied/unknown ReqPackage | |
776 | :rtype: dict | |
777 | """ | |
778 | conflicting = defaultdict(list) | |
779 | for p, rs in tree.items(): | |
780 | for req in rs: | |
781 | if req.is_conflicting(): | |
782 | conflicting[p].append(req) | |
783 | return conflicting | |
784 | ||
785 | ||
786 | def render_conflicts_text(conflicts): | |
787 | if conflicts: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
788 | print("Warning!!! Possibly conflicting dependencies found:", file=sys.stderr) |
9218 | 789 | # Enforce alphabetical order when listing conflicts |
790 | pkgs = sorted(conflicts.keys()) | |
791 | for p in pkgs: | |
792 | pkg = p.render_as_root(False) | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
793 | print(f"* {pkg}", file=sys.stderr) |
9218 | 794 | for req in conflicts[p]: |
795 | req_str = req.render_as_branch(False) | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
796 | print(f" - {req_str}", file=sys.stderr) |
9218 | 797 | |
798 | ||
799 | def cyclic_deps(tree): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
800 | """ |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
801 | Return cyclic dependencies as list of tuples |
9218 | 802 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
803 | :param PackageDAG tree: package tree/dag |
9218 | 804 | :returns: list of tuples representing cyclic dependencies |
805 | :rtype: list | |
806 | """ | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
807 | index = {p.key: {r.key for r in rs} for p, rs in tree.items()} |
9218 | 808 | cyclic = [] |
809 | for p, rs in tree.items(): | |
810 | for r in rs: | |
811 | if p.key in index.get(r.key, []): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
812 | 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 | 813 | cyclic.append((p, r, p_as_dep_of_r)) |
814 | return cyclic | |
815 | ||
816 | ||
817 | def render_cycles_text(cycles): | |
818 | if cycles: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
819 | print("Warning!! Cyclic dependencies found:", file=sys.stderr) |
9218 | 820 | # List in alphabetical order of the dependency that's cycling |
821 | # (2nd item in the tuple) | |
822 | cycles = sorted(cycles, key=lambda xs: xs[1].key) | |
823 | for a, b, c in cycles: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
824 | print(f"* {a.project_name} => {b.project_name} => {c.project_name}", file=sys.stderr) |
9218 | 825 | |
826 | ||
827 | def get_parser(): | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
828 | 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
|
829 | parser.add_argument("-v", "--version", action="version", version=f"{__version__}") |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
830 | 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
|
831 | parser.add_argument( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
832 | "--python", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
833 | default=sys.executable, |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
834 | 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
|
835 | ) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
836 | 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
|
837 | parser.add_argument( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
838 | "-l", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
839 | "--local-only", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
840 | action="store_true", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
841 | 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
|
842 | ) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
843 | 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
|
844 | parser.add_argument( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
845 | "-w", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
846 | "--warn", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
847 | action="store", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
848 | dest="warn", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
849 | nargs="?", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
850 | default="suppress", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
851 | choices=("silence", "suppress", "fail"), |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
852 | help=( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
853 | 'Warning control. "suppress" will show warnings ' |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
854 | "but return 0 whether or not they are present. " |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
855 | '"silence" will not show warnings at all and ' |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
856 | 'always return 0. "fail" will show warnings and ' |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
857 | "return 1 if any are present. The default is " |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
858 | '"suppress".' |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
859 | ), |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
860 | ) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
861 | parser.add_argument( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
862 | "-r", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
863 | "--reverse", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
864 | action="store_true", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
865 | default=False, |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
866 | help=( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
867 | "Shows the dependency tree in the reverse fashion " |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
868 | "ie. the sub-dependencies are listed with the " |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
869 | "list of packages that need them under them." |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
870 | ), |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
871 | ) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
872 | parser.add_argument( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
873 | "-p", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
874 | "--packages", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
875 | 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
|
876 | ) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
877 | parser.add_argument( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
878 | "-e", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
879 | "--exclude", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
880 | 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
|
881 | metavar="PACKAGES", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
882 | ) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
883 | parser.add_argument( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
884 | "-j", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
885 | "--json", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
886 | action="store_true", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
887 | default=False, |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
888 | help=( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
889 | "Display dependency tree as json. This will yield " |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
890 | '"raw" output that may be used by external tools. ' |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
891 | "This option overrides all other options." |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
892 | ), |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
893 | ) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
894 | parser.add_argument( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
895 | "--json-tree", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
896 | action="store_true", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
897 | default=False, |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
898 | help=( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
899 | "Display dependency tree as json which is nested " |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
900 | "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
|
901 | "This option overrides all other options (except --json)." |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
902 | ), |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
903 | ) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
904 | parser.add_argument( |
9849
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
905 | "--mermaid", |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
906 | action="store_true", |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
907 | default=False, |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
908 | help=("Display dependency tree as a Maermaid graph. " "This option overrides all other options."), |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
909 | ) |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
910 | parser.add_argument( |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
911 | "--graph-output", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
912 | dest="output_format", |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
913 | help=( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
914 | "Print a dependency graph in the specified output " |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
915 | "format. Available are all formats supported by " |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
916 | "GraphViz, e.g.: dot, jpeg, pdf, png, svg" |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
917 | ), |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
918 | ) |
9218 | 919 | return parser |
920 | ||
921 | ||
922 | def _get_args(): | |
923 | parser = get_parser() | |
924 | return parser.parse_args() | |
925 | ||
926 | ||
927 | def handle_non_host_target(args): | |
928 | of_python = os.path.abspath(args.python) | |
929 | # if target is not current python re-invoke it under the actual host | |
930 | if of_python != os.path.abspath(sys.executable): | |
931 | # there's no way to guarantee that graphviz is available, so refuse | |
932 | if args.output_format: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
933 | print("graphviz functionality is not supported when querying" " non-host python", file=sys.stderr) |
9218 | 934 | raise SystemExit(1) |
935 | argv = sys.argv[1:] # remove current python executable | |
936 | for py_at, value in enumerate(argv): | |
937 | if value == "--python": | |
938 | del argv[py_at] | |
939 | del argv[py_at] | |
940 | elif value.startswith("--python"): | |
941 | del argv[py_at] | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
942 | |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
943 | main_file = inspect.getsourcefile(sys.modules[__name__]) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
944 | with tempfile.TemporaryDirectory() as project: |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
945 | dest = os.path.join(project, "pipdeptree") |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
946 | shutil.copytree(os.path.dirname(main_file), dest) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
947 | # 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
|
948 | env = os.environ.copy() |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
949 | env["PYTHONPATH"] = project |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
950 | cmd = [of_python, "-m", "pipdeptree"] |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
951 | cmd.extend(argv) |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
952 | return subprocess.call(cmd, cwd=project, env=env) |
9218 | 953 | return None |
954 | ||
955 | ||
956 | def get_installed_distributions(local_only=False, user_only=False): | |
957 | try: | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
958 | from pip._internal.metadata import pkg_resources |
9218 | 959 | except ImportError: |
960 | # 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
|
961 | # version 20.3.4 (the latest pip version that works with python |
9218 | 962 | # version 2.7) |
963 | from pip._internal.utils import misc | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
964 | |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
965 | return misc.get_installed_distributions(local_only=local_only, user_only=user_only) |
9218 | 966 | else: |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
967 | dists = pkg_resources.Environment.from_paths(None).iter_installed_distributions( |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
968 | local_only=local_only, skip=(), user_only=user_only |
9218 | 969 | ) |
970 | return [d._dist for d in dists] | |
971 | ||
972 | ||
973 | def main(): | |
974 | args = _get_args() | |
975 | result = handle_non_host_target(args) | |
976 | if result is not None: | |
977 | return result | |
978 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
979 | pkgs = get_installed_distributions(local_only=args.local_only, user_only=args.user_only) |
9218 | 980 | |
981 | tree = PackageDAG.from_pkgs(pkgs) | |
982 | ||
983 | is_text_output = not any([args.json, args.json_tree, args.output_format]) | |
984 | ||
985 | return_code = 0 | |
986 | ||
987 | # Before any reversing or filtering, show warnings to console | |
988 | # 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
|
989 | # 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
|
990 | if is_text_output and args.warn != "silence": |
9218 | 991 | conflicts = conflicting_deps(tree) |
992 | if conflicts: | |
993 | render_conflicts_text(conflicts) | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
994 | print("-" * 72, file=sys.stderr) |
9218 | 995 | |
996 | cycles = cyclic_deps(tree) | |
997 | if cycles: | |
998 | render_cycles_text(cycles) | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
999 | print("-" * 72, file=sys.stderr) |
9218 | 1000 | |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
1001 | if args.warn == "fail" and (conflicts or cycles): |
9218 | 1002 | return_code = 1 |
1003 | ||
1004 | # Reverse the tree (if applicable) before filtering, thus ensuring | |
1005 | # that the filter will be applied on ReverseTree | |
1006 | if args.reverse: | |
1007 | tree = tree.reverse() | |
1008 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
1009 | 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
|
1010 | exclude = set(args.exclude.split(",")) if args.exclude else None |
9218 | 1011 | |
1012 | if show_only is not None or exclude is not None: | |
1013 | tree = tree.filter(show_only, exclude) | |
1014 | ||
1015 | if args.json: | |
1016 | print(render_json(tree, indent=4)) | |
1017 | elif args.json_tree: | |
1018 | print(render_json_tree(tree, indent=4)) | |
9849
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
1019 | elif args.mermaid: |
99782ca569ed
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9653
diff
changeset
|
1020 | print(render_mermaid(tree)) |
9218 | 1021 | elif args.output_format: |
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
1022 | output = dump_graphviz(tree, output_format=args.output_format, is_reverse=args.reverse) |
9218 | 1023 | print_graphviz(output) |
1024 | else: | |
1025 | render_text(tree, args.all, args.freeze) | |
1026 | ||
1027 | return return_code | |
1028 | ||
9589
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
1029 | # |
09218eb3ae21
Third Party packages
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9218
diff
changeset
|
1030 | # 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
|
1031 | # |
9218 | 1032 | |
1033 | if __name__ == '__main__': | |
1034 | sys.exit(main()) |