From 4b03f867a3deaecb07240f471fed9bf9c9072629 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Thu, 28 Sep 2023 14:31:42 +0200 Subject: [PATCH 1/2] Fix #426: call subclass when deriving from Version When using the replace method, an instance of the Version class is created and not the respective subclass. Co-authored-by: Danny Staple --- changelog.d/426.bugfix.rst | 2 ++ src/semver/version.py | 6 +++--- tests/test_subclass.py | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 changelog.d/426.bugfix.rst diff --git a/changelog.d/426.bugfix.rst b/changelog.d/426.bugfix.rst new file mode 100644 index 00000000..2c2d0eac --- /dev/null +++ b/changelog.d/426.bugfix.rst @@ -0,0 +1,2 @@ +Fix :meth:`~semver.version.Version.replace` method to use the derived class +of an instance instead of :class:`~semver.version.Version` class. diff --git a/src/semver/version.py b/src/semver/version.py index 9a7a4ffc..29309ab4 100644 --- a/src/semver/version.py +++ b/src/semver/version.py @@ -655,8 +655,8 @@ def parse( def replace(self, **parts: Union[int, Optional[str]]) -> "Version": """ - Replace one or more parts of a version and return a new - :class:`Version` object, but leave self untouched + Replace one or more parts of a version and return a new :class:`Version` + object, but leave self untouched. .. versionadded:: 2.9.0 Added :func:`Version.replace` @@ -670,7 +670,7 @@ def replace(self, **parts: Union[int, Optional[str]]) -> "Version": version = self.to_dict() version.update(parts) try: - return Version(**version) # type: ignore + return type(self)(**version) # type: ignore except TypeError: unknownkeys = set(parts) - set(self.to_dict()) error = "replace() got %d unexpected keyword argument(s): %s" % ( diff --git a/tests/test_subclass.py b/tests/test_subclass.py index cbf9d271..b33f4969 100644 --- a/tests/test_subclass.py +++ b/tests/test_subclass.py @@ -17,3 +17,37 @@ def __str__(self): v = SemVerWithVPrefix.parse("v1.2.3") assert str(v) == "v1.2.3" + + +def test_replace_from_subclass(): + # Issue#426 + # Taken from the example "Creating Subclasses from Version" + class SemVerWithVPrefix(Version): + """ + A subclass of Version which allows a "v" prefix + """ + + @classmethod + def parse(cls, version: str) -> "SemVerWithVPrefix": + """ + Parse version string to a Version instance. + + :param version: version string with "v" or "V" prefix + :raises ValueError: when version does not start with "v" or "V" + :return: a new instance + """ + if not version[0] in ("v", "V"): + raise ValueError( + f"{version!r}: not a valid semantic version tag. " + "Must start with 'v' or 'V'" + ) + return super().parse(version[1:], optional_minor_and_patch=True) + + def __str__(self) -> str: + # Reconstruct the tag + return "v" + super().__str__() + + version = SemVerWithVPrefix.parse("v1.1.0") + dev_version = version.replace(prerelease="dev.0") + + assert str(dev_version) == "v1.1.0-dev.0" From 2dda51c0b3d0956187533210927533af50bf6175 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Fri, 29 Sep 2023 14:34:57 +0200 Subject: [PATCH 2/2] GHA: Require setuptool>60 and setuptools-scm>60 --- .github/workflows/python-testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-testing.yml b/.github/workflows/python-testing.yml index f6decc03..adb06d4a 100644 --- a/.github/workflows/python-testing.yml +++ b/.github/workflows/python-testing.yml @@ -60,7 +60,7 @@ jobs: cache: 'pip' - name: Install dependencies run: | - python3 -m pip install --upgrade pip setuptools setuptools-scm + python3 -m pip install --upgrade pip setuptools>60 setuptools-scm>=60 pip install tox tox-gh-actions - name: Check run: |