diff --git a/.github/workflows/python-testing.yml b/.github/workflows/python-testing.yml index 8f32ffd1..bbbe2a42 100644 --- a/.github/workflows/python-testing.yml +++ b/.github/workflows/python-testing.yml @@ -1,20 +1,23 @@ --- name: Python +# HINT: Sync this paths with the egrep in step check_files on: push: branches: [ "master", "main" ] paths: - 'pyproject.toml' + - 'setup.cfg' - '**.py' - - '.github/workflows/python-testing.yml' + - '.github/workflows/*.yml' pull_request: branches: [ "master", "main" ] paths: - 'pyproject.toml' + - 'setup.cfg' - '**.py' - - '.github/workflows/python-testing.yml' + - '.github/workflows/*.yml' permissions: contents: read @@ -22,45 +25,67 @@ permissions: concurrency: # only cancel in-progress runs of the same workflow group: ${{ github.workflow }}-${{ github.ref }} - # ${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: + check-files: + runs-on: ubuntu-latest + outputs: + can_run: ${{ steps.check_files.outputs.can_run }} + + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: GitHub variables + id: gh-vars + run: | + for var in GITHUB_WORKFLOW GITHUB_ACTION GITHUB_ACTIONS GITHUB_REPOSITORY GITHUB_EVEN_NAME GITHUB_EVENT_PATH GITHUB_WORKSPACE GITHUB_SHA GITHUB_REF GITHUB_HEAD_REF GITHUB_BASE_REF; do + echo "$var = ${!var}" + done + + - name: Check for file changes + id: check_files + run: | + # ${{ github.event.after }} ${{ github.event.before }} + can_run=$(git diff --name-only HEAD~1 HEAD | \ + egrep -q '.github/workflows/|pyproject.toml|setup.cfg|\.py$' && echo 1 || echo 0) + echo "can_run=$can_run" + echo "can_run=$can_run" >> $GITHUB_OUTPUT + + skip_test: + runs-on: ubuntu-latest + needs: check-files + timeout-minutes: 2 + if: ${{ needs.check-files.outputs.can_run == '0' }} + + steps: + - name: Skip test + run: | + echo "Nothing to do as no TOML, Python, or YAML file has been changed. + " + echo "Skipping." + check: runs-on: ubuntu-latest + needs: check-files # Timout of 15min timeout-minutes: 15 + # needs.check-files.outputs.can_run + if: ${{ needs.check-files.outputs.can_run == '1' }} steps: - uses: actions/checkout@v3 - - name: Output env variables - run: | - echo "Default branch=${default-branch}" - echo "GITHUB_WORKFLOW=${GITHUB_WORKFLOW}" - echo "GITHUB_ACTION=$GITHUB_ACTION" - echo "GITHUB_ACTIONS=$GITHUB_ACTIONS" - echo "GITHUB_ACTOR=$GITHUB_ACTOR" - echo "GITHUB_REPOSITORY=$GITHUB_REPOSITORY" - echo "GITHUB_EVENT_NAME=$GITHUB_EVENT_NAME" - echo "GITHUB_EVENT_PATH=$GITHUB_EVENT_PATH" - echo "GITHUB_WORKSPACE=$GITHUB_WORKSPACE" - echo "GITHUB_SHA=$GITHUB_SHA" - echo "GITHUB_REF=$GITHUB_REF" - echo "GITHUB_HEAD_REF=$GITHUB_HEAD_REF" - echo "GITHUB_BASE_REF=$GITHUB_BASE_REF" - echo "::debug::---Start content of file $GITHUB_EVENT_PATH" - cat $GITHUB_EVENT_PATH - echo "\n" - echo "::debug::---end" - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: 3.8 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: | @@ -68,9 +93,10 @@ jobs: tests: needs: check - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} strategy: max-parallel: 5 + fail-fast: true matrix: python-version: ["3.7", "3.8", @@ -79,10 +105,11 @@ jobs: "3.11", # "3.12-dev" ] + os: [ubuntu-latest, "macos-latest"] steps: - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} + - name: Set up Python ${{ matrix.python-version }} for ${{ matrix.os }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..33e3e3aa --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,20 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +# 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 a773e1f3..2281f167 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,6 +18,58 @@ This section covers the changes between major version 2 and version 3. .. towncrier release notes start +Version 3.0.2 +============= + +:Released: 2023-10-09 +:Maintainer: + + +Bug Fixes +--------- + +* :pr:`418`: Replace :class:`~collection.OrderedDict` with :class:`dict`. + + The dict datatype is ordered since Python 3.7. As we do not support + Python 3.6 anymore, it can be considered safe to avoid :class:`~collection.OrderedDict`. + Related to :gh:`419`. + +* :pr:`426`: Fix :meth:`~semver.version.Version.replace` method to use the derived class + of an instance instead of :class:`~semver.version.Version` class. + + + +Improved Documentation +---------------------- + +* :pr:`431`: Clarify version policy for the different semver versions (v2, v3, >v3) + and the supported Python versions. + +* :gh:`432`: Improve external doc links to Python and Pydantic. + + + +Features +-------- + +* :pr:`417`: Amend GitHub Actions to check against MacOS. + + + +Trivial/Internal Changes +------------------------ + +* :pr:`420`: Introduce :py:class:`~typing.ClassVar` for some :class:`~semver.version.Version` + class variables, mainly :data:`~semver.version.Version.NAMES` and some private. + +* :pr:`421`: Insert mypy configuration into :file:`pyproject.toml` and remove + config options from :file:`tox.ini`. + + + +---- + + Version 3.0.1 ============= diff --git a/README.rst b/README.rst index ede10a18..66552796 100644 --- a/README.rst +++ b/README.rst @@ -3,30 +3,13 @@ Quickstart .. teaser-begin -A Python module for `semantic versioning`_. Simplifies comparing versions. +A Python module to simplify `semantic versioning`_. |GHAction| |python-support| |downloads| |license| |docs| |black| |openissues| |GHDiscussion| .. teaser-end -.. note:: - - This project works for Python 3.7 and greater only. If you are - looking for a compatible version for Python 2, use the - maintenance branch |MAINT|_. - - The last version of semver which supports Python 2.7 to 3.5 will be - 2.x.y However, keep in mind, the major 2 release is frozen: no new - features nor backports will be integrated. - - We recommend to upgrade your workflow to Python 3 to gain support, - bugfixes, and new features. - -.. |MAINT| replace:: ``maint/v2`` -.. _MAINT: https://github.com/python-semver/python-semver/tree/maint/v2 - - The module follows the ``MAJOR.MINOR.PATCH`` style: * ``MAJOR`` version when you make incompatible API changes, diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 00000000..4702eb08 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,14 @@ +# Getting support + +If you need help, try these ways: + +* Ask your questions on the [discussion](https://github.com/python-semver/python-semver/discussions) page. + + Our forum is a good way to post your questions. +* Read the [python-semver documentation](https://python-semver.readthedocs.io/). + + The documentation contains all the information to install and use the library. +* Suggest a new feature or a new [issue](https://github.com/python-semver/python-semver/issues/new) + + If you found a problem or would like to suggest a new feature that could improve python-semver, + this is the place to go. diff --git a/docs/advanced/combine-pydantic-and-semver.rst b/docs/advanced/combine-pydantic-and-semver.rst index a00c2cff..3236c2a2 100644 --- a/docs/advanced/combine-pydantic-and-semver.rst +++ b/docs/advanced/combine-pydantic-and-semver.rst @@ -5,45 +5,67 @@ According to its homepage, `Pydantic `_ "enforces type hints at runtime, and provides user friendly errors when data is invalid." -To work with Pydantic, use the following steps: +To work with Pydantic>2.0, use the following steps: 1. Derive a new class from :class:`~semver.version.Version` - first and add the magic methods :py:meth:`__get_validators__` - and :py:meth:`__modify_schema__` like this: + first and add the magic methods :py:meth:`__get_pydantic_core_schema__` + and :py:meth:`__get_pydantic_json_schema__` like this: .. code-block:: python + from typing import Annotated, Any, Callable + from pydantic import GetJsonSchemaHandler + from pydantic_core import core_schema + from pydantic.json_schema import JsonSchemaValue from semver import Version - class PydanticVersion(Version): - @classmethod - def _parse(cls, version): - return cls.parse(version) + class _VersionPydanticAnnotation: @classmethod - def __get_validators__(cls): - """Return a list of validator methods for pydantic models.""" - yield cls._parse + def __get_pydantic_core_schema__( + cls, + _source_type: Any, + _handler: Callable[[Any], core_schema.CoreSchema], + ) -> core_schema.CoreSchema: + def validate_from_str(value: str) -> Version: + return Version.parse(value) + + from_str_schema = core_schema.chain_schema( + [ + core_schema.str_schema(), + core_schema.no_info_plain_validator_function(validate_from_str), + ] + ) + + return core_schema.json_or_python_schema( + json_schema=from_str_schema, + python_schema=core_schema.union_schema( + [ + core_schema.is_instance_schema(Version), + from_str_schema, + ] + ), + serialization = core_schema.to_string_ser_schema(), + ) @classmethod - def __modify_schema__(cls, field_schema): - """Inject/mutate the pydantic field schema in-place.""" - field_schema.update(examples=["1.0.2", - "2.15.3-alpha", - "21.3.15-beta+12345", - ] - ) + def __get_pydantic_json_schema__( + cls, _core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler + ) -> JsonSchemaValue: + return handler(core_schema.str_schema()) + + ManifestVersion = Annotated[Version, _VersionPydanticAnnotation] 2. Create a new model (in this example :class:`MyModel`) and derive - it from :class:`pydantic.BaseModel`: + it from :py:class:`pydantic:pydantic.BaseModel`: .. code-block:: python import pydantic class MyModel(pydantic.BaseModel): - version: PydanticVersion + version: _VersionPydanticAnnotation 3. Use your model like this: @@ -54,4 +76,4 @@ To work with Pydantic, use the following steps: The attribute :py:attr:`model.version` will be an instance of :class:`~semver.version.Version`. If the version is invalid, the construction will raise a - :py:exc:`pydantic.ValidationError`. + :py:class:`pydantic:pydantic_core.ValidationError`. diff --git a/docs/changelog-semver3-devel.rst b/docs/changelog-semver3-devel.rst index 2d40635d..75579d0f 100644 --- a/docs/changelog-semver3-devel.rst +++ b/docs/changelog-semver3-devel.rst @@ -132,11 +132,11 @@ Improved Documentation :class:`~semver.version.Version` class * Remove semver. prefix in doctests to make examples shorter * Correct some references to dunder methods like - :func:`~.semver.version.Version.__getitem__`, - :func:`~.semver.version.Version.__gt__` etc. + :func:`~semver.version.Version.__getitem__`, + :func:`~semver.version.Version.__gt__` etc. * Remove inconsistencies and mention module level function as deprecated and discouraged from using - * Make empty :py:func:`super` call in :file:`semverwithvprefix.py` example + * Make empty :py:class:`python:super` call in :file:`semverwithvprefix.py` example * :gh:`315`: Improve release procedure text @@ -151,34 +151,32 @@ Trivial/Internal Changes The following functions got renamed: - * function ``semver.version.comparator`` got renamed to + * function :func:`semver.version.comparator` got renamed to :func:`semver.version._comparator` as it is only useful inside the :class:`~semver.version.Version` class. - * function ``semver.version.cmp`` got renamed to + * function :func:`semver.version.cmp` got renamed to :func:`semver.version._cmp` as it is only useful inside the :class:`~semver.version.Version` class. The following functions got integrated into the :class:`~semver.version.Version` class: - * function ``semver.version._nat_cmd`` as a classmethod - * function ``semver.version.ensure_str`` + * function :func:`semver.version._nat_cmd` as a classmethod + * function :func:`semver.version.ensure_str` * :gh:`313`: Correct :file:`tox.ini` for ``changelog`` entry to skip installation for semver. This should speed up the execution of towncrier. * :gh:`316`: Comparisons of :class:`~semver.version.Version` class and other - types return now a :py:const:`NotImplemented` constant instead - of a :py:exc:`TypeError` exception. + types return now a :py:data:`python:NotImplemented` constant instead + of a :py:exc:`python:TypeError` exception. - The `NotImplemented`_ section of the Python documentation recommends - returning this constant when comparing with ``__gt__``, ``__lt__``, - and other comparison operators to "to indicate that the operation is + In the Python documentation, :py:data:`python:NotImplemented` recommends + returning this constant when comparing with :py:meth:`__gt__ `, :py:meth:`__lt__ `, + and other comparison operators "to indicate that the operation is not implemented with respect to the other type". - .. _NotImplemented: https://docs.python.org/3/library/constants.html#NotImplemented - * :gh:`319`: Introduce stages in :file:`.travis.yml` The config file contains now two stages: check and test. If check fails, the test stage won't be executed. This could @@ -206,7 +204,7 @@ Version 3.0.0-dev.2 Deprecations ------------ -* :gh:`169`: Deprecate CLI functions not imported from ``semver.cli``. +* :gh:`169`: Deprecate CLI functions not imported from :mod:`semver.cli`. .. _semver-3.0.0-dev.2-features: @@ -222,10 +220,10 @@ Features * Create :file:`src/semver/_deprecated.py` for the ``deprecated`` decorator and other deprecated functions * Create :file:`src/semver/__main__.py` to allow calling the CLI using :command:`python -m semver` * Create :file:`src/semver/_types.py` to hold type aliases - * Create :file:`src/semver/version.py` to hold the :class:`Version` class (old name :class:`VersionInfo`) and its utility functions + * Create :file:`src/semver/version.py` to hold the :class:`~semver.version.Version` class (old name :class:`~semver.version.VersionInfo`) and its utility functions * Create :file:`src/semver/__about__.py` for all the metadata variables -* :gh:`305`: Rename :class:`VersionInfo` to :class:`Version` but keep an alias for compatibility +* :gh:`305`: Rename :class:`~semver.version.VersionInfo` to :class:`~semver.version.Version` but keep an alias for compatibility .. _semver-3.0.0-dev.2-docs: @@ -239,7 +237,7 @@ Improved Documentation * Add migration chapter from semver2 to semver3. * Distinguish between changlog for version 2 and 3 -* :gh:`305`: Add note about :class:`Version` rename. +* :gh:`305`: Add note about :class:`~semver.version.Version` rename. .. _semver-3.0.0-dev.2-trivial: @@ -314,8 +312,8 @@ Features * Split test suite into separate files under :file:`tests/` directory * Adjust and update :file:`setup.py`. Requires Python >=3.6.* - Extract metadata directly from source (affects all the ``__version__``, - ``__author__`` etc. variables) + Extract metadata directly from source (affects all the :data:`~semver.__about__.__version__`, + :data:`~semver.__about__.__author__` etc. variables) * :gh:`270`: Configure Towncrier (:pr:`273`:) @@ -331,7 +329,7 @@ Features * Update documentation and add include a new section "Changelog" included from :file:`changelog.d/README.rst`. -* :gh:`276`: Document how to create a sublass from :class:`VersionInfo` class +* :gh:`276`: Document how to create a sublass from :class:`~semver.version.VersionInfo` class * :gh:`213`: Add typing information diff --git a/docs/conf.py b/docs/conf.py index eab3248d..39d8cb4b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -126,8 +126,13 @@ def find_version(*file_paths): # See https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html intersphinx_mapping = { # Download it from the root with: - # wget -O docs/python-objects.inv https://docs.python.org/3/objects.inv + # wget -O docs/inventories/python-objects.inv https://docs.python.org/3/objects.inv "python": ("https://docs.python.org/3", (None, "inventories/python-objects.inv")), + # wget -O docs/inventories/pydantic.inv https://docs.pydantic.dev/latest/objects.inv + "pydantic": ( + "https://docs.pydantic.dev/latest/", + (None, "inventories/pydantic.inv"), + ), } # Avoid side-effects (namely that documentations local references can # suddenly resolve to an external location.) diff --git a/docs/contribute/doc-semver.rst b/docs/contribute/doc-semver.rst index fcc6c1ac..e5237eaf 100644 --- a/docs/contribute/doc-semver.rst +++ b/docs/contribute/doc-semver.rst @@ -19,13 +19,19 @@ used efficiently. A new feature is *not* complete if it isn't proberly documented. A good documentation includes: + * **Type annotations** + + This library supports type annotations. Therefore, each function + or method requires types for each arguments and return objects. + Exception of this rule is ``self``. + * **A docstring** Each docstring contains a summary line, a linebreak, an optional directive (see next item), the description of its arguments in `Sphinx style`_, and an optional doctest. The docstring is extracted and reused in the :ref:`api` section. - An appropriate docstring should look like this:: + An appropriate docstring looks like this:: def to_tuple(self) -> VersionTuple: """ @@ -70,11 +76,11 @@ documentation includes: * **The documentation** - A docstring is good, but in most cases it's too dense. API documentation - cannot replace a good user documentation. Describe how - to use your new feature in our documentation. Here you can give your - readers more examples, describe it in a broader context or show - edge cases. + A docstring is good, but in most cases it is too short. API documentation + cannot replace good user documentation. + Describe *how* to use your new feature in the documentation. + Here you can give your readers more examples, describe it in a broader + context, or show edge cases. .. _Sphinx style: https://sphinx-rtd-tutorial.rtfd.io/en/latest/docstrings.html diff --git a/docs/index.rst b/docs/index.rst index 1054c225..0f1f32d6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,6 +16,7 @@ Semver |version| -- Semantic Versioning migration/index advanced/index contribute/index + version-policy api .. toctree:: diff --git a/docs/install.rst b/docs/install.rst index 5404882f..dee441de 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -6,25 +6,20 @@ Release Policy As semver uses `Semantic Versioning`_, breaking changes are only introduced in major releases (incremented ``X`` in "X.Y.Z"). +Refer to section :ref:`version-policy` for a general overview. -For users who want to stay with major 2 releases only, add the following version -restriction:: +For users who want or need to stay with major 3 releases only, add the +following version restriction (:file:`setup.py`, :file:`requirements.txt`, +or :file:`pyproject.toml`):: - semver>=2,<3 - -This line avoids surprises. You will get any updates within the major 2 release like -2.11.0 or above. However, you will never get an update for semver 3.0.0. + semver>=3,<4 -Keep in mind, as this line avoids any major version updates, you also will never -get new exciting features or bug fixes. +This line avoids surprises. You will get any updates within the major 3 release like 3.1.x and above. However, you will never get an update for semver 4.0.0. -Same applies for semver v3, if you want to get all updates for the semver v3 -development line, but not a major update to semver v4:: +For users who have to stay with major 2 releases only, use the following line:: - semver>=3,<4 + semver>=2,<3 -You can add this line in your file :file:`setup.py`, :file:`requirements.txt`, -:file:`pyproject.toml`, or any other file that lists your dependencies. Pip --- diff --git a/docs/inventories/pydantic.inv b/docs/inventories/pydantic.inv new file mode 100644 index 00000000..455215a7 Binary files /dev/null and b/docs/inventories/pydantic.inv differ diff --git a/docs/inventories/python-objects.inv b/docs/inventories/python-objects.inv index 6f01e284..90bfa0a6 100644 Binary files a/docs/inventories/python-objects.inv and b/docs/inventories/python-objects.inv differ diff --git a/docs/migration/replace-deprecated-functions.rst b/docs/migration/replace-deprecated-functions.rst index ebe8c354..c0091b73 100644 --- a/docs/migration/replace-deprecated-functions.rst +++ b/docs/migration/replace-deprecated-functions.rst @@ -8,19 +8,26 @@ Replacing Deprecated Functions the module level. The preferred way of using semver is through the :class:`~semver.version.Version` class. -The deprecated functions can still be used in version 2.10.0 and above. In version 3 of -semver, the deprecated functions will be removed. +The deprecated functions can still be used in version 2.10.0 and above. +However, in future versions of semver, the deprecated functions will be removed. -The following list shows the deprecated functions and how you can replace -them with code which is compatible for future versions: +Deprecated Module Level Functions +--------------------------------- + +The following list shows the deprecated module level functions and how you can replace +them with code which is compatible for future versions: -* :func:`semver.bump_major`, :func:`semver.bump_minor`, :func:`semver.bump_patch`, :func:`semver.bump_prerelease`, :func:`semver.bump_build` +* :func:`semver.bump_major `, + :func:`semver.bump_minor `, + :func:`semver.bump_patch `, + :func:`semver.bump_prerelease `, + :func:`semver.bump_build ` Replace them with the respective methods of the :class:`~semver.version.Version` class. - For example, the function :func:`semver.bump_major` is replaced by - :meth:`~semver.version.Version.bump_major` and calling the ``str(versionobject)``: + For example, the function :func:`semver.bump_major ` is replaced by + :meth:`Version.bump_major ` and calling the ``str(versionobject)``: .. code-block:: python @@ -31,14 +38,9 @@ them with code which is compatible for future versions: Likewise with the other module level functions. -* :func:`semver.Version.isvalid` - - Replace it with :meth:`semver.version.Version.is_valid`: +* :func:`semver.finalize_version ` - -* :func:`semver.finalize_version` - - Replace it with :func:`semver.version.Version.finalize_version`: + Replace it with :meth:`Version.finalize_version `: .. code-block:: python @@ -47,7 +49,7 @@ them with code which is compatible for future versions: >>> s1 == s2 True -* :func:`semver.format_version` +* :func:`semver.format_version ` Replace it with ``str(versionobject)``: @@ -58,7 +60,7 @@ them with code which is compatible for future versions: >>> s1 == s2 True -* :func:`semver.max_ver` +* :func:`semver.max_ver ` Replace it with ``max(version1, version2, ...)`` or ``max([version1, version2, ...])`` and a ``key``: @@ -69,9 +71,9 @@ them with code which is compatible for future versions: >>> s1 == s2 True -* :func:`semver.min_ver` +* :func:`semver.min_ver ` - Replace it with ``min(version1, version2, ...)`` or ``min([version1, version2, ...])``: + Replace it with ``min(version1, version2, ...)`` or ``min([version1, version2, ...])`` and a ``key``: .. code-block:: python @@ -80,10 +82,10 @@ them with code which is compatible for future versions: >>> s1 == s2 True -* :func:`semver.parse` +* :func:`semver.parse ` - Replace it with :meth:`semver.version.Version.parse` and call - :meth:`semver.version.Version.to_dict`: + Replace it with :meth:`Version.parse ` and call + :meth:`Version.to_dict `: .. code-block:: python @@ -92,9 +94,9 @@ them with code which is compatible for future versions: >>> v1 == v2 True -* :func:`semver.parse_version_info` +* :func:`semver.parse_version_info ` - Replace it with :meth:`semver.version.Version.parse`: + Replace it with :meth:`Version.parse `: .. code-block:: python @@ -103,9 +105,9 @@ them with code which is compatible for future versions: >>> v1 == v2 True -* :func:`semver.replace` +* :func:`semver.replace ` - Replace it with :meth:`semver.version.Version.replace`: + Replace it with :meth:`Version.replace `: .. code-block:: python @@ -113,3 +115,22 @@ them with code which is compatible for future versions: >>> s2 = str(Version.parse('1.2.3').replace(major=2, patch=10)) >>> s1 == s2 True + + +Deprected Version methods +------------------------- + +The following list shows the deprecated methods of the :class:`~semver.version.Version` class. + +* :meth:`Version.isvalid ` + + Replace it with :meth:`Version.is_valid `: + + +Deprecated Classes +------------------ + +* :class:`VersionInfo ` + + The class was renamed to :class:`~semver.version.Version`. + Don't use the old name anymore. \ No newline at end of file diff --git a/docs/requirements.txt b/docs/requirements.txt index 1186b4c3..2a86234d 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,5 @@ # requirements file for documentation # sphinx +# required in .readthedocs.yaml sphinx-argparse sphinx-autodoc-typehints diff --git a/docs/usage/access-parts-of-a-version.rst b/docs/usage/access-parts-of-a-version.rst index 4eb9274f..b1ce3a14 100644 --- a/docs/usage/access-parts-of-a-version.rst +++ b/docs/usage/access-parts-of-a-version.rst @@ -21,7 +21,7 @@ parts of a version: 'build.4' However, the attributes are read-only. You cannot change any of the above attributes. -If you do, you get an :py:exc:`AttributeError`:: +If you do, you get an :py:exc:`python:AttributeError`:: >>> v.minor = 5 Traceback (most recent call last): diff --git a/docs/usage/access-parts-through-index.rst b/docs/usage/access-parts-through-index.rst index c3651a5e..4553056a 100644 --- a/docs/usage/access-parts-through-index.rst +++ b/docs/usage/access-parts-through-index.rst @@ -33,7 +33,7 @@ Or, as an alternative, you can pass a :func:`slice` object: >>> ver[sl] (10, 3, 2) -Negative numbers or undefined parts raise an :py:exc:`IndexError` exception: +Negative numbers or undefined parts raise an :py:exc:`python:IndexError` exception: .. code-block:: python diff --git a/docs/usage/compare-versions.rst b/docs/usage/compare-versions.rst index ddd03b68..839ad68b 100644 --- a/docs/usage/compare-versions.rst +++ b/docs/usage/compare-versions.rst @@ -62,7 +62,7 @@ To compare two versions depends on your type: >>> "3.5.0" > v True - However, if you compare incomplete strings, you get a :py:exc:`ValueError` exception:: + However, if you compare incomplete strings, you get a :py:exc:`python:ValueError` exception:: >>> v > "1.0" Traceback (most recent call last): @@ -82,7 +82,7 @@ To compare two versions depends on your type: >>> dict(major=1) < v True - If the dictionary contains unknown keys, you get a :py:exc:`TypeError` exception:: + If the dictionary contains unknown keys, you get a :py:exc:`python:TypeError` exception:: >>> v > dict(major=1, unknown=42) Traceback (most recent call last): diff --git a/docs/usage/convert-version-into-different-types.rst b/docs/usage/convert-version-into-different-types.rst index 6948438c..6a447d0d 100644 --- a/docs/usage/convert-version-into-different-types.rst +++ b/docs/usage/convert-version-into-different-types.rst @@ -17,7 +17,7 @@ It is possible to convert a :class:`~semver.version.Version` instance: >>> v = Version(major=3, minor=4, patch=5) >>> v.to_dict() - OrderedDict([('major', 3), ('minor', 4), ('patch', 5), ('prerelease', None), ('build', None)]) + {'major': 3, 'minor': 4, 'patch': 5, 'prerelease': None, 'build': None} * Into a tuple with :meth:`~semver.version.Version.to_tuple`:: diff --git a/docs/usage/create-a-version.rst b/docs/usage/create-a-version.rst index 48bb58a1..9404ae4b 100644 --- a/docs/usage/create-a-version.rst +++ b/docs/usage/create-a-version.rst @@ -41,7 +41,7 @@ A :class:`~semver.version.Version` instance can be created in different ways: Version(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') Keep in mind, the ``major``, ``minor``, ``patch`` parts has to - be positive integers or strings: + be positive integers or strings, otherwise a :py:exc:`python:ValueError` is raised: >>> d = {'major': -3, 'minor': 4, 'patch': 5, 'prerelease': 'pre.2', 'build': 'build.4'} >>> Version(**d) @@ -50,7 +50,7 @@ A :class:`~semver.version.Version` instance can be created in different ways: ValueError: 'major' is negative. A version can only be positive. As a minimum requirement, your dictionary needs at least the ``major`` - key, others can be omitted. You get a ``TypeError`` if your + key, others can be omitted. You get a :py:exc:`python:TypeError` if your dictionary contains invalid keys. Only the keys ``major``, ``minor``, ``patch``, ``prerelease``, and ``build`` are allowed. @@ -87,12 +87,12 @@ Depending on your use case, the following methods are available: * From a string into a dictionary - To access individual parts, you can use the function :func:`semver.parse`:: + To access individual parts, you can use the function :func:`~semver._deprecated.parse`:: >>> semver.parse("3.4.5-pre.2+build.4") - OrderedDict([('major', 3), ('minor', 4), ('patch', 5), ('prerelease', 'pre.2'), ('build', 'build.4')]) + {'major': 3, 'minor': 4, 'patch': 5, 'prerelease': 'pre.2', 'build': 'build.4'} - If you pass an invalid version string you will get a :py:exc:`ValueError`:: + If you pass an invalid version string you will get a :py:exc:`python:ValueError`:: >>> semver.parse("1.2") Traceback (most recent call last): diff --git a/docs/usage/semver-version.rst b/docs/usage/semver-version.rst index 0f2e2411..ac580f98 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.1' + '3.0.2' diff --git a/docs/version-policy.rst b/docs/version-policy.rst new file mode 100644 index 00000000..f0c423f8 --- /dev/null +++ b/docs/version-policy.rst @@ -0,0 +1,52 @@ +.. _version-policy: + +Version Policy +============== + +.. |MAINT| replace:: ``maint/v2`` +.. _MAINT: https://github.com/python-semver/python-semver/tree/maint/v2 +.. |CHANGELOG| replace:: ``Changelog`` +.. _CHANGELOG: https://github.com/python-semver/python-semver/blob/maint/v2/CHANGELOG.rst + +The move from v2 to v3 introduced many changes and deprecated module functions. +The main functionality is handled by the :class:`~semver.version.Version` class +now. Find more information in the section :ref:`semver2-to-3`. + + +semver Version 2 +---------------- + +Active development of major version 2 has stopped. No new features nor +backports will be integrated. +We recommend to upgrade your workflow to Python 3 to gain support, +bugfixes, and new features. + +If you still need this old version, use the |MAINT|_ branch. There you +can look for the |CHANGELOG|_ if you need some details about the history. + + +semver Version 3 +---------------- + +We will not intentionally make breaking changes in minor releases of V3. + +Methods marked as ``deprecated`` raise a warning message when used from the +:py:mod:`python:warnings` module. +Refer to section :ref:`sec_display_deprecation_warnings` to get more information about how to customize it. +Check section :ref:`sec_replace_deprecated_functions` to make your code +ready for future major releases. + + +semver Version 3 and beyond +--------------------------- + +Methods that were marked as deprecated will be very likely be removed. + + +Support for Python versions +--------------------------- + +This project will drop support for a Python version when the +following conditions are met: + +* The Python version has reached `EOL `_. diff --git a/pyproject.toml b/pyproject.toml index 6b12deb0..ce540deb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,16 @@ requires = [ ] build-backend = "setuptools.build_meta" +[tool.setuptools_scm] +version_scheme = "post-release" +local_scheme = "dirty-tag" + +[tool.mypy] +# ignore_missing_imports = true +check_untyped_defs = true +show_error_codes = true +# strict = true +pretty = true [tool.black] diff --git a/release-procedure.md b/release-procedure.md index 7476c79a..4225bfd9 100644 --- a/release-procedure.md +++ b/release-procedure.md @@ -99,14 +99,18 @@ create a new release. 1. Create a tag: - $ git tag -a x.x.x + ```bash + $ git tag -a x.y.z + ``` It's recommended to use the generated Tox output from the Changelog. 1. Push the tag: - $ git push --tags + ```bash + $ git push origin x.y.z + ``` 1. In [GitHub Release page](https://github.com/python-semver/python-semver/release) document the new release. diff --git a/src/semver/__about__.py b/src/semver/__about__.py index 2eff8c86..a0d9cf90 100644 --- a/src/semver/__about__.py +++ b/src/semver/__about__.py @@ -16,7 +16,7 @@ """ #: Semver version -__version__ = "3.0.1" +__version__ = "3.0.2" #: Original semver author __author__ = "Kostiantyn Rybnikov" diff --git a/src/semver/version.py b/src/semver/version.py index d2f336c0..29309ab4 100644 --- a/src/semver/version.py +++ b/src/semver/version.py @@ -1,13 +1,14 @@ """Version handling by a semver compatible version class.""" -import collections import re from functools import wraps from typing import ( Any, + ClassVar, Dict, Iterable, Optional, + Pattern, SupportsInt, Tuple, Union, @@ -73,12 +74,14 @@ class Version: __slots__ = ("_major", "_minor", "_patch", "_prerelease", "_build") #: The names of the different parts of a version - NAMES = tuple([item[1:] for item in __slots__]) + NAMES: ClassVar[Tuple[str, ...]] = tuple([item[1:] for item in __slots__]) #: Regex for number in a prerelease - _LAST_NUMBER = re.compile(r"(?:[^\d]*(\d+)[^\d]*)+") + _LAST_NUMBER: ClassVar[Pattern[str]] = re.compile(r"(?:[^\d]*(\d+)[^\d]*)+") #: Regex template for a semver version - _REGEX_TEMPLATE = r""" + _REGEX_TEMPLATE: ClassVar[ + str + ] = r""" ^ (?P0|[1-9]\d*) (?: @@ -100,12 +103,12 @@ class Version: $ """ #: Regex for a semver version - _REGEX = re.compile( + _REGEX: ClassVar[Pattern[str]] = re.compile( _REGEX_TEMPLATE.format(opt_patch="", opt_minor=""), re.VERBOSE, ) #: Regex for a semver version that might be shorter - _REGEX_OPTIONAL_MINOR_AND_PATCH = re.compile( + _REGEX_OPTIONAL_MINOR_AND_PATCH: ClassVar[Pattern[str]] = re.compile( _REGEX_TEMPLATE.format(opt_patch="?", opt_minor="?"), re.VERBOSE, ) @@ -218,27 +221,24 @@ def to_tuple(self) -> VersionTuple: def to_dict(self) -> VersionDict: """ - Convert the Version object to an OrderedDict. + Convert the Version object to an dict. .. versionadded:: 2.10.0 Renamed :meth:`Version._asdict` to :meth:`Version.to_dict` to make this function available in the public API. - :return: an OrderedDict with the keys in the order ``major``, ``minor``, + :return: an dict with the keys in the order ``major``, ``minor``, ``patch``, ``prerelease``, and ``build``. >>> semver.Version(3, 2, 1).to_dict() - OrderedDict([('major', 3), ('minor', 2), ('patch', 1), \ -('prerelease', None), ('build', None)]) - """ - return collections.OrderedDict( - ( - ("major", self.major), - ("minor", self.minor), - ("patch", self.patch), - ("prerelease", self.prerelease), - ("build", self.build), - ) + {'major': 3, 'minor': 2, 'patch': 1, 'prerelease': None, 'build': None} + """ + return dict( + major=self.major, + minor=self.minor, + patch=self.patch, + prerelease=self.prerelease, + build=self.build, ) def __iter__(self) -> VersionIterator: @@ -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" diff --git a/tox.ini b/tox.ini index b18aa1f7..1bc77e1b 100644 --- a/tox.ini +++ b/tox.ini @@ -49,7 +49,7 @@ commands = flake8 {posargs:} description = Check code style basepython = python3 deps = mypy -commands = mypy {posargs:--ignore-missing-imports --check-untyped-defs src} +commands = mypy {posargs:src} [testenv:docstrings] pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy