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