Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a1d97da

Browse files
authored
Merge pull request winpython#1104 from stonebig/master
add piptree utility
2 parents 6669098 + ff2786f commit a1d97da

File tree

1 file changed

+188
-0
lines changed

1 file changed

+188
-0
lines changed

winpython/piptree.py

+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# -*- coding: utf-8 -*-
2+
import json, sys, re, platform, os
3+
from winpython import utils
4+
from collections import OrderedDict
5+
from pip._vendor.packaging.markers import Marker
6+
7+
8+
def normalize(this):
9+
"""apply https://peps.python.org/pep-0503/#normalized-names"""
10+
return re.sub(r"[-_.]+", "-", this).lower()
11+
12+
13+
class pipdata:
14+
"""Wrapper aroud pip inspect"""
15+
16+
def __init__(self):
17+
18+
# get pip_inpsect raw data in json form
19+
pip_inspect = utils.exec_run_cmd(["pip", "inspect"])
20+
pip_json = json.loads(pip_inspect)
21+
22+
# create a distro{} dict of Packages
23+
# key = normalised package name
24+
# string_elements = 'name', 'version', 'summary'
25+
# requires = list of dict with 1 level need downward
26+
# req_key = package_key requires
27+
# req_extra = extra branch needed of the package_key ('all' or '')
28+
# req_version = version needed
29+
# req_marker = marker of the requirement (if any)
30+
self.distro = {}
31+
replacements = str.maketrans({" ": "", "[": "", "]": "", "'": "", '"': ""})
32+
self.environment = {
33+
"implementation_name": sys.implementation.name,
34+
"implementation_version": "{0.major}.{0.minor}.{0.micro}".format(
35+
sys.implementation.version
36+
),
37+
"os_name": os.name,
38+
"platform_machine": platform.machine(),
39+
"platform_release": platform.release(),
40+
"platform_system": platform.system(),
41+
"platform_version": platform.version(),
42+
"python_full_version": platform.python_version(),
43+
"platform_python_implementation": platform.python_implementation(),
44+
"python_version": ".".join(platform.python_version_tuple()[:2]),
45+
"sys_platform": sys.platform,
46+
}
47+
48+
for p in pip_json["installed"]:
49+
meta = p["metadata"]
50+
name = meta["name"]
51+
key = normalize(name)
52+
requires = []
53+
if "requires_dist" in meta:
54+
for i in meta["requires_dist"]:
55+
det = (i + ";").split(";")
56+
req_nameextra = normalize((det[0] + " ").split(" ")[0])
57+
req_key = normalize((req_nameextra + "[").split("[")[0])
58+
req_key_extra = req_nameextra[len(req_key) + 1 :].split("]")[0]
59+
req_version = det[0][len(req_nameextra) :].translate(replacements)
60+
req_marker = det[1]
61+
62+
req_add = {
63+
"req_key": req_key,
64+
"req_version": req_version,
65+
"req_extra": req_key_extra,
66+
}
67+
# add the marker of the requirement, if not nothing:
68+
if not req_marker == "":
69+
req_add["req_marker"] = req_marker
70+
requires += [req_add]
71+
self.distro[key] = {
72+
"name": name,
73+
"version": meta["version"],
74+
"summary": meta["summary"] if "summary" in meta else "",
75+
"requires_dist": requires,
76+
"wanted_per": [],
77+
"description": meta["description"] if "description" in meta else "",
78+
}
79+
# On a second pass, complement distro in reverse mode with 'wanted-per':
80+
# - get all downward links in 'requires_dist' of each package
81+
# - feed the required packages 'wanted_per' as a reverse dict of dict
82+
# contains =
83+
# req_key = upstream package_key
84+
# req_version = downstream package version wanted
85+
# req_marker = marker of the downstream package requirement (if any)
86+
87+
for p in self.distro:
88+
for r in self.distro[p]["requires_dist"]:
89+
if r["req_key"] in self.distro:
90+
want_add = {
91+
"req_key": p,
92+
"req_version": r["req_version"],
93+
"req_extra": r["req_extra"],
94+
} # req_key_extra
95+
if "req_marker" in r:
96+
want_add["req_marker"] = r["req_marker"] # req_key_extra
97+
self.distro[r["req_key"]]["wanted_per"] += [want_add]
98+
99+
def _downraw(self, pp, extra="", version_req="", depth=20, path=[]):
100+
"""build a nested list of needed packages with given extra and depth"""
101+
envi = {"extra": extra, **self.environment}
102+
p = normalize(pp)
103+
ret_all = []
104+
if p in path:
105+
print("cycle!", "->".join(path + [p]))
106+
elif p in self.distro and len(path) <= depth:
107+
if extra == "":
108+
ret = [f'{p}=={self.distro[p]["version"]} {version_req}']
109+
else:
110+
ret = [f'{p}[{extra}]=={self.distro[p]["version"]} {version_req}']
111+
for r in self.distro[p]["requires_dist"]:
112+
if r["req_key"] in self.distro:
113+
if "req_marker" not in r or Marker(r["req_marker"]).evaluate(
114+
environment=envi
115+
):
116+
ret += self._downraw(
117+
r["req_key"],
118+
r["req_extra"],
119+
r["req_version"],
120+
depth,
121+
path + [p],
122+
)
123+
ret_all += [ret]
124+
return ret_all
125+
126+
def _upraw(self, pp, extra="", version_req="", depth=20, path=[]):
127+
"""build a nested list of user packages with given extra and depth"""
128+
envi = {"extra": extra, **self.environment}
129+
p = normalize(pp)
130+
ret_all = []
131+
if p in path:
132+
print("cycle!", "->".join(path + [p]))
133+
elif p in self.distro and len(path) <= depth:
134+
if extra == "":
135+
ret_all = [f'{p}=={self.distro[p]["version"]} {version_req}']
136+
else:
137+
ret_all = [f'{p}[{extra}]=={self.distro[p]["version"]} {version_req}']
138+
ret = []
139+
for r in self.distro[p]["wanted_per"]:
140+
if r["req_key"] in self.distro and r["req_key"] not in path:
141+
if "req_marker" not in r or Marker(r["req_marker"]).evaluate(
142+
environment=envi
143+
):
144+
ret += self._upraw(
145+
r["req_key"],
146+
"",
147+
f"[requires: {p}"
148+
+ (
149+
"[" + r["req_extra"] + "]"
150+
if r["req_extra"] != ""
151+
else ""
152+
)
153+
+ f'{r["req_version"]}]',
154+
depth,
155+
path + [p],
156+
)
157+
if not ret == []:
158+
ret_all += [ret]
159+
return ret_all
160+
161+
def down(self, pp="", extra="", depth=99, indent=5, version_req=""):
162+
"""print the downward requirements for the package or all packages"""
163+
if not pp == "":
164+
rawtext = json.dumps(
165+
self._downraw(pp, extra, version_req, depth), indent=indent
166+
)
167+
lines = [l for l in rawtext.split("\n") if len(l.strip()) > 2]
168+
print("\n".join(lines).replace('"', ""))
169+
else:
170+
for one_pp in sorted(distro):
171+
down(self, one_pp, extra, depth, indent, version_req)
172+
173+
def up(self, pp, extra="", depth=99, indent=5, version_req=""):
174+
"""print the upward needs for the package"""
175+
rawtext = json.dumps(self._upraw(pp, extra, version_req, depth), indent=indent)
176+
lines = [l for l in rawtext.split("\n") if len(l.strip()) > 2]
177+
print("\n".join(lines).replace('"', ""))
178+
179+
def description(self, pp):
180+
"return desciption of the package"
181+
if pp in self.distro:
182+
return print("\n".join(self.distro[pp]["description"].split(r"\n")))
183+
184+
def pip_list(self):
185+
""" do like pip list"""
186+
return [(p , self.distro[p]['version']) for p in sorted(self.distro)]
187+
188+

0 commit comments

Comments
 (0)