From 93647ae5192c81b1acdef5017c1f187e257c6510 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Wed, 18 Sep 2024 16:28:16 -0400 Subject: [PATCH] feat: setting a non-dynamic field is an error (#161) Signed-off-by: Henry Schreiner --- pyproject_metadata/__init__.py | 14 +++++++++++++- tests/test_standard_metadata.py | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/pyproject_metadata/__init__.py b/pyproject_metadata/__init__.py index cb84cc4..8e8ab52 100644 --- a/pyproject_metadata/__init__.py +++ b/pyproject_metadata/__init__.py @@ -529,10 +529,20 @@ class StandardMetadata: """ metadata_version: str | None = None + _locked_metadata: bool = False def __post_init__(self) -> None: self.validate() + def __setattr__(self, name: str, value: Any) -> None: + if self._locked_metadata and name.replace('_', '-') not in set(self.dynamic) | { + 'metadata-version', + 'dynamic-metadata', + }: + msg = f'Field "{name}" is not dynamic' + raise AttributeError(msg) + super().__setattr__(name, value) + def validate(self, *, warn: bool = True) -> None: if self.auto_metadata_version not in KNOWN_METADATA_VERSIONS: msg = f'The metadata_version must be one of {KNOWN_METADATA_VERSIONS} or None (default)' @@ -661,7 +671,7 @@ def from_pyproject( else None ) - return cls( + self = cls( name=name, version=version, description=description, @@ -683,6 +693,8 @@ def from_pyproject( dynamic_metadata=dynamic_metadata or [], metadata_version=metadata_version, ) + self._locked_metadata = True + return self def as_rfc822(self) -> RFC822Message: message = RFC822Message() diff --git a/tests/test_standard_metadata.py b/tests/test_standard_metadata.py index bc4043d..3b3497f 100644 --- a/tests/test_standard_metadata.py +++ b/tests/test_standard_metadata.py @@ -1066,6 +1066,25 @@ def test_version_dynamic() -> None: metadata.version = packaging.version.Version('1.2.3') +def test_modify_dynamic() -> None: + metadata = pyproject_metadata.StandardMetadata.from_pyproject( + { + 'project': { + 'name': 'example', + 'version': '1.2.3', + 'dynamic': [ + 'requires-python', + ], + }, + } + ) + metadata.requires_python = packaging.specifiers.SpecifierSet('>=3.12') + with pytest.raises( + AttributeError, match=re.escape('Field "version" is not dynamic') + ): + metadata.version = packaging.version.Version('1.2.3') + + def test_missing_keys_warns() -> None: with pytest.warns( pyproject_metadata.ConfigurationWarning,