From bd97cfcd595c882d92b73430d4bf5c880d6062dc Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sun, 17 Nov 2024 11:54:01 +0100 Subject: [PATCH 1/6] Require validation for bug issue template --- .github/ISSUE_TEMPLATE/bug_report.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index a263dcb..6e75e2c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -55,6 +55,8 @@ body: label: Situation description: A clear and concise description of what the bug is. placeholder: Describe the problem you see... + validations: + required: true - type: textarea id: reproduction_steps @@ -66,6 +68,8 @@ body: 2. Scroll down to '....' 3. See error placeholder: Describe the steps to reproduce the issue... + validations: + required: true - type: textarea id: expected_behavior @@ -73,6 +77,8 @@ body: label: Expected behavior description: A clear and concise description of what you expected to happen. placeholder: Describe the expected behavior... + validations: + required: true - type: textarea id: environment From bc41390f388d88114e8529b17fe145c5af390cbb Mon Sep 17 00:00:00 2001 From: Victor Westerhuis Date: Fri, 13 Dec 2024 09:16:03 +0100 Subject: [PATCH 2/6] Fix comparison with subclasses --- src/semver/version.py | 2 +- tests/test_subclass.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/semver/version.py b/src/semver/version.py index 2f1f8cb..0ff5b9f 100644 --- a/src/semver/version.py +++ b/src/semver/version.py @@ -41,7 +41,7 @@ def _comparator(operator: Comparator) -> Comparator: @wraps(operator) def wrapper(self: "Version", other: Comparable) -> bool: comparable_types = ( - Version, + type(self), dict, tuple, list, diff --git a/tests/test_subclass.py b/tests/test_subclass.py index b33f496..0d39000 100644 --- a/tests/test_subclass.py +++ b/tests/test_subclass.py @@ -1,4 +1,5 @@ from semver import Version +import pytest def test_subclass_from_versioninfo(): @@ -51,3 +52,20 @@ def __str__(self) -> str: dev_version = version.replace(prerelease="dev.0") assert str(dev_version) == "v1.1.0-dev.0" + + +def test_compare_with_subclass(): + class SemVerSubclass(Version): + pass + + with pytest.raises(TypeError): + SemVerSubclass.parse("1.0.0").compare(Version.parse("1.0.0")) + assert Version.parse("1.0.0").compare(SemVerSubclass.parse("1.0.0")) == 0 + + assert ( + SemVerSubclass.parse("1.0.0").__eq__(Version.parse("1.0.0")) is NotImplemented + ) + assert Version.parse("1.0.0").__eq__(SemVerSubclass.parse("1.0.0")) is True + + assert SemVerSubclass.parse("1.0.0") == Version.parse("1.0.0") + assert Version.parse("1.0.0") == SemVerSubclass.parse("1.0.0") From cc4ae0755098dfd52e0ee197c2099ef207079fbe Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Fri, 13 Dec 2024 13:51:33 +0100 Subject: [PATCH 3/6] Change version to 3.0.3 * Allow uvx to be executed by tox * Adjust building semver section * Rework release procedure with uv * Add restview to `pyproject.toml` * Mention PEP 639 in `pyproject.toml` * Update Changelog * Extend `.readthedocs.yaml` with jobs --- .readthedocs.yaml | 12 +++- CHANGELOG.rst | 87 ++++++++++++++++++++++- changelog.d/pr435.doc.rst | 7 -- changelog.d/pr436.doc.rst | 3 - changelog.d/pr447.internal.rst | 15 ---- docs/build-semver.rst | 53 +++----------- docs/usage/semver-version.rst | 2 +- pyproject.toml | 6 +- release-procedure.md | 124 +++++++++++++++++++-------------- src/semver/__about__.py | 2 +- tox.ini | 1 + uv.lock | 29 ++++++-- 12 files changed, 210 insertions(+), 131 deletions(-) delete mode 100644 changelog.d/pr435.doc.rst delete mode 100644 changelog.d/pr436.doc.rst delete mode 100644 changelog.d/pr447.internal.rst diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 33e3e3a..626c2a5 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -10,11 +10,17 @@ build: os: ubuntu-22.04 tools: python: "3.11" + jobs: + pre_install: + # - curl -LsSf https://astral.sh/uv/install.sh | sh + - pip install uv + - uv export --only-group docs --no-hashes --no-color > requirements-docs.txt + post_install: + - pip install -r requirements-docs.txt + pre_build: + - make -C docs html # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/conf.py -python: - install: - - requirements: docs/requirements.txt diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2281f16..97a5f89 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,11 +18,96 @@ This section covers the changes between major version 2 and version 3. .. towncrier release notes start +Version 3.0.3 +============= + +:Released: 2025-01-18 +:Maintainer: Tom Schraitle + + +Bug Fixes +--------- + +* :pr:`453`: The check in ``_comparator`` does not match the check in :meth:`Version.compare`. + This breaks comparision with subclasses. + + + +Improved Documentation +---------------------- + +* :pr:`435`: Several small improvements for documentation: + + * Add meta description to improve SEO + * Use canonicals on ReadTheDocs (commit 87f639f) + * Pin versions for reproducable doc builds (commit 03fb990) + * Add missing :file:`.readthedocs.yaml` file (commit ec9348a) + * Correct some smaller issues when building (commit f65feab) + +* :pr:`436`: Move search box more at the top. This makes it easier for + users as if the TOC is long, the search box isn't visible + anymore. + + + +Features +-------- + +* :pr:`439`: Improve type hints to fix TODOs + + + +Internal Changes +---------------- + +* :pr:`440`: Update workflow file + +* :pr:`446`: Add Python 3.13 to GitHub Actions + +* :pr:`447`: Modernize project configs with :file:`pyproject.toml` and + use Astral's uv command. + + * In :file:`pyproject.toml`: + + * Move all project related data from :file:`setup.cfg` to :file:`pyproject.toml` + * Use new dependency group from :pep:`735` + * Consolidate flake8, isort, pycodestyle with ruff + * Split towncrier config type "trivial" into "trivial" and "internal" + + * Create config file for ruff (:file:`.ruff.toml`) + * Create config file for pytest (:file:`.pytest.ini`) + * Simplify :file:`tox.ini` and remove old stuff + * Document installation with new :command:`uv` command + * Simplify Sphinx config with :func:`find_version()` + * Update the authors + * Use :command:`uv` in GitHub Action :file:`python-testing.yml` workflow + +* Update :file:`release-procedure.md`. + +* :pr:`451`: Turn our Markdown issue templates into YAML + + +Trivial Changes +--------------- + +* :pr:`438`: Replace organization placeholder in license + +* :pr:`445`: Improve private :func:`_nat_cmp` method: + + * Remove obsolete else. + * Find a better way to identify digits without the :mod:`re` module. + * Fix docstring in :meth:`Version.compare` + + + +---- + + Version 3.0.2 ============= :Released: 2023-10-09 -:Maintainer: +:Maintainer: Tom Schraitle Bug Fixes diff --git a/changelog.d/pr435.doc.rst b/changelog.d/pr435.doc.rst deleted file mode 100644 index 87925c3..0000000 --- a/changelog.d/pr435.doc.rst +++ /dev/null @@ -1,7 +0,0 @@ -Several small improvements for documentation: - -* Add meta description to improve SEO -* Use canonicals on ReadTheDocs (commit 87f639f) -* Pin versions for reproducable doc builds (commit 03fb990) -* Add missing :file:`.readthedocs.yaml` file (commit ec9348a) -* Correct some smaller issues when building (commit f65feab) diff --git a/changelog.d/pr436.doc.rst b/changelog.d/pr436.doc.rst deleted file mode 100644 index 5f07836..0000000 --- a/changelog.d/pr436.doc.rst +++ /dev/null @@ -1,3 +0,0 @@ -Move search box more at the top. This makes it easier for -users as if the TOC is long, the search box isn't visible -anymore. diff --git a/changelog.d/pr447.internal.rst b/changelog.d/pr447.internal.rst deleted file mode 100644 index 2f1e367..0000000 --- a/changelog.d/pr447.internal.rst +++ /dev/null @@ -1,15 +0,0 @@ -Modernize project configs with :file:`pyproject.toml` - -* In :file:`pyproject.toml`: - * Move all project related data from :file:`setup.cfg` to :file:`pyproject.toml` - * Use new dependency group from :pep:`735` - * Consolidate flake8, isort, pycodestyle with ruff - * Split towncrier config type "trivial" into "trivial" and - "internal" -* Create config file for ruff (:file:`.ruff.toml`) -* Create config file for pytest (:file:`.pytest.ini`) -* Simplify :file:`tox.ini` and remove old stuff -* Document installation with new :command:`uv` command -* Simplify Sphinx config with :func:`find_version()` -* Update the authors -* Use :command:`uv` in GitHub Action :file:`python-testing.yml` workflow diff --git a/docs/build-semver.rst b/docs/build-semver.rst index b5626d2..9c0441d 100644 --- a/docs/build-semver.rst +++ b/docs/build-semver.rst @@ -7,58 +7,27 @@ Building semver :description lang=en: Building semver -.. _PEP 517: https://www.python.org/dev/peps/pep-0517/ -.. _PEP 621: https://www.python.org/dev/peps/pep-0621/ -.. _A Practical Guide to Setuptools and Pyproject.toml: https://godatadriven.com/blog/a-practical-guide-to-setuptools-and-pyproject-toml/ -.. _Declarative config: https://setuptools.rtfd.io/en/latest/userguide/declarative_config.html +.. _Installing uv: https://docs.astral.sh/uv/getting-started/installation/ -This project changed slightly its way how it is built. The reason for this -was to still support the "traditional" way with :command:`setup.py`, -but at the same time try out the newer way with :file:`pyproject.toml`. -As Python 3.6 got deprecated, this project does support from now on only -:file:`pyproject.toml`. +This project changed its way how it is built over time. We used to have +a :file:`setup.py` file, but switched to a :file:`pyproject.toml` setup. +The build process is managed by :command:`uv` command. -Background information ----------------------- +You need: -Skip this section and head over to :ref:`build-pyproject-build` if you just -want to know how to build semver. -This section gives some background information how this project is set up. +* Python 3.7 or newer. -The traditional way with :command:`setup.py` in this project uses a -`Declarative config`_. With this approach, the :command:`setup.py` is -stripped down to its bare minimum and all the metadata is stored in -:file:`setup.cfg`. +* The :mod:`setuptools` module version 61 or newer which is used as + a build backend. -The new :file:`pyproject.toml` contains only information about the build backend, currently setuptools.build_meta. The idea is taken from -`A Practical Guide to Setuptools and Pyproject.toml`_. -Setuptools-specific configuration keys as defined in `PEP 621`_ are currently -not used. +* The command :command:`uv` from Astral. Refer to the section + `Installing uv`_ for more information. -.. _build-pyproject-build: - -Building with pyproject-build ------------------------------ - -To build semver you need: - -* The :mod:`build` module which implements the `PEP 517`_ build - frontend. - Install it with:: - - pip install build - - Some Linux distributions has already packaged it. If you prefer - to use the module with your package manager, search for - :file:`python-build` or :file:`python3-build` and install it. - -* The command :command:`pyproject-build` from the :mod:`build` module. - To build semver, run:: - pyproject-build + uv build After the command is finished, you can find two files in the :file:`dist` folder: a ``.tar.gz`` and a ``.whl`` file. \ No newline at end of file diff --git a/docs/usage/semver-version.rst b/docs/usage/semver-version.rst index 5116529..df04a74 100644 --- a/docs/usage/semver-version.rst +++ b/docs/usage/semver-version.rst @@ -4,4 +4,4 @@ Getting the Version of semver To know the version of semver itself, use the following construct:: >>> semver.__version__ - '3.0.3-alpha.1' + '3.0.3' diff --git a/pyproject.toml b/pyproject.toml index 6c9cb93..3398628 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,6 @@ [build-system] requires = [ - # sync with setup.py until we discard non-pep-517/518 "setuptools>=61", # "setuptools-scm>=8", ] @@ -19,6 +18,8 @@ requires-python = ">=3.7" name = "semver" description = "Python helper for Semantic Versioning (https://semver.org)" readme = "README.rst" +# PEP 639 +# licence = "BSD-2-Clause" # readme.content-type = "text/x-rst" license = { file = "LICENSE.txt" } authors = [ @@ -34,7 +35,7 @@ classifiers = [ "Environment :: Web Environment", "Intended Audience :: Developers", "Development Status :: 5 - Production/Stable", - "License :: OSI Approved :: BSD License", + # "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.7", @@ -92,6 +93,7 @@ docs = [ "sphinx", # ==7.0.1 "sphinx-argparse", # ==0.4.0 "sphinx-autodoc-typehints", # ==1.24.0 + "restview", ] devel = [ {include-group = "typing"}, diff --git a/release-procedure.md b/release-procedure.md index 4225bfd..f6c7c37 100644 --- a/release-procedure.md +++ b/release-procedure.md @@ -3,120 +3,142 @@ The following procedures gives a short overview of what steps are needed to create a new release. -## Prepare the Release +## Prepare your environment -1. Verify: +1. Create your API tokens: - * all issues for a new release are closed: . + 1. From the [PyPI test server](https://test.pypi.org/manage/account/token/). - * that all pull requests that should be included in this release are merged: . + 1. From the official [PyPI server](https://test.pypi.org/manage/account/token/). - * that continuous integration for latest build was passing: - . + 1. Save both tokens it in a safe place like your password manager. + +1. Create a file `~/.pypirc` with file mode 0600 and the following minimal content: + + # Protect the file with chmod 0600 ~/.pypirc + [distutils] + index-servers = + test-semver + semver + + [test-semver] + repository = https://test.pypi.org/legacy/ + username = __token__ + password = + + [semver] + repository = https://pypi.org/legacy/ + username = __token__ + password = + +1. Install uv as shown in Astral's [Installing uv](https://docs.astral.sh/uv/getting-started/installation/) documentation. + +1. Update the project's environment: + + uv sync --group devel + +1. Activate your environment: + + source .venv/bin/activate + +## Prepare the Release 1. Create a new branch `release/`. -1. If one or several supported Python versions have been removed or added, verify that the 3 following files have been updated: - * `setup.cfg` +1. If one or several supported **Python** versions have been removed or added, verify that the following files have been updated: + * `pyproject.toml` (look into the key `project.requires-python` and `project.classifiers`) * `tox.ini` * `.git/workflows/pythonpackage.yml` * `CITATION.cff` -1. Verify that the version has been updated and follow - : +1. Verify that: + + * the version has been updated and follow : + * `src/semver/__about__.py` + * `docs/usage/semver-version.rst` + + * all issues for a new release are closed: . - * `src/semver/__about__.py` - * `docs/usage/semver-version.rst` + * all pull requests that should be included in this release are merged: . + + * continuous integration for latest build was passing: + . 1. Add eventually new contributor(s) to [CONTRIBUTORS](https://github.com/python-semver/python-semver/blob/master/CONTRIBUTORS). +1. Create the changelog: -1. Check if all changelog entries are created. If some are missing, [create them](https://python-semver.readthedocs.io/en/latest/development.html#adding-a-changelog-entry). + 1. Check if all changelog entries are created. If some are missing, [create them](https://python-semver.readthedocs.io/en/latest/development.html#adding-a-changelog-entry). -1. Show the new draft [CHANGELOG](https://github.com/python-semver/python-semver/blob/master/CHANGELOG.rst) entry for the latest release with: + 1. Show the new draft [CHANGELOG](https://github.com/python-semver/python-semver/blob/master/CHANGELOG.rst) entry for the latest release with: - $ tox -e changelog + uvx tox r -e changelog - Check the output. If you are not happy, update the files in the - `changelog.d/` directory. - If everything is okay, build the new `CHANGELOG` with: + 1. Check the output. If you are not happy, update the files in the + `changelog.d/` directory. + If everything is okay, build the new `CHANGELOG` with: - $ tox -e changelog -- build + uvx tox r -e changelog -- build 1. Build the documentation and check the output: - $ tox -e docs + uvx tox r -e docs 1. Commit all changes, push, and create a pull request. ## Create the New Release -1. Ensure that long description ([README.rst](https://github.com/python-semver/python-semver/blob/master/README.rst)) can be correctly rendered by Pypi using `restview --long-description` +1. Ensure that long description ([README.rst](https://github.com/python-semver/python-semver/blob/master/README.rst)) can be correctly rendered by Pypi using `uvx restview -b README.rst` 1. Clean up your local Git repository. Be careful, as it **will remove all files** which are not versioned by Git: - $ git clean -xfd + git clean -xfd Before you create your distribution files, clean the directory too: - $ rm dist/* + rm dist/* 1. Create the distribution files (wheel and source): - $ tox -e prepare-dist + uvx tox r -e prepare-dist 1. Upload the wheel and source to TestPyPI first: - ```bash - $ twine upload --repository-url https://test.pypi.org/legacy/ dist/* - ``` + twine upload --verbose --repository test-semver dist/* - If you have a `~/.pypirc` with a `testpypi` section, the upload can be - simplified: - - $ twine upload --repository testpypi dist/* + (Normally you would do it with `uv publish`, but for some unknown reason it didn't work for me.) 1. Check if everything is okay with the wheel. Check also the web site `https://test.pypi.org/project//` +# Finish the release + 1. If everything looks fine, merge the pull request. 1. Upload to PyPI: - ```bash - $ git clean -xfd - $ tox -e prepare-dist - $ twine upload dist/* - ``` + $ git clean -xfd + $ tox r -e prepare-dist + $ twine upload --verbose --repository semver dist/* 1. Go to https://pypi.org/project/semver/ to verify that new version is online and the page is rendered correctly. -# Finish the release - 1. Create a tag: - ```bash - $ git tag -a x.y.z - ``` + git tag -a x.y.z - It's recommended to use the generated Tox output - from the Changelog. + It's recommended to use the generated Tox output from the Changelog. 1. Push the tag: - ```bash - $ git push origin x.y.z - ``` + git push origin x.y.z -1. In [GitHub Release page](https://github.com/python-semver/python-semver/release) - document the new release. - Select the tag from the last step and copy the - content of the tag description into the release - description. +1. In [GitHub Release page](https://github.com/python-semver/python-semver/release) document the new release. + Select the tag from the last step and copy the content of the tag description into the release description. 1. Announce it in . diff --git a/src/semver/__about__.py b/src/semver/__about__.py index 697372f..d971e03 100644 --- a/src/semver/__about__.py +++ b/src/semver/__about__.py @@ -16,7 +16,7 @@ """ #: Semver version -__version__ = "3.0.3-alpha.1" +__version__ = "3.0.3" #: Original semver author __author__ = "Kostiantyn Rybnikov" diff --git a/tox.ini b/tox.ini index 74918c4..5c9db17 100644 --- a/tox.ini +++ b/tox.ini @@ -63,6 +63,7 @@ skip_install = true allowlist_externals = make echo + uvx commands = uvx make -C docs html commands_post = diff --git a/uv.lock b/uv.lock index 026a584..c14d136 100644 --- a/uv.lock +++ b/uv.lock @@ -278,7 +278,7 @@ name = "click" version = "8.1.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "importlib-metadata", marker = "python_full_version < '3.8'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } @@ -876,16 +876,16 @@ wheels = [ [[package]] name = "readme-renderer" -version = "37.3" +version = "36.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "bleach" }, { name = "docutils" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/81/c3/d20152fcd1986117b898f66928938f329d0c91ddc47f081c58e64e0f51dc/readme_renderer-37.3.tar.gz", hash = "sha256:cd653186dfc73055656f090f227f5cb22a046d7f71a841dfa305f55c9a513273", size = 29718 } +sdist = { url = "https://files.pythonhosted.org/packages/15/4e/0ffa80eb3e0d0fcc0c6b901b36d4faa11c47d10b9a066fdd42f24c7e646a/readme_renderer-36.0.tar.gz", hash = "sha256:f71aeef9a588fcbed1f4cc001ba611370e94a0cd27c75b1140537618ec78f0a2", size = 28680 } wheels = [ - { url = "https://files.pythonhosted.org/packages/97/52/fd8a77d6f0a9ddeb26ed8fb334e01ac546106bf0c5b8e40dc826c5bd160f/readme_renderer-37.3-py3-none-any.whl", hash = "sha256:f67a16caedfa71eef48a31b39708637a6f4664c4394801a7b0d6432d13907343", size = 14055 }, + { url = "https://files.pythonhosted.org/packages/0a/3e/ce07c86adbc46cfedf637dd77333184bcd0a913f9c169b0b3afc25f67b6b/readme_renderer-36.0-py3-none-any.whl", hash = "sha256:2c37e472ca96755caba6cc58bcbf673a5574bc033385a2ac91d85dfef2799876", size = 14076 }, ] [[package]] @@ -915,6 +915,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481 }, ] +[[package]] +name = "restview" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "pygments" }, + { name = "readme-renderer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/d4/36ed06051e9702d3dae5f7bb0296b79b18621ff5a1bf43247509cbfeff8d/restview-3.0.2.tar.gz", hash = "sha256:8b4d75a0bed76b67b456ef7011f4eb6c98a556c9f837642df2202c7312fccc1a", size = 50048 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/69/4046cbec75d4574e84fa3b2be53487ae9054129527211961555631805849/restview-3.0.2-py3-none-any.whl", hash = "sha256:9fbd26507f6ddc436a49d0ffced5bc8102a898f289fe53c1234a17be46eb660d", size = 38020 }, +] + [[package]] name = "rfc3986" version = "2.0.0" @@ -978,7 +992,6 @@ wheels = [ [[package]] name = "semver" -version = "3.0.3a1" source = { editable = "." } [package.dev-dependencies] @@ -993,6 +1006,7 @@ devel = [ { name = "pyright" }, { name = "pytest" }, { name = "pytest-cov" }, + { name = "restview" }, { name = "ruff" }, { name = "sphinx" }, { name = "sphinx-argparse" }, @@ -1002,6 +1016,7 @@ devel = [ { name = "twine" }, ] docs = [ + { name = "restview" }, { name = "sphinx" }, { name = "sphinx-argparse" }, { name = "sphinx-autodoc-typehints" }, @@ -1019,6 +1034,7 @@ gh-action = [ { name = "pyright" }, { name = "pytest" }, { name = "pytest-cov" }, + { name = "restview" }, { name = "ruff" }, { name = "sphinx" }, { name = "sphinx-argparse" }, @@ -1054,6 +1070,7 @@ devel = [ { name = "pyright" }, { name = "pytest" }, { name = "pytest-cov" }, + { name = "restview" }, { name = "ruff" }, { name = "sphinx" }, { name = "sphinx-argparse" }, @@ -1063,6 +1080,7 @@ devel = [ { name = "twine" }, ] docs = [ + { name = "restview" }, { name = "sphinx" }, { name = "sphinx-argparse" }, { name = "sphinx-autodoc-typehints" }, @@ -1080,6 +1098,7 @@ gh-action = [ { name = "pyright" }, { name = "pytest" }, { name = "pytest-cov" }, + { name = "restview" }, { name = "ruff" }, { name = "sphinx" }, { name = "sphinx-argparse" }, From bae0b7ca7ce3f2a3268290ff9b4b379e7ed3a779 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Mon, 20 Jan 2025 09:08:20 +0100 Subject: [PATCH 4/6] Fix #459: Fix 3.0.3 * Raise version 3.0.3 -> 3.0.4 * Fix #457: Re-enable Trove license identifier * Fix #456: Fix source dist file - Sort MANIFEST.in and add missing files - Add CONTRIBUTORS, .pytest.ini, .ruff.toml, and uv.lock * Update Changelog --- CHANGELOG.rst | 19 +++++++++++++++++++ MANIFEST.in | 20 +++++++++++++++++++- docs/usage/semver-version.rst | 2 +- pyproject.toml | 2 +- src/semver/__about__.py | 2 +- 5 files changed, 41 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 97a5f89..9efa447 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,6 +18,25 @@ This section covers the changes between major version 2 and version 3. .. towncrier release notes start +Version 3.0.4 +============= + +:Released: 2025-01-24 +:Maintainer: Tom Schraitle + + +Bug Fixes +--------- + +* :gh:`459`: Fix 3.0.3: + + * :pr:`457`: Re-enable Trove license identifier + * :pr:`456`: Fix source dist file + + +---- + + Version 3.0.3 ============= diff --git a/MANIFEST.in b/MANIFEST.in index e37851c..fa2dcb2 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,8 +1,26 @@ +include *.md include *.rst include *.txt -include tests/test_*.py +include CONTRIBUTORS +include CITATION.cff +include Makefile +include changelog.d/* +graft docs/** +include tests/*.py +include tox.ini +include .pytest.ini +include .ruff.toml +include uv.lock + +# The dot files: +include .coveragerc +include .editorconfig +include .gitignore +include .readthedocs.yaml + prune docs/_build recursive-exclude .github * +prune docs/**/__pycache__ global-exclude __pycache__ diff --git a/docs/usage/semver-version.rst b/docs/usage/semver-version.rst index df04a74..c771a00 100644 --- a/docs/usage/semver-version.rst +++ b/docs/usage/semver-version.rst @@ -4,4 +4,4 @@ Getting the Version of semver To know the version of semver itself, use the following construct:: >>> semver.__version__ - '3.0.3' + '3.0.4' diff --git a/pyproject.toml b/pyproject.toml index 3398628..6048167 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,7 @@ classifiers = [ "Environment :: Web Environment", "Intended Audience :: Developers", "Development Status :: 5 - Production/Stable", - # "License :: OSI Approved :: BSD License", + "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.7", diff --git a/src/semver/__about__.py b/src/semver/__about__.py index d971e03..21494e6 100644 --- a/src/semver/__about__.py +++ b/src/semver/__about__.py @@ -16,7 +16,7 @@ """ #: Semver version -__version__ = "3.0.3" +__version__ = "3.0.4" #: Original semver author __author__ = "Kostiantyn Rybnikov" From d959aa7d669bbabd9ccce89f2119639bdc8a05dd Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Tue, 28 Jan 2025 09:46:32 +0100 Subject: [PATCH 5/6] Fix #463: Remove duplicate code Version.bump_build (#464) * Fix #463: Remove duplicate code Version.bump_build * Update changelog --- changelog.d/463.trivial.rst | 1 + src/semver/version.py | 12 ------------ 2 files changed, 1 insertion(+), 12 deletions(-) create mode 100644 changelog.d/463.trivial.rst diff --git a/changelog.d/463.trivial.rst b/changelog.d/463.trivial.rst new file mode 100644 index 0000000..3dcad2a --- /dev/null +++ b/changelog.d/463.trivial.rst @@ -0,0 +1 @@ +Remove double code in :meth:`Version.bump_build` \ No newline at end of file diff --git a/src/semver/version.py b/src/semver/version.py index 0ff5b9f..ec24fbb 100644 --- a/src/semver/version.py +++ b/src/semver/version.py @@ -359,18 +359,6 @@ def bump_build(self, token: Optional[str] = "build") -> "Version": else: build = str(token) + ".0" - # self._build or (token or "build") + ".0" - build = cls._increment_string(build) - if self._build is not None: - build = self._build - elif token == "": - build = "0" - elif token is None: - build = "build.0" - else: - build = str(token) + ".0" - - # self._build or (token or "build") + ".0" build = cls._increment_string(build) return cls(self._major, self._minor, self._patch, self._prerelease, build) From d8813b67a2ec5d579365f51a6d8f96d02f3fcbc3 Mon Sep 17 00:00:00 2001 From: Learloj Date: Tue, 28 Jan 2025 13:40:12 +0100 Subject: [PATCH 6/6] Fix #460 Improve bump_prerelease to alway get a newer version (#462) Raising a prerelease version always results in a newer version, and raising an empty prerelease version has the option to raise the patch version as well Co-authored-by: Tom Schraitle --------- Co-authored-by: Bas Roos Co-authored-by: Tom Schraitle --- changelog.d/460.bugfix.rst | 9 ++++ docs/usage/raise-parts-of-a-version.rst | 17 +++++-- src/semver/version.py | 64 +++++++++++++++++++------ tests/test_bump.py | 55 ++++++++++++++++++--- tests/test_pysemver-cli.py | 2 +- tox.ini | 1 + 6 files changed, 122 insertions(+), 26 deletions(-) create mode 100644 changelog.d/460.bugfix.rst diff --git a/changelog.d/460.bugfix.rst b/changelog.d/460.bugfix.rst new file mode 100644 index 0000000..dc21db8 --- /dev/null +++ b/changelog.d/460.bugfix.rst @@ -0,0 +1,9 @@ +:meth:`~semver.version.Version.bump_prerelease` will now add `.0` to an +existing prerelease when the last segment of the current prerelease, split by +dots (`.`), is not numeric. This is to ensure the new prerelease is considered +higher than the previous one. + +:meth:`~semver.version.Version.bump_prerelease` now also support an argument +`bump_when_empty` which will bump the patch version if there is no existing +prerelease, to ensure the resulting version is considered a higher version than +the previous one. \ No newline at end of file diff --git a/docs/usage/raise-parts-of-a-version.rst b/docs/usage/raise-parts-of-a-version.rst index be89cf8..101a8c3 100644 --- a/docs/usage/raise-parts-of-a-version.rst +++ b/docs/usage/raise-parts-of-a-version.rst @@ -3,13 +3,14 @@ Raising Parts of a Version .. note:: - Keep in mind, "raising" the pre-release only will make your - complete version *lower* than before. + Keep in mind, by default, "raising" the pre-release for a version without an existing + prerelease part, only will make your complete version *lower* than before. For example, having version ``1.0.0`` and raising the pre-release will lead to ``1.0.0-rc.1``, but ``1.0.0-rc.1`` is smaller than ``1.0.0``. - If you search for a way to take into account this behavior, look for the + To avoid this, set `bump_when_empty=True` in the + :meth:`~semver.version.Version.bump_prerelease` method, or by using the method :meth:`~semver.version.Version.next_version` in section :ref:`increase-parts-of-a-version`. @@ -67,4 +68,14 @@ is not taken into account: >>> str(Version.parse("3.4.5-rc.1").bump_prerelease('')) '3.4.5-rc.2' +To ensure correct ordering, we append `.0` to the last prerelease identifier +if it's not numeric. This prevents cases where `rc9` would incorrectly sort +lower than `rc10` (non-numeric identifiers are compared alphabetically): + +.. code-block:: python + + >>> str(Version.parse("3.4.5-rc9").bump_prerelease()) + '3.4.5-rc9.0' + >>> str(Version.parse("3.4.5-rc.9").bump_prerelease()) + '3.4.5-rc.10' diff --git a/src/semver/version.py b/src/semver/version.py index ec24fbb..f9450f9 100644 --- a/src/semver/version.py +++ b/src/semver/version.py @@ -77,8 +77,10 @@ class Version: #: The names of the different parts of a version NAMES: ClassVar[Tuple[str, ...]] = tuple([item[1:] for item in __slots__]) - #: Regex for number in a prerelease + #: Regex for number in a build _LAST_NUMBER: ClassVar[Pattern[str]] = re.compile(r"(?:[^\d]*(\d+)[^\d]*)+") + #: Regex for number in a prerelease + _LAST_PRERELEASE: ClassVar[Pattern[str]] = re.compile(r"^(.*\.)?(\d+)$") #: Regex template for a semver version _REGEX_TEMPLATE: ClassVar[ str @@ -245,6 +247,23 @@ def __iter__(self) -> VersionIterator: """Return iter(self).""" yield from self.to_tuple() + @staticmethod + def _increment_prerelease(string: str) -> str: + """ + Check if the last part of a dot-separated string is numeric. If yes, + increase them. Else, add '.0' + + :param string: the prerelease version to increment + :return: the incremented string + """ + match = Version._LAST_PRERELEASE.search(string) + if match: + next_ = str(int(match.group(2)) + 1) + string = match.group(1) + next_ if match.group(1) else next_ + else: + string += ".0" + return string + @staticmethod def _increment_string(string: str) -> str: """ @@ -305,35 +324,52 @@ def bump_patch(self) -> "Version": cls = type(self) return cls(self._major, self._minor, self._patch + 1) - def bump_prerelease(self, token: Optional[str] = "rc") -> "Version": + def bump_prerelease( + self, + token: Optional[str] = "rc", + bump_when_empty: Optional[bool] = False + ) -> "Version": """ Raise the prerelease part of the version, return a new object but leave self untouched. + .. versionchanged:: 3.1.0 + Parameter `bump_when_empty` added. When set to true, bumps the patch version + when called with a version that has no prerelease segment, so the return + value will be considered a newer version. + + Adds `.0` to the prerelease if the last part of the dot-separated + prerelease is not a number. + :param token: defaults to ``'rc'`` :return: new :class:`Version` object with the raised prerelease part. The original object is not modified. >>> ver = semver.parse("3.4.5") >>> ver.bump_prerelease().prerelease - 'rc.2' + 'rc.1' >>> ver.bump_prerelease('').prerelease '1' >>> ver.bump_prerelease(None).prerelease 'rc.1' + >>> str(ver.bump_prerelease(bump_when_empty=True)) + '3.4.6-rc.1' """ cls = type(self) + patch = self._patch if self._prerelease is not None: - prerelease = self._prerelease - elif token == "": - prerelease = "0" - elif token is None: - prerelease = "rc.0" + prerelease = cls._increment_prerelease(self._prerelease) else: - prerelease = str(token) + ".0" + if bump_when_empty: + patch += 1 + if token == "": + prerelease = "1" + elif token is None: + prerelease = "rc.1" + else: + prerelease = str(token) + ".1" - prerelease = cls._increment_string(prerelease) - return cls(self._major, self._minor, self._patch, prerelease) + return cls(self._major, self._minor, patch, prerelease) def bump_build(self, token: Optional[str] = "build") -> "Version": """ @@ -445,10 +481,8 @@ def next_version(self, part: str, prerelease_token: str = "rc") -> "Version": # Only check the main parts: if part in cls.NAMES[:3]: return getattr(version, "bump_" + part)() - - if not version.prerelease: - version = version.bump_patch() - return version.bump_prerelease(prerelease_token) + else: + return version.bump_prerelease(prerelease_token, bump_when_empty=True) @_comparator def __eq__(self, other: Comparable) -> bool: # type: ignore diff --git a/tests/test_bump.py b/tests/test_bump.py index 34e0b2a..fcbedf4 100644 --- a/tests/test_bump.py +++ b/tests/test_bump.py @@ -6,6 +6,7 @@ bump_minor, bump_patch, bump_prerelease, + compare, parse_version_info, ) @@ -32,62 +33,101 @@ def test_should_versioninfo_bump_minor_and_patch(): v = parse_version_info("3.4.5") expected = parse_version_info("3.5.1") assert v.bump_minor().bump_patch() == expected + assert v.compare(expected) == -1 def test_should_versioninfo_bump_patch_and_prerelease(): v = parse_version_info("3.4.5-rc.1") expected = parse_version_info("3.4.6-rc.1") assert v.bump_patch().bump_prerelease() == expected + assert v.compare(expected) == -1 def test_should_versioninfo_bump_patch_and_prerelease_with_token(): v = parse_version_info("3.4.5-dev.1") expected = parse_version_info("3.4.6-dev.1") assert v.bump_patch().bump_prerelease("dev") == expected + assert v.compare(expected) == -1 def test_should_versioninfo_bump_prerelease_and_build(): v = parse_version_info("3.4.5-rc.1+build.1") expected = parse_version_info("3.4.5-rc.2+build.2") assert v.bump_prerelease().bump_build() == expected + assert v.compare(expected) == -1 def test_should_versioninfo_bump_prerelease_and_build_with_token(): v = parse_version_info("3.4.5-rc.1+b.1") expected = parse_version_info("3.4.5-rc.2+b.2") assert v.bump_prerelease().bump_build("b") == expected + assert v.compare(expected) == -1 def test_should_versioninfo_bump_multiple(): v = parse_version_info("3.4.5-rc.1+build.1") expected = parse_version_info("3.4.5-rc.2+build.2") assert v.bump_prerelease().bump_build().bump_build() == expected + assert v.compare(expected) == -1 expected = parse_version_info("3.4.5-rc.3") assert v.bump_prerelease().bump_build().bump_build().bump_prerelease() == expected + assert v.compare(expected) == -1 def test_should_versioninfo_bump_prerelease_with_empty_str(): v = parse_version_info("3.4.5") expected = parse_version_info("3.4.5-1") assert v.bump_prerelease("") == expected + assert v.compare(expected) == 1 def test_should_versioninfo_bump_prerelease_with_none(): v = parse_version_info("3.4.5") expected = parse_version_info("3.4.5-rc.1") assert v.bump_prerelease(None) == expected + assert v.compare(expected) == 1 + + +def test_should_versioninfo_bump_prerelease_nonnumeric(): + v = parse_version_info("3.4.5-rc1") + expected = parse_version_info("3.4.5-rc1.0") + assert v.bump_prerelease(None) == expected + assert v.compare(expected) == -1 + + +def test_should_versioninfo_bump_prerelease_nonnumeric_nine(): + v = parse_version_info("3.4.5-rc9") + expected = parse_version_info("3.4.5-rc9.0") + assert v.bump_prerelease(None) == expected + assert v.compare(expected) == -1 + + +def test_should_versioninfo_bump_prerelease_bump_patch(): + v = parse_version_info("3.4.5") + expected = parse_version_info("3.4.6-rc.1") + assert v.bump_prerelease(bump_when_empty=True) == expected + assert v.compare(expected) == -1 + + +def test_should_versioninfo_bump_patch_and_prerelease_bump_patch(): + v = parse_version_info("3.4.5") + expected = parse_version_info("3.4.7-rc.1") + assert v.bump_patch().bump_prerelease(bump_when_empty=True) == expected + assert v.compare(expected) == -1 def test_should_versioninfo_bump_build_with_empty_str(): v = parse_version_info("3.4.5") expected = parse_version_info("3.4.5+1") assert v.bump_build("") == expected + assert v.compare(expected) == 0 def test_should_versioninfo_bump_build_with_none(): v = parse_version_info("3.4.5") expected = parse_version_info("3.4.5+build.1") assert v.bump_build(None) == expected + assert v.compare(expected) == 0 def test_should_ignore_extensions_for_bump(): @@ -95,18 +135,18 @@ def test_should_ignore_extensions_for_bump(): @pytest.mark.parametrize( - "version,token,expected", + "version,token,expected,expected_compare", [ - ("3.4.5-rc.9", None, "3.4.5-rc.10"), - ("3.4.5", None, "3.4.5-rc.1"), - ("3.4.5", "dev", "3.4.5-dev.1"), - ("3.4.5", "", "3.4.5-rc.1"), + ("3.4.5-rc.9", None, "3.4.5-rc.10", -1), + ("3.4.5", None, "3.4.5-rc.1", 1), + ("3.4.5", "dev", "3.4.5-dev.1", 1), + ("3.4.5", "", "3.4.5-rc.1", 1), ], ) -def test_should_bump_prerelease(version, token, expected): +def test_should_bump_prerelease(version, token, expected, expected_compare): token = "rc" if not token else token assert bump_prerelease(version, token) == expected - + assert compare(version, expected) == expected_compare def test_should_ignore_build_on_prerelease_bump(): assert bump_prerelease("3.4.5-rc.1+build.4") == "3.4.5-rc.2" @@ -123,3 +163,4 @@ def test_should_ignore_build_on_prerelease_bump(): ) def test_should_bump_build(version, expected): assert bump_build(version) == expected + assert compare(version, expected) == 0 \ No newline at end of file diff --git a/tests/test_pysemver-cli.py b/tests/test_pysemver-cli.py index e783a0b..5a0a2f8 100644 --- a/tests/test_pysemver-cli.py +++ b/tests/test_pysemver-cli.py @@ -55,7 +55,7 @@ def test_should_parse_cli_arguments(cli, expected): ( cmd_bump, Namespace(bump="prerelease", version="1.2.3-rc1"), - does_not_raise("1.2.3-rc2"), + does_not_raise("1.2.3-rc1.0"), ), ( cmd_bump, diff --git a/tox.ini b/tox.ini index 5c9db17..d39ce81 100644 --- a/tox.ini +++ b/tox.ini @@ -29,6 +29,7 @@ deps = setuptools-scm setenv = PIP_DISABLE_PIP_VERSION_CHECK = 1 +downloads = true [testenv:mypy]