Skip to content

Commit e08255b

Browse files
committed
wip
1 parent 3407856 commit e08255b

File tree

1 file changed

+108
-29
lines changed

1 file changed

+108
-29
lines changed

scripts/populate_tox/populate_tox.py

Lines changed: 108 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
import configparser
22
import functools
3+
import time
34
from datetime import datetime, timedelta
45
from pathlib import Path
56

67
import requests
78

8-
from ..split_tox_gh_actions.split_tox_gh_actions import GROUPS
9-
10-
print(GROUPS)
119

1210
# Only consider package versions going back this far
13-
CUTOFF = datetime.now() - timedelta(days=365 * 3)
11+
CUTOFF = datetime.now() - timedelta(days=365 * 5)
1412
LOWEST_SUPPORTED_PY_VERSION = "3.6"
1513

1614
TOX_FILE = Path(__file__).resolve().parent.parent.parent / "tox.ini"
1715

1816
PYPI_PROJECT_URL = "https://pypi.python.org/pypi/{project}/json"
1917
PYPI_VERSION_URL = "https://pypi.python.org/pypi/{project}/{version}/json"
2018

19+
CLASSIFIER_PREFIX = "Programming Language :: Python :: "
20+
2121
EXCLUDE = {
2222
"common",
2323
}
@@ -27,7 +27,7 @@
2727

2828
@functools.total_ordering
2929
class Version:
30-
def __init__(self, version, metadata):
30+
def __init__(self, version, metadata=None):
3131
self.raw = version
3232
self.metadata = metadata
3333

@@ -36,10 +36,14 @@ def __init__(self, version, metadata):
3636
self.patch = None
3737
self.parsed = None
3838

39+
self.python_versions = []
40+
3941
try:
4042
parsed = version.split(".")
41-
if parsed[2].isnumeric():
43+
if len(parsed) == 3 and parsed[2].isnumeric():
4244
self.major, self.minor, self.patch = (int(p) for p in parsed)
45+
elif len(parsed) == 2 and parsed[1].isnumeric():
46+
self.major, self.minor = (int(p) for p in parsed)
4347
except Exception:
4448
# This will fail for e.g. prereleases, but we don't care about those
4549
# for now
@@ -97,50 +101,125 @@ def parse_tox():
97101
print(f"ERROR reading line {line}")
98102

99103

100-
def fetch_metadata(package):
104+
def fetch_package(package: str) -> dict:
105+
"""Fetch package metadata from PYPI."""
101106
url = PYPI_PROJECT_URL.format(project=package)
102107
pypi_data = requests.get(url)
103108

104109
if pypi_data.status_code != 200:
105110
print(f"{package} not found")
106111

107-
import pprint
108-
109-
pprint.pprint(package)
110-
pprint.pprint(pypi_data.json())
111112
return pypi_data.json()
112113

113114

114-
def parse_metadata(data):
115-
package = data["info"]["name"]
115+
def get_releases(pypi_data: dict) -> list[Version]:
116+
package = pypi_data["info"]["name"]
116117

117-
majors = {}
118+
versions = []
119+
120+
for release, metadata in pypi_data["releases"].items():
121+
if not metadata:
122+
continue
118123

119-
for release, metadata in data["releases"].items():
120124
meta = metadata[0]
121125
if datetime.fromisoformat(meta["upload_time"]) < CUTOFF:
122126
continue
123127

124128
version = Version(release, meta)
125129
if not version.valid:
126-
print(f"Failed to parse version {release} of package {package}")
130+
print(
131+
f"Failed to parse version {release} of package {package}. Ignoring..."
132+
)
127133
continue
128134

129-
if version.major not in majors:
130-
# 0 -> [min 0.x version, max 0.x version]
131-
majors[version.major] = [version, version]
132-
continue
135+
versions.append(version)
133136

134-
if version < majors[version.major][0]:
135-
majors[version.major][0] = version
136-
if version > majors[version.major][1]:
137-
majors[version.major][1] = version
137+
return sorted(versions)
138+
139+
140+
def pick_releases_to_test(releases: list[Version]) -> list[Version]:
141+
indexes = [
142+
0, # oldest version younger than CUTOFF
143+
len(releases) // 3,
144+
len(releases) // 3 * 2,
145+
-1, # latest
146+
]
147+
return [releases[i] for i in indexes]
148+
149+
150+
def fetch_release(package: str, version: Version) -> dict:
151+
url = PYPI_VERSION_URL.format(project=package, version=version)
152+
pypi_data = requests.get(url)
153+
154+
if pypi_data.status_code != 200:
155+
print(f"{package} not found")
156+
157+
return pypi_data.json()
158+
159+
160+
def determine_python_versions(
161+
package: str, version: Version, pypi_data: dict
162+
) -> list[str]:
163+
try:
164+
classifiers = pypi_data["info"]["classifiers"]
165+
except (AttributeError, IndexError):
166+
print(f"{package} {version} has no classifiers")
167+
return []
168+
169+
python_versions = []
170+
for classifier in classifiers:
171+
if classifier.startswith(CLASSIFIER_PREFIX):
172+
python_version = classifier[len(CLASSIFIER_PREFIX) :]
173+
if "." in python_version:
174+
# we don't care about stuff like
175+
# Programming Language :: Python :: 3 :: Only
176+
# Programming Language :: Python :: 3
177+
# etc., we're only interested in specific versions like 3.13
178+
python_versions.append(python_version)
179+
180+
python_versions = [
181+
version
182+
for version in python_versions
183+
if Version(version) >= Version(LOWEST_SUPPORTED_PY_VERSION)
184+
]
185+
186+
return python_versions
187+
188+
189+
def write_tox_file(package, versions):
190+
for version in versions:
191+
print(
192+
"{python_versions}-{package}-v{version}".format(
193+
python_versions=",".join([f"py{v}" for v in version.python_versions]),
194+
package=package,
195+
version=version,
196+
)
197+
)
198+
199+
print()
200+
201+
for version in versions:
202+
print(f"{package}-v{version}: {package}=={version.raw}")
203+
204+
205+
if __name__ == "__main__":
206+
for package in ("celery", "django"):
207+
pypi_data = fetch_package(package)
208+
releases = get_releases(pypi_data)
209+
test_releases = pick_releases_to_test(releases)
210+
for release in test_releases:
211+
release_pypi_data = fetch_release(package, release)
212+
release.python_versions = determine_python_versions(
213+
package, release, release_pypi_data
214+
)
215+
# XXX if no supported python versions -> delete
138216

139-
print(release, "not too old", meta["upload_time"])
217+
print(release, " on ", release.python_versions)
218+
time.sleep(0.1)
140219

141-
return majors
220+
print(releases)
221+
print(test_releases)
142222

223+
write_tox_file(package, test_releases)
143224

144-
print(parse_tox())
145-
print(packages)
146-
print(parse_metadata(fetch_metadata("celery")))
225+
print(parse_tox())

0 commit comments

Comments
 (0)