From f84203d1598117a3ae91bbe8a5dd7a9111a7ffa0 Mon Sep 17 00:00:00 2001 From: Dustin Ingram Date: Tue, 14 Jan 2025 15:21:30 +0000 Subject: [PATCH 1/3] Update DynamicFieldsEnum to include all deprecated fields --- ...pdate_dynamicfieldsenum_to_include_all_.py | 109 ++++++++++++++++++ warehouse/packaging/models.py | 10 +- 2 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 warehouse/migrations/versions/24aa37164e72_update_dynamicfieldsenum_to_include_all_.py diff --git a/warehouse/migrations/versions/24aa37164e72_update_dynamicfieldsenum_to_include_all_.py b/warehouse/migrations/versions/24aa37164e72_update_dynamicfieldsenum_to_include_all_.py new file mode 100644 index 000000000000..6584ec6b8754 --- /dev/null +++ b/warehouse/migrations/versions/24aa37164e72_update_dynamicfieldsenum_to_include_all_.py @@ -0,0 +1,109 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Update DynamicFieldsEnum to include all deprecated fields + +Revision ID: 24aa37164e72 +Revises: ed4cc2ef6b0f +Create Date: 2025-01-14 15:19:26.813044 +""" + +from alembic import op +from alembic_postgresql_enum import ColumnType, TableReference + +revision = "24aa37164e72" +down_revision = "ed4cc2ef6b0f" + + +def upgrade(): + op.sync_enum_values( + enum_schema="public", + enum_name="release_dynamic_fields", + new_values=[ + "Platform", + "Supported-Platform", + "Summary", + "Description", + "Description-Content-Type", + "Keywords", + "Author", + "Author-Email", + "Maintainer", + "Maintainer-Email", + "License", + "License-Expression", + "License-File", + "Classifier", + "Requires-Dist", + "Requires-Python", + "Requires-External", + "Project-Url", + "Provides-Extra", + "Provides-Dist", + "Obsoletes-Dist", + "Home-Page", + "Download-Url", + "Requires", + "Provides", + "Obsoletes", + ], + affected_columns=[ + TableReference( + table_schema="public", + table_name="releases", + column_name="dynamic", + column_type=ColumnType.ARRAY, + ) + ], + enum_values_to_rename=[], + ) + + +def downgrade(): + op.sync_enum_values( + enum_schema="public", + enum_name="release_dynamic_fields", + new_values=[ + "Platform", + "Supported-Platform", + "Summary", + "Description", + "Description-Content-Type", + "Keywords", + "Home-Page", + "Download-Url", + "Author", + "Author-Email", + "Maintainer", + "Maintainer-Email", + "License", + "License-Expression", + "License-File", + "Classifier", + "Requires-Dist", + "Requires-Python", + "Requires-External", + "Project-Url", + "Provides-Extra", + "Provides-Dist", + "Obsoletes-Dist", + ], + affected_columns=[ + TableReference( + table_schema="public", + table_name="releases", + column_name="dynamic", + column_type=ColumnType.ARRAY, + ) + ], + enum_values_to_rename=[], + ) diff --git a/warehouse/packaging/models.py b/warehouse/packaging/models.py index b8b8e19e2862..c02cbf6f70f6 100644 --- a/warehouse/packaging/models.py +++ b/warehouse/packaging/models.py @@ -547,8 +547,6 @@ class ReleaseURL(db.Model): "Description", "Description-Content-Type", "Keywords", - "Home-Page", - "Download-Url", "Author", "Author-Email", "Maintainer", @@ -564,6 +562,14 @@ class ReleaseURL(db.Model): "Provides-Extra", "Provides-Dist", "Obsoletes-Dist", + # Although the following are deprecated fields, they are technically + # permitted as dynamic by PEP 643 + # https://github.com/pypa/setuptools/issues/4797#issuecomment-2589514950 + "Home-Page", + "Download-Url", + "Requires", + "Provides", + "Obsoletes", name="release_dynamic_fields", ) From f5e205f8be30802cd2a72142e47cdcc6156092a1 Mon Sep 17 00:00:00 2001 From: Dustin Ingram Date: Tue, 14 Jan 2025 17:07:52 +0000 Subject: [PATCH 2/3] Update test to check actually invalid Dynamic value --- tests/unit/forklift/test_metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/forklift/test_metadata.py b/tests/unit/forklift/test_metadata.py index 6b0da8ba3840..c32adccb79cb 100644 --- a/tests/unit/forklift/test_metadata.py +++ b/tests/unit/forklift/test_metadata.py @@ -283,7 +283,7 @@ def test_valid_dynamic(self): def test_invalid_dynamic(self): data = MultiDict(metadata_version="2.2", name="spam", version="2.0") - data.add("dynamic", "Requires") + data.add("dynamic", "Invalid") with pytest.raises(ExceptionGroup) as excinfo: metadata.parse(None, form_data=data) _assert_invalid_metadata(excinfo.value, "dynamic") From fd332e550954d2a87534e7561d9dbd3bc601a79e Mon Sep 17 00:00:00 2001 From: Dustin Ingram Date: Tue, 14 Jan 2025 17:40:03 +0000 Subject: [PATCH 3/3] Add a test for edge case where our enum is not updated --- tests/unit/forklift/test_metadata.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/unit/forklift/test_metadata.py b/tests/unit/forklift/test_metadata.py index c32adccb79cb..9ef0a1bb08cb 100644 --- a/tests/unit/forklift/test_metadata.py +++ b/tests/unit/forklift/test_metadata.py @@ -14,6 +14,7 @@ import pytest from packaging.version import Version +from sqlalchemy.dialects.postgresql import ENUM from webob.multidict import MultiDict from warehouse.forklift import metadata @@ -288,6 +289,19 @@ def test_invalid_dynamic(self): metadata.parse(None, form_data=data) _assert_invalid_metadata(excinfo.value, "dynamic") + def test_valid_dynamic_but_missing_from_our_enum(self, monkeypatch): + """ + Handles the case where there are new metadata fields that pypa/packaging + considers to be valid, but don't exist in our enum and would otherwise fail + when inserting them into the database + """ + monkeypatch.setattr(metadata, "DynamicFieldsEnum", ENUM()) + data = MultiDict(metadata_version="2.2", name="spam", version="2.0") + data.add("dynamic", "author") + with pytest.raises(ExceptionGroup) as excinfo: + metadata.parse(None, form_data=data) + _assert_invalid_metadata(excinfo.value, "dynamic") + class TestFromFormData: def test_valid(self):