@@ -42,48 +42,69 @@ def get_installed_distributions(python_path=None):
42
42
return [d ._dist for d in dists ]
43
43
44
44
45
+ def _get_extra_deps_from_dist (dist ):
46
+ extra_deps = {}
47
+ if not dist :
48
+ return extra_deps
49
+ # all requirements, some of which may be extra-only:
50
+ reqs = dist .metadata .get_all ('Requires-Dist' ) or []
51
+ # extras this package defines:
52
+ extras = dist .metadata .get_all ('Provides-Extra' ) or []
53
+ for req_str in reqs :
54
+ req = Requirement (req_str )
55
+ if req .marker and 'extra' in str (req .marker ):
56
+ # evaluate marker for each declared extra
57
+ for extra in extras :
58
+ if req .marker .evaluate ({'extra' : extra }):
59
+ extra_deps .setdefault (extra , []).append ({"name" : str (req .name ), "versionSpecifiers" : str (req .specifier ), "url" : str (req .url ) if req .url else None })
60
+ return extra_deps
61
+
62
+
63
+ def _get_deps_from_extras (name_version_cache , name_dist_cache , extra_deps ):
64
+ dependencies = []
65
+ if not extra_deps :
66
+ return dependencies
67
+ # Treat an extra with the name all as dependencies
68
+ all_deps = extra_deps .get ("all" , [])
69
+ for dep in all_deps :
70
+ dversion = name_version_cache .get (dep ["name" ])
71
+ if not dversion :
72
+ continue
73
+ dversionSpecifiers = dep .get ("versionSpecifiers" )
74
+ dpurl = f"""pkg:pypi/{ dep ["name" ].lower ()} @{ dversion } """
75
+ dextra_deps = _get_extra_deps_from_dist (name_dist_cache .get (dep ["name" ]))
76
+ ddependencies = _get_deps_from_extras (name_version_cache , name_dist_cache , dextra_deps )
77
+ dependencies .append ({
78
+ "name" : dep ["name" ],
79
+ "version" : dversion ,
80
+ "versionSpecifiers" : dversionSpecifiers ,
81
+ "purl" : dpurl ,
82
+ "extra_deps" : dextra_deps ,
83
+ "dependencies" : ddependencies
84
+ })
85
+ return dependencies
86
+
87
+
45
88
def get_installed_with_extras ():
46
89
result = {}
47
90
if not REQUIREMENT_MODULE_FOUND :
48
91
return result
49
92
name_version_cache = {}
93
+ name_dist_cache = {}
50
94
for dist in importlib_metadata .distributions ():
51
95
name = dist .metadata ['Name' ]
52
96
version = dist .version or ""
53
97
name_version_cache [name ] = version
98
+ name_dist_cache [name ] = dist
54
99
for dist in importlib_metadata .distributions ():
55
100
name = dist .metadata ['Name' ]
56
101
version = dist .version or ""
57
102
# extras this package defines:
58
103
extras = dist .metadata .get_all ('Provides-Extra' ) or []
59
- # all requirements, some of which may be extra-only:
60
- reqs = dist .metadata .get_all ('Requires-Dist' ) or []
61
-
62
104
# map each extra → its extra-only dependencies
63
- extra_deps = {}
64
- for req_str in reqs :
65
- req = Requirement (req_str )
66
- if req .marker and 'extra' in str (req .marker ):
67
- # evaluate marker for each declared extra
68
- for extra in extras :
69
- if req .marker .evaluate ({'extra' : extra }):
70
- extra_deps .setdefault (extra , []).append ({"name" : str (req .name ), "versionSpecifiers" : str (req .specifier ), "url" : str (req .url ) if req .url else None })
105
+ extra_deps = _get_extra_deps_from_dist (dist )
71
106
purl = f"pkg:pypi/{ name .lower ()} @{ version } "
72
- dependencies = []
73
- # Treat an extra with the name all as dependencies
74
- all_deps = extra_deps .get ("all" , [])
75
- for dep in all_deps :
76
- dversion = name_version_cache .get (dep ["name" ])
77
- if not dversion :
78
- continue
79
- dversionSpecifiers = dep .get ("versionSpecifiers" )
80
- dpurl = f"""pkg:pypi/{ dep ["name" ].lower ()} @{ dversion } """
81
- dependencies .append ({
82
- "name" : dep ["name" ],
83
- "version" : dversion ,
84
- "versionSpecifiers" : dversionSpecifiers ,
85
- "purl" : dpurl
86
- })
107
+ dependencies = _get_deps_from_extras (name_version_cache , name_dist_cache , extra_deps )
87
108
result [purl ] = {
88
109
'name' : name ,
89
110
'version' : version ,
@@ -162,15 +183,8 @@ def main(argv):
162
183
"dependencies" : dependencies + all_dependencies ,
163
184
}
164
185
)
165
- all_deps = {}
166
- for t in tree :
167
- for d in t ["dependencies" ]:
168
- all_deps [d ["name" ]] = True
169
- trimmed_tree = [
170
- t for t in tree if t ["name" ] not in all_deps
171
- ]
172
186
with open (out_file , mode = "w" , encoding = "utf-8" ) as fp :
173
- json .dump (trimmed_tree , fp )
187
+ json .dump (tree , fp )
174
188
175
189
176
190
if __name__ == "__main__" :
0 commit comments