Skip to content

Commit 8f8bcf3

Browse files
authored
Merge pull request #56 from DavidCEllis/fix-online-search
Fix online search failing due to the presence of pymanager
2 parents 984eacf + 7d8864e commit 8f8bcf3

File tree

4 files changed

+54
-45
lines changed

4 files changed

+54
-45
lines changed

src/ducktools/pythonfinder/pythonorg_search.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
# ducktools-pythonfinder
22
# MIT License
3-
#
3+
#
44
# Copyright (c) 2023-2025 David C Ellis
5-
#
5+
#
66
# Permission is hereby granted, free of charge, to any person obtaining a copy
77
# of this software and associated documentation files (the "Software"), to deal
88
# in the Software without restriction, including without limitation the rights
99
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1010
# copies of the Software, and to permit persons to whom the Software is
1111
# furnished to do so, subject to the following conditions:
12-
#
12+
#
1313
# The above copyright notice and this permission notice shall be included in all
1414
# copies or substantial portions of the Software.
15-
#
15+
#
1616
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1717
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1818
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -31,6 +31,7 @@
3131

3232
import json
3333
import platform
34+
import re
3435

3536
from urllib.request import urlopen
3637

@@ -44,6 +45,8 @@
4445
RELEASE_PAGE = "https://www.python.org/api/v2/downloads/release/"
4546
RELEASE_FILE_PAGE = "https://www.python.org/api/v2/downloads/release_file/"
4647

48+
PYTHON_RELEASE_RE = re.compile(R"Python \d+\.\d+\.\d+(?:(?:a|b|rc)\d)?")
49+
4750

4851
class UnsupportedError(Exception):
4952
pass
@@ -206,13 +209,16 @@ class PythonOrgSearch(Prefab):
206209
def releases(self) -> list[PythonRelease]:
207210
"""Get all releases from python.org/api/v2/downloads/release"""
208211
if self._releases is None:
209-
if self.release_page_cache:
210-
data = json.loads(self.release_page_cache)
211-
else: # pragma: nocover
212+
if not self.release_page_cache:
212213
with urlopen(self.release_page) as req:
213-
data = json.load(req)
214+
self.release_page_cache = req.read().decode("utf8")
215+
216+
data = json.loads(self.release_page_cache)
214217

215-
self._releases = [PythonRelease.from_dict(release) for release in data]
218+
self._releases = [
219+
PythonRelease.from_dict(release) for release in data
220+
if PYTHON_RELEASE_RE.fullmatch(release["name"])
221+
]
216222
self._releases.sort(key=lambda ver: ver.version_tuple, reverse=True)
217223

218224
return self._releases
@@ -221,11 +227,11 @@ def releases(self) -> list[PythonRelease]:
221227
def release_files(self) -> list[PythonReleaseFile]:
222228
"""Get all release files from python.org/api/v2/downloads/release"""
223229
if self._release_files is None:
224-
if self.release_file_page_cache:
225-
data = json.loads(self.release_file_page_cache)
226-
else: # pragma: nocover
230+
if not self.release_file_page_cache:
227231
with urlopen(self.release_file_page) as req:
228-
data = json.load(req)
232+
self.release_file_page_cache = req.read().decode("utf8")
233+
234+
data = json.loads(self.release_file_page_cache)
229235

230236
self._release_files = [PythonReleaseFile.from_dict(relfile) for relfile in data]
231237
return self._release_files

tests/sources/release.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

tests/sources/release_file.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

tests/test_orgsearch.py

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
# ducktools-pythonfinder
22
# MIT License
3-
#
3+
#
44
# Copyright (c) 2023-2025 David C Ellis
5-
#
5+
#
66
# Permission is hereby granted, free of charge, to any person obtaining a copy
77
# of this software and associated documentation files (the "Software"), to deal
88
# in the Software without restriction, including without limitation the rights
99
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1010
# copies of the Software, and to permit persons to whom the Software is
1111
# furnished to do so, subject to the following conditions:
12-
#
12+
#
1313
# The above copyright notice and this permission notice shall be included in all
1414
# copies or substantial portions of the Software.
15-
#
15+
#
1616
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1717
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1818
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -78,7 +78,7 @@ def test_latest(self, searcher):
7878
s = searcher(self.system, self.machine)
7979

8080
latest_312 = s.latest_binary_match(SpecifierSet("~=3.12.0"))
81-
assert latest_312.version == "3.12.5"
81+
assert latest_312.version == "3.12.10"
8282

8383
before_312 = s.latest_binary_match(SpecifierSet("<3.12"))
8484
assert before_312.version == "3.11.9"
@@ -88,12 +88,13 @@ def test_latest_bins(self, searcher):
8888

8989
latest_310 = s.latest_minor_binaries(SpecifierSet(">=3.10.0"))
9090

91-
assert len(latest_310) == 3
91+
assert len(latest_310) == 4
9292

9393
# Check releases in reverse order
94-
assert latest_310[0].version == "3.12.5"
95-
assert latest_310[1].version == "3.11.9"
96-
assert latest_310[2].version == "3.10.11"
94+
assert latest_310[0].version == "3.13.3"
95+
assert latest_310[1].version == "3.12.10"
96+
assert latest_310[2].version == "3.11.9"
97+
assert latest_310[3].version == "3.10.11"
9798

9899
def test_all_matching_binaries(self, searcher):
99100
s = searcher(self.system, self.machine)
@@ -102,7 +103,7 @@ def test_all_matching_binaries(self, searcher):
102103

