From 2d383c2be475efde8245ef789144f946f5b8160f Mon Sep 17 00:00:00 2001 From: Brian Cavagnolo Date: Tue, 23 Aug 2022 17:10:21 -0700 Subject: [PATCH 1/2] add setter for version on ProjectPackage This enables support for dynamically setting the package version (e.g., from a plugin) Resolves: python-poetry/poetry#5493 --- src/poetry/core/packages/package.py | 20 +++++++++++------- src/poetry/core/packages/project_package.py | 15 ++++++++++++++ tests/packages/test_package.py | 23 +++++++++++++++++++++ 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/src/poetry/core/packages/package.py b/src/poetry/core/packages/package.py index f4d7d26b8..aa04880d7 100644 --- a/src/poetry/core/packages/package.py +++ b/src/poetry/core/packages/package.py @@ -66,7 +66,6 @@ def __init__( """ Creates a new in memory package. """ - from poetry.core.semver.version import Version from poetry.core.version.markers import AnyMarker super().__init__( @@ -79,12 +78,7 @@ def __init__( features=features, ) - if not isinstance(version, Version): - self._version = Version.parse(version) - self._pretty_version = pretty_version or version - else: - self._version = version - self._pretty_version = pretty_version or self._version.text + self._set_version(version, pretty_version) self.description = "" @@ -216,6 +210,18 @@ def all_requires( for dependency in group.dependencies ] + def _set_version( + self, version: str | Version, pretty_version: str | None = None + ) -> None: + from poetry.core.semver.version import Version + + if not isinstance(version, Version): + self._version = Version.parse(version) + self._pretty_version = pretty_version or version + else: + self._version = version + self._pretty_version = pretty_version or self._version.text + def _get_author(self) -> dict[str, str | None]: if not self._authors: return {"name": None, "email": None} diff --git a/src/poetry/core/packages/project_package.py b/src/poetry/core/packages/project_package.py index 99a6e4e5e..1d6f5505f 100644 --- a/src/poetry/core/packages/project_package.py +++ b/src/poetry/core/packages/project_package.py @@ -63,6 +63,15 @@ def python_versions(self, value: str) -> None: create_nested_marker("python_version", self._python_constraint) ) + @property + def version(self) -> Version: + # override version to make it settable + return super().version + + @version.setter + def version(self, value: str | Version) -> None: + self._set_version(value) + @property def urls(self) -> dict[str, str]: urls = super().urls @@ -71,5 +80,11 @@ def urls(self) -> dict[str, str]: return urls + def __hash__(self) -> int: + # The parent Package class's __hash__ incorporates the version because + # a Package's version is immutable. But a ProjectPackage's version is + # mutable. So call Package's parent hash function. + return super(Package, self).__hash__() + def build_should_generate_setup(self) -> bool: return self.build_config.get("generate-setup-file", True) diff --git a/tests/packages/test_package.py b/tests/packages/test_package.py index b8062f0d5..191beacb1 100644 --- a/tests/packages/test_package.py +++ b/tests/packages/test_package.py @@ -13,8 +13,10 @@ from poetry.core.packages.directory_dependency import DirectoryDependency from poetry.core.packages.file_dependency import FileDependency from poetry.core.packages.package import Package +from poetry.core.packages.project_package import ProjectPackage from poetry.core.packages.url_dependency import URLDependency from poetry.core.packages.vcs_dependency import VCSDependency +from poetry.core.semver.version import Version @pytest.fixture() @@ -540,3 +542,24 @@ def test_python_versions_are_normalized() -> None: == 'python_version > "3.6" and python_version <= "3.10"' ) assert str(package.python_constraint) == ">=3.7,<3.11" + + +def test_cannot_update_package_version() -> None: + package = Package("foo", "1.2.3") + with pytest.raises(AttributeError): + package.version = "1.2.4" # type: ignore[misc,assignment] + + +def test_project_package_version_update_string() -> None: + package = ProjectPackage("foo", "1.2.3") + # TODO: I could use some help deciding what to do about mypy here. The + # setter accepts both str and Version, even though the property is always a + # Version. + package.version = "1.2.4" # type: ignore[assignment] + assert package.version.text == "1.2.4" + + +def test_project_package_version_update_version() -> None: + package = ProjectPackage("foo", "1.2.3") + package.version = Version.parse("1.2.4") + assert package.version.text == "1.2.4" From c41a0a419b9df0a026085d4b641607d5204da8b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Fri, 26 Aug 2022 14:34:49 +0200 Subject: [PATCH 2/2] test(package): Hash of ProjectPackage must not change when changing version --- tests/packages/test_package.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/tests/packages/test_package.py b/tests/packages/test_package.py index 191beacb1..ffaec8316 100644 --- a/tests/packages/test_package.py +++ b/tests/packages/test_package.py @@ -552,9 +552,6 @@ def test_cannot_update_package_version() -> None: def test_project_package_version_update_string() -> None: package = ProjectPackage("foo", "1.2.3") - # TODO: I could use some help deciding what to do about mypy here. The - # setter accepts both str and Version, even though the property is always a - # Version. package.version = "1.2.4" # type: ignore[assignment] assert package.version.text == "1.2.4" @@ -563,3 +560,17 @@ def test_project_package_version_update_version() -> None: package = ProjectPackage("foo", "1.2.3") package.version = Version.parse("1.2.4") assert package.version.text == "1.2.4" + + +def test_project_package_hash_not_changed_when_version_is_changed() -> None: + package = ProjectPackage("foo", "1.2.3") + package_hash = hash(package) + package_clone = package.clone() + assert package == package_clone + assert hash(package) == hash(package_clone) + + package.version = Version.parse("1.2.4") + + assert hash(package) == package_hash, "Hash must not change!" + assert hash(package_clone) == package_hash + assert package != package_clone