pipgrip 0.11.1__py2.py3-none-any.whl → 0.12.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pipgrip/_repo_version.py +1 -1
- pipgrip/cli.py +170 -28
- {pipgrip-0.11.1.dist-info → pipgrip-0.12.0.dist-info}/METADATA +11 -8
- {pipgrip-0.11.1.dist-info → pipgrip-0.12.0.dist-info}/RECORD +8 -8
- {pipgrip-0.11.1.dist-info → pipgrip-0.12.0.dist-info}/WHEEL +1 -1
- {pipgrip-0.11.1.dist-info → pipgrip-0.12.0.dist-info}/entry_points.txt +0 -0
- {pipgrip-0.11.1.dist-info → pipgrip-0.12.0.dist-info}/licenses/LICENSE +0 -0
- {pipgrip-0.11.1.dist-info → pipgrip-0.12.0.dist-info}/top_level.txt +0 -0
pipgrip/_repo_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
version = "0.
|
|
1
|
+
version = "0.12.0"
|
pipgrip/cli.py
CHANGED
|
@@ -42,7 +42,7 @@ from multiprocessing import cpu_count
|
|
|
42
42
|
from subprocess import CalledProcessError
|
|
43
43
|
|
|
44
44
|
import click
|
|
45
|
-
from anytree import AsciiStyle, ContStyle, Node, RenderTree
|
|
45
|
+
from anytree import AsciiStyle, ContStyle, Node, PreOrderIter, RenderTree
|
|
46
46
|
from anytree.exporter import DictExporter
|
|
47
47
|
from packaging.markers import default_environment
|
|
48
48
|
from packaging.requirements import InvalidRequirement
|
|
@@ -107,6 +107,47 @@ class DepTreeDictExporter(DictExporter):
|
|
|
107
107
|
return data
|
|
108
108
|
|
|
109
109
|
|
|
110
|
+
class ReversedDepTreeDictExporter(DictExporter):
|
|
111
|
+
"""Export reversed tree in full detail, children renamed to dependents."""
|
|
112
|
+
|
|
113
|
+
def __init__(
|
|
114
|
+
self, dictcls=OrderedDict, attriter=None, childiter=list, maxlevel=None
|
|
115
|
+
):
|
|
116
|
+
DictExporter.__init__(
|
|
117
|
+
self,
|
|
118
|
+
dictcls=dictcls,
|
|
119
|
+
attriter=attriter,
|
|
120
|
+
childiter=childiter,
|
|
121
|
+
maxlevel=maxlevel,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
@classmethod
|
|
125
|
+
def customsort(cls, tup):
|
|
126
|
+
order = ["name", "extras_name", "version", "pip_string", "requires"]
|
|
127
|
+
k, v = tup
|
|
128
|
+
if k in order:
|
|
129
|
+
return (str(order.index(k)), 0)
|
|
130
|
+
return tup
|
|
131
|
+
|
|
132
|
+
def export(self, node):
|
|
133
|
+
"""Export tree starting at `node`."""
|
|
134
|
+
attriter = self.attriter or partial(sorted, key=self.customsort)
|
|
135
|
+
return self.__export(node, self.dictcls, attriter, self.childiter)
|
|
136
|
+
|
|
137
|
+
def __export(self, node, dictcls, attriter, childiter, level=1):
|
|
138
|
+
attr_values = attriter(self._iter_attr_values(node))
|
|
139
|
+
data = dictcls(attr_values)
|
|
140
|
+
maxlevel = self.maxlevel
|
|
141
|
+
if maxlevel is None or level < maxlevel:
|
|
142
|
+
children = [
|
|
143
|
+
self.__export(child, dictcls, attriter, childiter, level=level + 1)
|
|
144
|
+
for child in childiter(node.children)
|
|
145
|
+
]
|
|
146
|
+
if children:
|
|
147
|
+
data["dependents"] = children
|
|
148
|
+
return data
|
|
149
|
+
|
|
150
|
+
|
|
110
151
|
def flatten(tree_dict):
|
|
111
152
|
"""Flatten tree_dict to a shallow OrderedDict with all unique exact pins."""
|
|
112
153
|
out = OrderedDict()
|
|
@@ -221,16 +262,18 @@ def render_tree(tree_root, max_depth, tree_ascii=False):
|
|
|
221
262
|
for fill, _, node in RenderTree(child, style=style):
|
|
222
263
|
if max_depth and node.depth > max_depth:
|
|
223
264
|
continue
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
#
|
|
233
|
-
|
|
265
|
+
# Build the parenthetical part
|
|
266
|
+
requires = getattr(node, "requires", None)
|
|
267
|
+
# fmt: off
|
|
268
|
+
cyclic = u", cyclic" if hasattr(node, "cyclic") else u""
|
|
269
|
+
if requires:
|
|
270
|
+
# Reversed tree dependent: name (version requires spec)
|
|
271
|
+
paren = u"{} requires {}{}".format(node.version, requires, cyclic)
|
|
272
|
+
else:
|
|
273
|
+
# Normal node: name (version)
|
|
274
|
+
paren = u"{}{}".format(node.version, cyclic)
|
|
275
|
+
lines.append(u"{}{} ({})".format(fill, node.pip_string, paren))
|
|
276
|
+
# fmt: on
|
|
234
277
|
output += lines
|
|
235
278
|
return "\n".join(output)
|
|
236
279
|
|
|
@@ -254,6 +297,94 @@ def render_json_tree_full(tree_root, max_depth, sort):
|
|
|
254
297
|
return tree_dict_full
|
|
255
298
|
|
|
256
299
|
|
|
300
|
+
def reverse_tree(tree_root):
|
|
301
|
+
"""Reverse the dependency tree to show dependents instead of dependencies.
|
|
302
|
+
|
|
303
|
+
Creates a new tree where:
|
|
304
|
+
- Each unique package becomes a root-level node (sorted alphabetically)
|
|
305
|
+
- Children are dependents with their requirement spec inline
|
|
306
|
+
- Format: name (version requires spec)
|
|
307
|
+
"""
|
|
308
|
+
# Build reverse mapping: {extras_name: [(dependent_extras_name, dependent_node, req_spec), ...]}
|
|
309
|
+
# Use extras_name as key to distinguish packages with different extras (e.g., etils[enp] vs etils[epy])
|
|
310
|
+
reverse_map = {}
|
|
311
|
+
for node in PreOrderIter(tree_root):
|
|
312
|
+
if node.name == "__root__":
|
|
313
|
+
continue
|
|
314
|
+
for child in node.children:
|
|
315
|
+
# child is the dependency, node is the dependent
|
|
316
|
+
# child.pip_string = how node requires child (e.g., "numpy>=1.7")
|
|
317
|
+
child_key = child.extras_name
|
|
318
|
+
if child_key not in reverse_map:
|
|
319
|
+
reverse_map[child_key] = []
|
|
320
|
+
req_spec = child.pip_string
|
|
321
|
+
# Avoid duplicate entries for same dependent
|
|
322
|
+
if not any(d[0] == node.extras_name for d in reverse_map[child_key]):
|
|
323
|
+
reverse_map[child_key].append((node.extras_name, node, req_spec))
|
|
324
|
+
|
|
325
|
+
# Collect all unique packages with their resolved versions
|
|
326
|
+
all_packages = {} # extras_name -> (version, extras_name, extras)
|
|
327
|
+
for node in PreOrderIter(tree_root):
|
|
328
|
+
if node.name != "__root__":
|
|
329
|
+
all_packages[node.extras_name] = (
|
|
330
|
+
node.version,
|
|
331
|
+
node.extras_name,
|
|
332
|
+
node.extras,
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
def add_dependents(parent_node, pkg_extras_name, visited):
|
|
336
|
+
"""Recursively add dependents with their requirement spec."""
|
|
337
|
+
dependents = reverse_map.get(pkg_extras_name, [])
|
|
338
|
+
for dependent_extras_name, dependent_node, req_spec in sorted(
|
|
339
|
+
dependents, key=lambda x: x[0]
|
|
340
|
+
):
|
|
341
|
+
dep_child = Node(
|
|
342
|
+
dependent_node.name,
|
|
343
|
+
parent=parent_node,
|
|
344
|
+
version=dependent_node.version,
|
|
345
|
+
extras_name=dependent_node.extras_name,
|
|
346
|
+
extras=dependent_node.extras,
|
|
347
|
+
pip_string=dependent_node.extras_name, # Just the name
|
|
348
|
+
requires=req_spec, # How this dependent requires the parent
|
|
349
|
+
)
|
|
350
|
+
if dependent_extras_name in visited:
|
|
351
|
+
dep_child.cyclic = True
|
|
352
|
+
else:
|
|
353
|
+
# Recursively add this dependent's dependents
|
|
354
|
+
add_dependents(
|
|
355
|
+
dep_child, dependent_extras_name, visited | {dependent_extras_name}
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
# Create reversed tree
|
|
359
|
+
reversed_root = Node("__root__")
|
|
360
|
+
|
|
361
|
+
# Create root-level nodes for each package (sorted alphabetically by extras_name)
|
|
362
|
+
for pkg_extras_name in sorted(all_packages.keys()):
|
|
363
|
+
version, extras_name, extras = all_packages[pkg_extras_name]
|
|
364
|
+
pkg_node = Node(
|
|
365
|
+
pkg_extras_name.split("[")[0], # Base name for node.name
|
|
366
|
+
parent=reversed_root,
|
|
367
|
+
version=version,
|
|
368
|
+
extras_name=extras_name,
|
|
369
|
+
extras=extras,
|
|
370
|
+
pip_string=extras_name, # e.g., "numpy" or "etils[enp]" - version shown in parentheses
|
|
371
|
+
)
|
|
372
|
+
# Recursively add dependents
|
|
373
|
+
add_dependents(pkg_node, pkg_extras_name, {pkg_extras_name})
|
|
374
|
+
|
|
375
|
+
return reversed_root
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def render_reversed_json_tree_full(reversed_tree_root, max_depth, sort):
|
|
379
|
+
"""Render reversed tree to JSON with 'dependents' instead of 'dependencies'."""
|
|
380
|
+
maxlevel = max_depth + 1 if max_depth else None
|
|
381
|
+
exporter = ReversedDepTreeDictExporter(
|
|
382
|
+
maxlevel=maxlevel, attriter=sorted if sort else None
|
|
383
|
+
)
|
|
384
|
+
tree_dict_full = exporter.export(reversed_tree_root)["dependents"]
|
|
385
|
+
return tree_dict_full
|
|
386
|
+
|
|
387
|
+
|
|
257
388
|
def render_lock(packages, include_dot=True, sort=False):
|
|
258
389
|
fn = sorted if sort else list
|
|
259
390
|
return fn(
|
|
@@ -304,7 +435,7 @@ def render_lock(packages, include_dot=True, sort=False):
|
|
|
304
435
|
@click.option(
|
|
305
436
|
"--json",
|
|
306
437
|
is_flag=True,
|
|
307
|
-
help="Output pins as JSON dict instead of newline-separated pins. Combine with --tree for a detailed nested JSON dependency tree.",
|
|
438
|
+
help="Output pins as JSON dict instead of newline-separated pins. Combine with --tree or --reversed-tree for a detailed nested JSON dependency tree.",
|
|
308
439
|
)
|
|
309
440
|
@click.option(
|
|
310
441
|
"--sort",
|
|
@@ -338,6 +469,11 @@ def render_lock(packages, include_dot=True, sort=False):
|
|
|
338
469
|
is_flag=True,
|
|
339
470
|
help="Output human readable dependency tree (bottom-up).",
|
|
340
471
|
)
|
|
472
|
+
@click.option(
|
|
473
|
+
"--reversed-tree-ascii",
|
|
474
|
+
is_flag=True,
|
|
475
|
+
help="Output human readable dependency tree (bottom-up) with ASCII tree markers.",
|
|
476
|
+
)
|
|
341
477
|
@click.option(
|
|
342
478
|
"--max-depth",
|
|
343
479
|
type=click.INT,
|
|
@@ -407,6 +543,7 @@ def main(
|
|
|
407
543
|
tree_json,
|
|
408
544
|
tree_json_exact,
|
|
409
545
|
reversed_tree,
|
|
546
|
+
reversed_tree_ascii,
|
|
410
547
|
max_depth,
|
|
411
548
|
cache_dir,
|
|
412
549
|
no_cache_dir,
|
|
@@ -429,23 +566,22 @@ def main(
|
|
|
429
566
|
logger.debug("pip version: %s", PIP_VERSION)
|
|
430
567
|
logger.debug("pipgrip version: %s", __version__)
|
|
431
568
|
|
|
432
|
-
if (
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
reversed_tree,
|
|
441
|
-
)
|
|
569
|
+
if sum(
|
|
570
|
+
(
|
|
571
|
+
pipe,
|
|
572
|
+
(json or tree or reversed_tree),
|
|
573
|
+
tree_ascii,
|
|
574
|
+
tree_json,
|
|
575
|
+
tree_json_exact,
|
|
576
|
+
reversed_tree_ascii,
|
|
442
577
|
)
|
|
443
|
-
|
|
444
|
-
):
|
|
578
|
+
) > 1 or (tree and (reversed_tree or reversed_tree_ascii)):
|
|
445
579
|
raise click.ClickException("Illegal combination of output formats selected")
|
|
446
580
|
|
|
447
|
-
if tree_ascii
|
|
581
|
+
if tree_ascii:
|
|
448
582
|
tree = True
|
|
583
|
+
if reversed_tree_ascii:
|
|
584
|
+
reversed_tree = True
|
|
449
585
|
elif tree_json_exact:
|
|
450
586
|
tree_json = True
|
|
451
587
|
|
|
@@ -561,9 +697,15 @@ def main(
|
|
|
561
697
|
)
|
|
562
698
|
|
|
563
699
|
if reversed_tree:
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
700
|
+
reversed_tree_root = reverse_tree(tree_root)
|
|
701
|
+
if json:
|
|
702
|
+
output = dumps(
|
|
703
|
+
render_reversed_json_tree_full(reversed_tree_root, max_depth, sort),
|
|
704
|
+
default=sorted,
|
|
705
|
+
)
|
|
706
|
+
else:
|
|
707
|
+
output = render_tree(reversed_tree_root, max_depth, reversed_tree_ascii)
|
|
708
|
+
elif tree:
|
|
567
709
|
if json:
|
|
568
710
|
output = dumps(
|
|
569
711
|
render_json_tree_full(tree_root, max_depth, sort), default=sorted
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pipgrip
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.0
|
|
4
4
|
Summary: Lightweight pip dependency resolver with deptree preview functionality based on the PubGrub algorithm
|
|
5
5
|
Home-page: https://github.com/ddelange/pipgrip
|
|
6
6
|
Author: ddelange
|
|
@@ -140,8 +140,8 @@ Options:
|
|
|
140
140
|
--pipe Output space-separated pins instead of newline-
|
|
141
141
|
separated pins.
|
|
142
142
|
--json Output pins as JSON dict instead of newline-
|
|
143
|
-
separated pins. Combine with --tree
|
|
144
|
-
nested JSON dependency tree.
|
|
143
|
+
separated pins. Combine with --tree or --reversed-
|
|
144
|
+
tree for a detailed nested JSON dependency tree.
|
|
145
145
|
--sort Sort pins alphabetically before writing out. Can
|
|
146
146
|
be used bare, or in combination with --lock,
|
|
147
147
|
--pipe, --json, --tree-json, or --tree-json-exact.
|
|
@@ -154,6 +154,8 @@ Options:
|
|
|
154
154
|
--tree-ascii Output human readable dependency tree with ASCII
|
|
155
155
|
tree markers.
|
|
156
156
|
--reversed-tree Output human readable dependency tree (bottom-up).
|
|
157
|
+
--reversed-tree-ascii Output human readable dependency tree (bottom-up)
|
|
158
|
+
with ASCII tree markers.
|
|
157
159
|
--max-depth INTEGER Maximum (JSON) tree rendering depth (default -1).
|
|
158
160
|
--cache-dir DIRECTORY Use a custom cache dir.
|
|
159
161
|
--no-cache-dir Disable pip cache for the wheels downloaded by
|
|
@@ -184,13 +186,14 @@ Exhaustive dependency trees without the need to install any packages ([at most b
|
|
|
184
186
|
```
|
|
185
187
|
$ pipgrip --tree pipgrip
|
|
186
188
|
|
|
187
|
-
pipgrip (0.
|
|
189
|
+
pipgrip (0.12.0)
|
|
188
190
|
├── anytree>=2.4.1 (2.13.0)
|
|
189
191
|
├── click>=7 (8.3.1)
|
|
190
|
-
├── packaging>=17 (
|
|
191
|
-
├── pip>=22.2 (
|
|
192
|
-
├── setuptools<81,>=38.3 (80.
|
|
193
|
-
└── wheel (0.
|
|
192
|
+
├── packaging>=17 (26.0)
|
|
193
|
+
├── pip>=22.2 (26.0)
|
|
194
|
+
├── setuptools<81,>=38.3 (80.10.2)
|
|
195
|
+
└── wheel (0.46.3)
|
|
196
|
+
└── packaging>=24.0 (26.0)
|
|
194
197
|
```
|
|
195
198
|
|
|
196
199
|
For more details/further processing, combine `--tree` with `--json` for a detailed nested JSON dependency tree. See also `--tree-ascii` (no unicode tree markers), and `--tree-json` & `--tree-json-exact` (simplified JSON dependency trees).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
pipgrip/__init__.py,sha256=eF4gP3t6HU78VGDzL3PdQUP6dt_aBGEXLl9Xge9dAu4,1924
|
|
2
|
-
pipgrip/_repo_version.py,sha256=
|
|
3
|
-
pipgrip/cli.py,sha256=
|
|
2
|
+
pipgrip/_repo_version.py,sha256=0d_eblF-LMXh1_mZw-q2DqmmX_vZj5tYDy8MONmqkNU,19
|
|
3
|
+
pipgrip/cli.py,sha256=7quSQL5L7qOr7RyR2_QhHUEyH9-j_nQMmX9EeXO7mPk,27062
|
|
4
4
|
pipgrip/compat.py,sha256=54TukmlLmNW_va39lA8QiQlfGohzbs0kPHTfa2nTRfE,1889
|
|
5
5
|
pipgrip/package_source.py,sha256=hGGzNyjHDgvDZrUjEGhjilfjxllQa-wkj8ucPXnKfwg,10035
|
|
6
6
|
pipgrip/pipper.py,sha256=G_FvgYVEDeCq-qf-0-s5vAdzhR4fXQJnfJnooCpNyI8,19582
|
|
@@ -29,9 +29,9 @@ pipgrip/libs/semver/version.py,sha256=uNAkxpcQgWj047IJtZB9Fo_UlRFWCSAs8RxQ75ckTO
|
|
|
29
29
|
pipgrip/libs/semver/version_constraint.py,sha256=nOFmEpOHCzpfqO1LFnXSqyZGGeN4mjLT228KYqh1qFY,2539
|
|
30
30
|
pipgrip/libs/semver/version_range.py,sha256=NkmqYueJ_j8kxKd00BEykHFOWI39r31s38Csj8hu4-w,15350
|
|
31
31
|
pipgrip/libs/semver/version_union.py,sha256=8ngwRmFKaOKmZ10M-ebyeRNB9uVl4HNL7FzoZYmOgDE,9801
|
|
32
|
-
pipgrip-0.
|
|
33
|
-
pipgrip-0.
|
|
34
|
-
pipgrip-0.
|
|
35
|
-
pipgrip-0.
|
|
36
|
-
pipgrip-0.
|
|
37
|
-
pipgrip-0.
|
|
32
|
+
pipgrip-0.12.0.dist-info/licenses/LICENSE,sha256=ZoxfsQqxkYOcxTHFEye8YJRUUBAJISfdHej3Di7u_Bs,1587
|
|
33
|
+
pipgrip-0.12.0.dist-info/METADATA,sha256=ts6W4XWRCvtoHLCNO88q-woRUSiaAOgm6BkcTrPXmJM,18895
|
|
34
|
+
pipgrip-0.12.0.dist-info/WHEEL,sha256=Mk1ST5gDzEO5il5kYREiBnzzM469m5sI8ESPl7TRhJY,110
|
|
35
|
+
pipgrip-0.12.0.dist-info/entry_points.txt,sha256=VGqby8sWTjfkK20Vj_FqBZ_UxBlgmAOzq4rcJLYlRq0,45
|
|
36
|
+
pipgrip-0.12.0.dist-info/top_level.txt,sha256=upoyu3ujOmKRvBUtTrwzk58e-r6zJahuT_8-RDsd2p0,8
|
|
37
|
+
pipgrip-0.12.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|