103104
# releases of each minor version since 3.8
104105
# v3.9.3 was yanked for incompatibilities
105-
assert len(all_bins) == 52
106+
assert len(all_bins) == 61
106107

107108
def test_all_bin_extensions(self, searcher):
108109
# Check all extensions provided match one of the supported set
@@ -118,14 +119,14 @@ def test_all_bin_extensions(self, searcher):
118119
def test_prerelease(self, searcher):
119120
s = searcher(self.system, self.machine)
120121
match = s.latest_binary_match(SpecifierSet(">=3.12"), prereleases=True)
121-
assert match.version == "3.13.0rc1"
122+
assert match.version == "3.14.0b2"
122123
assert match.is_prerelease is True
123124

124125
def test_latest_download(self, searcher):
125126
s = searcher(self.system, self.machine)
126127
download = s.latest_python_download()
127-
assert download.version == "3.12.5"
128-
assert download.url.endswith("3.12.5-amd64.exe")
128+
assert download.version == "3.13.3"
129+
assert download.url.endswith("3.13.3-amd64.exe")
129130

130131

131132
class TestMacOS:
@@ -136,7 +137,7 @@ def test_latest(self, searcher):
136137
s = searcher(self.system, self.machine)
137138

138139
latest_312 = s.latest_binary_match(SpecifierSet("~=3.12.0"))
139-
assert latest_312.version == "3.12.5"
140+
assert latest_312.version == "3.12.10"
140141

141142
before_312 = s.latest_binary_match(SpecifierSet("<3.12"))
142143
assert before_312.version == "3.11.9"
@@ -146,12 +147,13 @@ def test_latest_bins(self, searcher):
146147

147148
latest_310 = s.latest_minor_binaries(SpecifierSet(">=3.10.0"))
148149

149-
assert len(latest_310) == 3
150+
assert len(latest_310) == 4
150151

151152
# Check releases in reverse order
152-
assert latest_310[0].version == "3.12.5"
153-
assert latest_310[1].version == "3.11.9"
154-
assert latest_310[2].version == "3.10.11"
153+
assert latest_310[0].version == "3.13.3"
154+
assert latest_310[1].version == "3.12.10"
155+
assert latest_310[2].version == "3.11.9"
156+
assert latest_310[3].version == "3.10.11"
155157

156158
def test_all_matching_binaries(self, searcher):
157159
s = searcher(self.system, self.machine)
@@ -161,7 +163,7 @@ def test_all_matching_binaries(self, searcher):
161163
# releases of each minor version since 3.8
162164
# v3.9.3 was yanked for incompatibilities
163165
# MacOS has duplicates for Intel only binaries
164-
assert len(all_bins) == 64
166+
assert len(all_bins) == 73
165167

166168
def test_all_bin_extensions(self, searcher):
167169
# Check all extensions provided match one of the supported set
@@ -177,8 +179,8 @@ def test_all_bin_extensions(self, searcher):
177179
def test_latest_download(self, searcher):
178180
s = searcher(self.system, self.machine)
179181
download = s.latest_python_download()
180-
assert download.version == "3.12.5"
181-
assert download.url.endswith("3.12.5-macos11.pkg")
182+
assert download.version == "3.13.3"
183+
assert download.url.endswith("3.13.3-macos11.pkg")
182184

183185

184186
class TestLinux:
@@ -189,22 +191,23 @@ def test_latest(self, searcher):
189191
s = searcher(self.system, self.machine)
190192

191193
latest_312 = s.latest_binary_match(SpecifierSet("~=3.12.0"))
192-
assert latest_312.version == "3.12.5"
194+
assert latest_312.version == "3.12.10"
193195

194196
before_312 = s.latest_binary_match(SpecifierSet("<3.12"))
195-
assert before_312.version == "3.11.9"
197+
assert before_312.version == "3.11.12" # Linux gets extra releases
196198

197199
def test_latest_bins(self, searcher):
198200
s = searcher(self.system, self.machine)
199201

200202
latest_310 = s.latest_minor_binaries(SpecifierSet(">=3.10.0"))
201203

202-
assert len(latest_310) == 3
204+
assert len(latest_310) == 4
203205

204206
# Check releases in reverse order
205-
assert latest_310[0].version == "3.12.5"
206-
assert latest_310[1].version == "3.11.9"
207-
assert latest_310[2].version == "3.10.14" # source only release
207+
assert latest_310[0].version == "3.13.3"
208+
assert latest_310[1].version == "3.12.10"
209+
assert latest_310[2].version == "3.11.12" # source only release
210+
assert latest_310[3].version == "3.10.17" # source only release
208211

209212
def test_all_matching_binaries(self, searcher):
210213
s = searcher(self.system, self.machine)
@@ -214,7 +217,7 @@ def test_all_matching_binaries(self, searcher):
214217
# releases of each minor version since 3.8
215218
# v3.9.3 was yanked for incompatibilities
216219
# Linux includes source releases and has multiple formats for them
217-
assert len(all_bins) == 140
220+
assert len(all_bins) == 178
218221

219222
def test_all_bin_extensions(self, searcher):
220223
# Check all extensions provided match one of the supported set
@@ -230,5 +233,5 @@ def test_all_bin_extensions(self, searcher):
230233
def test_latest_download(self, searcher):
231234
s = searcher(self.system, self.machine)
232235
download = s.latest_python_download()
233-
assert download.version == "3.12.5"
234-
assert download.url.endswith("3.12.5.tgz")
236+
assert download.version == "3.13.3"
237+
assert download.url.endswith("3.13.3.tgz")

0 commit comments

Comments
 (0)