Skip to content

Commit a675cc0

Browse files
Fix manylinux wheel matching for PEP 425 sorted platform tags (#1401)
The manylinux wheel regex pattern failed to match wheels using the properly sorted compressed platform tag format specified by PEP 425. PEP 425 requires compressed platform tags to be sorted alphabetically, which places legacy tags (manylinux2014) before PEP 600 tags (manylinux_2_17) since '2' < '_' in ASCII. Some packages like psycopg-binary have adopted this format in recent releases, causing get_cached_manylinux_wheel() to return None for valid wheels. Update build_manylinux_wheel_file_match_pattern() to support: - Sorted format: manylinux2014_x86_64.manylinux_2_17_x86_64 (PEP 425) - Unsorted format: manylinux_2_17_x86_64.manylinux2014_x86_64 (legacy) - Legacy only: manylinux2014_x86_64 Add test_get_manylinux_python314() and test_manylinux_wheel_platform_tag_sort_order() to verify the fix. Github-Issue:#1400 Co-authored-by: shane <[email protected]>
1 parent fd67ff1 commit a675cc0

File tree

2 files changed

+60
-2
lines changed

2 files changed

+60
-2
lines changed

tests/test_core.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,56 @@ def test_get_manylinux_python313(self):
235235
self.assertTrue(os.path.isfile(path))
236236
os.remove(path)
237237

238+
def test_get_manylinux_python314(self):
239+
z = Zappa(runtime="python3.14")
240+
self.assertIsNotNone(z.get_cached_manylinux_wheel("psycopg-binary", "3.3.1"))
241+
self.assertIsNone(z.get_cached_manylinux_wheel("derp_no_such_thing", "0.0"))
242+
243+
# mock with a known manylinux wheel package so that code for downloading them gets invoked
244+
mock_installed_packages = {"psycopg-binary": "3.3.1"}
245+
with mock.patch(
246+
"zappa.core.Zappa.get_installed_packages",
247+
return_value=mock_installed_packages,
248+
):
249+
z = Zappa(runtime="python3.14")
250+
path = z.create_lambda_zip(handler_file=os.path.realpath(__file__))
251+
self.assertTrue(os.path.isfile(path))
252+
os.remove(path)
253+
254+
# same, but with an ABI3 package
255+
mock_installed_packages = {"cryptography": "46.0.3"}
256+
with mock.patch(
257+
"zappa.core.Zappa.get_installed_packages",
258+
return_value=mock_installed_packages,
259+
):
260+
z = Zappa(runtime="python3.14")
261+
path = z.create_lambda_zip(handler_file=os.path.realpath(__file__))
262+
self.assertTrue(os.path.isfile(path))
263+
os.remove(path)
264+
265+
def test_manylinux_wheel_platform_tag_sort_order(self):
266+
"""Test that wheel filename matching supports both sorted and unsorted platform tag orders.
267+
268+
Per PEP 425, compressed platform tags should be dot-separated and sorted.
269+
Alphabetically sorted order puts legacy tags (manylinux2014) before PEP 600
270+
tags (manylinux_2_17). We support both orderings for compatibility.
271+
"""
272+
from zappa.core import build_manylinux_wheel_file_match_pattern
273+
274+
pattern = build_manylinux_wheel_file_match_pattern("python3.13", "x86_64")
275+
276+
# Sorted format (legacy first) - per PEP 425
277+
sorted_filename = "psycopg_binary-3.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl"
278+
self.assertIsNotNone(pattern.match(sorted_filename))
279+
280+
# Unsorted format (PEP 600 first) - for backwards compatibility
281+
unsorted_filename = "psycopg_binary-3.2.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
282+
self.assertIsNotNone(pattern.match(unsorted_filename))
283+
284+
# Legacy only format
285+
legacy_only_filename = "somepackage-1.0.0-cp313-cp313-manylinux2014_x86_64.whl"
286+
self.assertIsNotNone(pattern.match(legacy_only_filename))
287+
238288
def test_verify_downloaded_manylinux_wheel(self):
239289
z = Zappa(runtime="python3.10")
240290
cached_wheels_dir = Path(tempfile.gettempdir()) / "cached_wheels"

zappa/core.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,17 @@ def build_manylinux_wheel_file_match_pattern(runtime: str, architecture: str) ->
170170
else:
171171
raise ValueError(f"Invalid 'architecture', must be one of {VALID_ARCHITECTURES}, got: {architecture}")
172172

173+
# Support compressed platform tags per PEP 425 (dot-separated, sorted).
174+
# Properly sorted order puts legacy tags (manylinux2014) before PEP 600 tags (manylinux_2_17)
175+
# alphabetically. We also support the reverse order for backwards compatibility.
176+
# Examples:
177+
# - manylinux2014_x86_64.whl (legacy only)
178+
# - manylinux2014_x86_64.manylinux_2_17_x86_64.whl (sorted: legacy first)
179+
# - manylinux_2_17_x86_64.manylinux2014_x86_64.whl (unsorted: PEP 600 first)
180+
legacy_tag = rf'({"|".join(manylinux_legacy_tags)})_({"|".join(valid_platform_tags)})'
181+
pep600_tag = rf'manylinux_\d+_\d+_({"|".join(valid_platform_tags)})'
173182
manylinux_wheel_file_match = (
174-
rf'^.*{python_tag}-(manylinux_\d+_\d+_({"|".join(valid_platform_tags)})[.])?'
175-
rf'({"|".join(manylinux_legacy_tags)})_({"|".join(valid_platform_tags)})[.]whl$'
183+
rf"^.*{python_tag}-{python_tag}-" rf"({legacy_tag}[.]{pep600_tag}|{pep600_tag}[.]{legacy_tag}|{legacy_tag})[.]whl$"
176184
)
177185

178186
# The 'abi3' tag is a compiled distribution format designed for compatibility across multiple Python 3 versions.

0 commit comments

Comments
 (0)