Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
mikf
GitHub Repository: mikf/gallery-dl
Path: blob/master/scripts/requirements.py
14119 views
1
#!/usr/bin/env python3
2
# -*- coding: utf-8 -*-
3
4
# Copyright 2026 Mike Fährmann
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License version 2 as
8
# published by the Free Software Foundation.
9
10
import util
11
import sys
12
import re
13
import argparse
14
import requests
15
16
session = None
17
results = {}
18
19
20
def package_hashes(pkg, args):
21
global session
22
if session is None:
23
session = requests.Session()
24
25
pkg, eq, version = pkg.partition("==")
26
if eq:
27
u = f"https://pypi.org/pypi/{pkg}/{version}/json"
28
else:
29
u = f"https://pypi.org/pypi/{pkg}/json"
30
d = session.get(u).json()
31
if not (i := d.get("info")):
32
return
33
34
n = i["name"]
35
results[n.lower()] = result = {
36
"name" : n,
37
"version": i["version"],
38
"files" : [],
39
"dependencies": [],
40
"extras" : {},
41
"parents": [],
42
}
43
44
def append(file):
45
files.append(((file["filename"], file["digests"]["sha256"])))
46
47
py3 = None
48
wheel = (0, None)
49
files = result["files"]
50
for u in d["urls"]:
51
if u.get("yanked"):
52
continue
53
if not args.sdist and u["packagetype"] == "sdist":
54
continue
55
56
v = u["python_version"]
57
if v == "py3":
58
py3 = u
59
continue
60
61
if args.freethreaded and args.freethreaded(u["filename"]):
62
continue
63
if args.architecture and not args.architecture(u["filename"]):
64
continue
65
if args.platform and not args.platform(u["filename"]):
66
continue
67
68
if v == "cp314":
69
wheel = (99, ())
70
elif v.startswith("cp3"):
71
if f"-{v}t-" in u["filename"]:
72
continue
73
v = int(v[3:])
74
if v > wheel[0]:
75
wheel = (v, [u])
76
elif v == wheel[0]:
77
wheel[1].append(u)
78
continue
79
else:
80
continue
81
82
append(u)
83
84
if not files and wheel[0]:
85
for u in wheel[1]:
86
append(u)
87
if not files and py3:
88
append(py3)
89
90
for d in i["requires_dist"] or ():
91
name = re.sub(r"([\w-]+).+", r"\1", d).lower()
92
93
pos = d.find(" extra == ")
94
if pos < 0:
95
result["dependencies"].append(name)
96
else:
97
extra = d[pos+11:d.find('"', pos+11)]
98
if e := result["extras"].get(extra):
99
e.append(name)
100
else:
101
result["extras"][extra] = [name]
102
103
return result
104
105
106
def collect(pkg, args, level=0, parent=None):
107
pkg = pkg.replace("_", "-")
108
pkgl = pkg.partition("==")[0].lower()
109
pkgl, ex, extras = pkgl.partition("[")
110
if pkgl in args.exclude:
111
return
112
if pkgl not in results:
113
if ex:
114
pkg = pkg.partition("[")[0]
115
results[pkgl] = info = package_hashes(pkg, args)
116
117
if args.dependencies > level:
118
for dep in info["dependencies"]:
119
collect(dep, args, level+1, pkg)
120
121
if not level:
122
if ex:
123
extras = (extras[:-1],)
124
else:
125
extras = info["extras"] if args.Extra else args.extra
126
for extra in extras:
127
for dep in info["extras"][extra]:
128
collect(dep, args, level+1, f"{pkgl}[{extra}]")
129
if parent:
130
results[pkgl]["parents"].append(parent)
131
132
133
def output(write, args):
134
pkgs = list(results.values())
135
pkgs.sort(key=lambda p: (
136
bool(p["parents"]), len(p["files"]), p["name"].lower()))
137
138
first = True
139
for pkg in pkgs:
140
if pkg["files"]:
141
if first:
142
first = False
143
else:
144
write("\n")
145
146
write(f'{pkg["name"]}=={pkg["version"]} \\\n')
147
for name, sha256 in pkg["files"]:
148
write(f' --hash=sha256:{sha256} \\\n')
149
if args.filenames:
150
for name, sha256 in pkg["files"]:
151
write(f' # {name} \\\n')
152
if pkg["parents"]:
153
parents = sorted(set(pkg["parents"]))
154
write(f' # from {", ".join(parents)}\n')
155
156
157
def parse_args(args=None):
158
parser = argparse.ArgumentParser(args)
159
parser.add_argument("-a", "--architecture", action="append", default=[])
160
parser.add_argument("-d", "--dependencies", action="count", default=0)
161
parser.add_argument("-D", "--Dependencies",
162
dest="dependencies", action="store_const", const=128)
163
parser.add_argument("-e", "--extra", action="append", default=[])
164
parser.add_argument("-E", "--Extra", action="store_true")
165
parser.add_argument("-f", "--freethreaded", action="store_true")
166
parser.add_argument("-F", "--filenames", action="store_true")
167
parser.add_argument("-i", "--input")
168
parser.add_argument("-N", "--no-clobber", default="w",
169
dest="mode", action="store_const", const="x")
170
parser.add_argument("-o", "--output")
171
parser.add_argument("-O", "--Output")
172
parser.add_argument("-p", "--platform", action="append", default=[])
173
parser.add_argument("-s", "--sdist", action="store_true")
174
parser.add_argument("-x", "--exclude", action="append", default=[])
175
176
parser.add_argument("--x32", "--x86", action="store_true")
177
parser.add_argument("--x64", "--x86_64", "--x86-64", "--amd64",
178
action="store_true")
179
parser.add_argument("--arm64", action="store_true")
180
181
parser.add_argument("--windows", action="store_true")
182
parser.add_argument("--linux", action="store_true")
183
parser.add_argument("--manylinux", "--glibc", action="store_true")
184
parser.add_argument("--musllinux", action="store_true")
185
parser.add_argument("--macosx", "--osx", action="store_true")
186
187
parser.add_argument("PKGS", nargs="*")
188
args = parser.parse_args()
189
190
if args.input:
191
pkgs = args.PKGS
192
with util.open(args.input) as fp:
193
for line in fp:
194
line = line.strip()
195
if not line or line[0] == "#":
196
continue
197
pkgs.append(line)
198
199
if args.Output:
200
if not args.pkgs:
201
args.pkgs = (args.Output,)
202
args.output = util.path("requirements", args.Output.lower())
203
else:
204
args.pkgs = args.PKGS
205
206
if args.freethreaded:
207
args.freethreaded = False
208
else:
209
args.freethreaded = re.compile(
210
r"p\d+t-").search
211
212
if args.x32:
213
args.architecture.append("win32")
214
if args.x64:
215
args.architecture.append("x86_64")
216
args.architecture.append("amd64")
217
if args.arm64:
218
args.architecture.append("universal2")
219
args.architecture.append("aarch64")
220
args.architecture.append("arm64")
221
if args.architecture:
222
args.architecture = re.compile(
223
fr"_(?:{'|'.join(args.architecture)})\.").search
224
225
if args.windows:
226
args.platform.append("win")
227
if args.linux:
228
args.platform.append("manylinux\\d*")
229
args.platform.append("musllinux")
230
if args.manylinux:
231
args.platform.append("manylinux")
232
if args.musllinux:
233
args.platform.append("musllinux")
234
if args.macosx:
235
args.platform.append("macosx")
236
if args.platform:
237
args.platform = re.compile(
238
fr"-(?:{'|'.join(args.platform)})_").search
239
240
return args
241
242
243
def main():
244
args = parse_args()
245
for pkg in args.pkgs:
246
collect(pkg, args)
247
248
if not args.output or args.output == "-":
249
output(sys.stdout.write, args)
250
else:
251
with util.lazy(args.output, args.mode) as fp:
252
output(fp.write, args)
253
254
255
if __name__ == "__main__":
256
main()
257
258