From c31a147e1fb8b9c31c6290514674d927c62e6a88 Mon Sep 17 00:00:00 2001 From: Mondo Diaz Date: Wed, 4 Feb 2026 17:02:02 -0600 Subject: [PATCH] fix: treat bare version constraints as exact match When resolving dependencies like certifi@2025.10.5, the bare version string "2025.10.5" was being rejected as an invalid SpecifierSet and falling back to wildcard, which fetched the latest version instead. Now bare versions starting with a digit are automatically prefixed with "==" to create an exact match constraint. --- backend/app/registry_client.py | 12 +++++++- backend/tests/unit/test_registry_client.py | 36 ++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/backend/app/registry_client.py b/backend/app/registry_client.py index df66139..f6b07d5 100644 --- a/backend/app/registry_client.py +++ b/backend/app/registry_client.py @@ -269,8 +269,18 @@ class PyPIRegistryClient(RegistryClient): return None # Parse constraint + # If constraint looks like a bare version (no operator), treat as exact match + # e.g., "2025.10.5" -> "==2025.10.5" + effective_constraint = constraint + if constraint and constraint[0].isdigit(): + effective_constraint = f"=={constraint}" + logger.debug( + f"Bare version '{constraint}' for {normalized}, " + f"treating as exact match '{effective_constraint}'" + ) + try: - specifier = SpecifierSet(constraint) + specifier = SpecifierSet(effective_constraint) except InvalidSpecifier: # Invalid constraint - treat as wildcard logger.warning( diff --git a/backend/tests/unit/test_registry_client.py b/backend/tests/unit/test_registry_client.py index 1938d08..045effc 100644 --- a/backend/tests/unit/test_registry_client.py +++ b/backend/tests/unit/test_registry_client.py @@ -176,6 +176,42 @@ class TestPyPIRegistryClient: assert result is None + @pytest.mark.asyncio + async def test_resolve_constraint_bare_version(self, client, mock_http_client): + """Test resolving bare version string as exact match.""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "info": {"version": "2.0.0"}, + "releases": { + "1.0.0": [ + { + "packagetype": "bdist_wheel", + "url": "https://files.pythonhosted.org/test-1.0.0.whl", + "filename": "test-1.0.0.whl", + "digests": {"sha256": "abc123"}, + "size": 1000, + } + ], + "2.0.0": [ + { + "packagetype": "bdist_wheel", + "url": "https://files.pythonhosted.org/test-2.0.0.whl", + "filename": "test-2.0.0.whl", + "digests": {"sha256": "def456"}, + "size": 2000, + } + ], + }, + } + mock_http_client.get.return_value = mock_response + + # Bare version "1.0.0" should resolve to exactly 1.0.0, not latest + result = await client.resolve_constraint("test-package", "1.0.0") + + assert result is not None + assert result.version == "1.0.0" + class TestVersionInfo: """Tests for VersionInfo dataclass."""