Skip to content

Commit cb03698

Browse files
Fix checking if a package is on PyPI. (#487)
We were for example checking this url: https://pypi.org/simple/collective.recipe.backup This worked for years. But now it should be: https://pypi.org/simple/collective-recipe-backup/ Fixes issue #486.
1 parent 6f9a6c8 commit cb03698

4 files changed

Lines changed: 35 additions & 45 deletions

File tree

CHANGES.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ Changelog for zest.releaser
44
9.9.1 (unreleased)
55
------------------
66

7-
- Nothing changed yet.
7+
- Fix checking if a package is on PyPI.
8+
We were for example checking this url: https://pypi.org/simple/collective.recipe.backup
9+
This worked for years. But now it should be: https://pypi.org/simple/collective-recipe-backup/
10+
[maurits]
811

912

1013
9.9.0 (2026-02-10)

zest/releaser/release.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
from build import ProjectBuilder
44
from build.env import DefaultIsolatedEnv
55
from colorama import Fore
6-
from urllib import request
7-
from urllib.error import HTTPError
6+
from packaging.utils import canonicalize_name
87

98
import logging
109
import os
@@ -51,12 +50,12 @@
5150

5251
def package_in_pypi(package):
5352
"""Check whether the package is registered on pypi"""
54-
url = "https://pypi.org/simple/%s" % package
53+
url = f"https://pypi.org/simple/{canonicalize_name(package)}/"
5554
try:
56-
request.urlopen(url)
57-
return True
58-
except HTTPError as e:
59-
logger.debug("Package not found on pypi: %s", e)
55+
response = requests.head(url)
56+
return response.ok
57+
except requests.HTTPError as e:
58+
logger.debug("Package %s not found on pypi: %s", package, e)
6059
return False
6160

6261

zest/releaser/tests/functional.py

Lines changed: 22 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
"""Set up functional test fixtures"""
22

3-
from io import StringIO
4-
from urllib import request
5-
from urllib.error import HTTPError
3+
from packaging.utils import canonicalize_name
64
from zest.releaser import choose
75
from zest.releaser import utils
86
from zest.releaser.baserelease import NOTHING_CHANGED_YET
97
from zest.releaser.utils import execute_command
108
from zest.releaser.utils import filename_from_test_dir
119

1210
import os
11+
import requests
1312
import shutil
1413
import sys
1514
import tarfile
@@ -35,38 +34,27 @@ def _exit(code=None):
3534

3635
sys.exit = _exit
3736

38-
# Monkey patch urllib for pypi access mocking.
39-
test.orig_urlopen = request.urlopen
37+
# Monkey patch the requests library for PyPI access mocking.
38+
test.orig_requests_head = requests.head
4039
test.mock_pypi_available = []
4140

42-
def _make_mock_urlopen(mock_pypi_available):
43-
def _mock_urlopen(url):
44-
# print "Mock opening", url
45-
package = url.replace("https://pypi.org/simple/", "")
46-
if package not in mock_pypi_available:
47-
# On Python 3.14 we get a ResourceWarning because the HTTPError
48-
# is a tempfile and we should clean it up, instead of relying
49-
# on Python to do it for us. We can fix that with:
50-
# with HTTPError(...) as not_found_error:
51-
# raise not_found_error
52-
# But that gives an error on Python 3.9:
53-
# AttributeError: 'NoneType' object has no attribute 'closed'
54-
# So we can only do that once we drop 3.9 support.
55-
raise HTTPError(
56-
url,
57-
404,
58-
"HTTP Error 404: Not Found (%s does not have any releases)"
59-
% package,
60-
None,
61-
None,
62-
)
63-
else:
64-
answer = " ".join(mock_pypi_available)
65-
return StringIO(answer)
66-
67-
return _mock_urlopen
68-
69-
request.urlopen = _make_mock_urlopen(test.mock_pypi_available)
41+
class MockResponse:
42+
def __init__(self, ok):
43+
self.ok = ok
44+
45+
def _make_mock_head(mock_pypi_available):
46+
def _mock_head(url):
47+
package = url.replace("https://pypi.org/simple/", "").replace("/", "")
48+
# package my.example has been canonicalized to my-example,
49+
# so we do the same in our mock list.
50+
canonical_packages = [
51+
canonicalize_name(name) for name in mock_pypi_available
52+
]
53+
return MockResponse(package in canonical_packages)
54+
55+
return _mock_head
56+
57+
requests.head = _make_mock_head(test.mock_pypi_available)
7058

7159
# Extract example project
7260
example_tar = filename_from_test_dir("example.tar")
@@ -130,7 +118,7 @@ def rename_changelog(src: str, dst: str):
130118

131119
def teardown(test):
132120
sys.exit = test.orig_exit
133-
request.urlopen = test.orig_urlopen
121+
requests.head = test.orig_requests_head
134122
os.chdir(test.orig_dir)
135123
sys.argv[1:] = test.orig_argv
136124
shutil.rmtree(test.tempdir)

zest/releaser/tests/release.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ Check availability on pypi
1515
"Testing" means "don't really poll pypi", so the test setup does some
1616
monkeypatching for us:
1717

18-
>>> from urllib import request
19-
>>> request.urlopen
20-
<function ..._mock_urlopen at ...>
18+
>>> import requests
19+
>>> requests.head
20+
<function ..._mock_head at ...>
2121

2222
There's a mock list of packages that our mock pypi provides:
2323

0 commit comments

Comments
 (0)