diff --git a/.gitignore b/.gitignore index c0859cd3..dead3352 100644 --- a/.gitignore +++ b/.gitignore @@ -268,3 +268,6 @@ fabric.properties docs/_api !docs/_api/semver.__about__.rst + +# For node +node_modules/ diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7f515aa1..28a401e2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,8 @@ Changes for the upcoming release can be found in the `"changelog.d" directory `_ in our repository. +This section covers the changes between major version 2 and version 3. + .. Do *NOT* add changelog entries here! @@ -16,65 +18,108 @@ in our repository. .. towncrier release notes start -Version 3.0.0-dev.4 -=================== +Version 3.0.0 +============= -:Released: 2022-12-18 -:Maintainer: +:Released: 2023-03-19 +:Maintainer: Tom Schraitle Bug Fixes --------- +* :gh:`291`: Disallow negative numbers in VersionInfo arguments + for ``major``, ``minor``, and ``patch``. + +* :gh:`310`: Rework API documentation. + Follow a more "semi-manual" attempt and add auto directives + into :file:`docs/api.rst`. + +* :gh:`344`: Allow empty string, a string with a prefix, or ``None`` + as token in + :meth:`~semver.version.Version.bump_build` and + :meth:`~semver.version.Version.bump_prerelease`. + * :gh:`374`: Correct Towncrier's config entries in the :file:`pyproject.toml` file. The old entries ``[[tool.towncrier.type]]`` are deprecated and need to be replaced by ``[tool.towncrier.fragment.]``. +* :pr:`384`: General cleanup, reformat files: + * Reformat source code with black again as some config options + did accidentely exclude the semver source code. + Mostly remove some includes/excludes in the black config. + * Integrate concurrency in GH Action + * Ignore Python files on project dirs in .gitignore + * Remove unused patterns in MANIFEST.in + * Use ``extend-exclude`` for flake in :file:`setup.cfg`` and adapt list. + * Use ``skip_install=True`` in :file:`tox.ini` for black -Deprecations ------------- +* :pr:`393`: Fix command :command:`python -m semver` to avoid the error "invalid choice" -* :gh:`372`: Deprecate support for Python 3.6. +* :pr:`396`: Calling :meth:`~semver.version.Version.parse` on a derived class will show correct type of derived class. - Python 3.6 reached its end of life and isn't supported anymore. - At the time of writing (Dec 2022), the lowest version is 3.7. - Although the `poll `_ - didn't cast many votes, the majority agree to remove support for - Python 3.6. +Deprecations +------------ +* :gh:`169`: Deprecate CLI functions not imported from ``semver.cli``. +* :gh:`234`: In :file:`setup.py` simplified file and remove + ``Tox`` and ``Clean`` classes -Improved Documentation ----------------------- +* :gh:`284`: Deprecate the use of :meth:`~Version.isvalid`. -* :gh:`335`: Add new section "Converting versions between PyPI and semver" the limitations - and possible use cases to convert from one into the other versioning scheme. + Rename :meth:`~semver.version.Version.isvalid` + to :meth:`~semver.version.Version.is_valid` + for consistency reasons with :meth:`~semver.version.Version.is_compatible`. -* :gh:`340`: Describe how to get version from a file -* :gh:`343`: Describe combining Pydantic with semver in the "Advanced topic" - section. +* :pr:`290`: For semver 3.0.0-alpha0 deprecated: -* :gh:`350`: Restructure usage section. Create subdirectory "usage/" and splitted - all section into different files. + * Remove anything related to Python2 + * In :file:`tox.ini` and :file:`.travis.yml` + Remove targets py27, py34, py35, and pypy. + Add py38, py39, and nightly (allow to fail) + * In :file:`setup.py` simplified file and remove + ``Tox`` and ``Clean`` classes + * Remove old Python versions (2.7, 3.4, 3.5, and pypy) + from Travis -* :gh:`351`: Introduce new topics for: +* :gh:`372`: Deprecate support for Python 3.6. - * "Migration to semver3" - * "Advanced topics" + Python 3.6 reached its end of life and isn't supported anymore. + At the time of writing (Dec 2022), the lowest version is 3.7. + Although the `poll `_ + didn't cast many votes, the majority agreed to remove support for + Python 3.6. Features -------- -* :pr:`359`: Add optional parameter ``optional_minor_and_patch`` in :meth:`.Version.parse` to allow optional +* :gh:`169`: Create semver package and split code among different modules in the packages: + + * Remove :file:`semver.py` + * Create :file:`src/semver/__init__.py` + * Create :file:`src/semver/cli.py` for all CLI methods + * 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/__about__.py` for all the metadata variables + +* :gh:`213`: Add typing information + +* :gh:`284`: Implement :meth:`~semver.version.Version.is_compatible` to make "is self compatible with X". + +* :gh:`305`: Rename :class:`~semver.version.VersionInfo` to :class:`~semver.version.Version` but keep an alias for compatibility + +* :pr:`359`: Add optional parameter ``optional_minor_and_patch`` in :meth:`~semver.version.Version.parse` to allow optional minor and patch parts. -* :pr:`362`: Make :meth:`.Version.match` accept a bare version string as match expression, defaulting to - equality testing. +* :pr:`362`: Make :meth:`~semver.version.Version.match` accept a bare version string as match expression, defaulting to equality testing. * :gh:`364`: Enhance :file:`pyproject.toml` to make it possible to use the :command:`pyproject-build` command from the build module. @@ -95,34 +140,28 @@ Features -Trivial/Internal Changes ------------------------- - -* :gh:`378`: Fix some typos in Towncrier configuration - - - ----- - - -Version 3.0.0-dev.3 -=================== +Improved Documentation +---------------------- -:Released: 2022-01-19 -:Maintainer: Tom Schraitle +* :gh:`276`: Document how to create a sublass from :class:`~semver.version.VersionInfo` class +* :gh:`284`: Document deprecation of :meth:`~semver.version.Version.isvalid`. -Bug Fixes ---------- +* :pr:`290`: Several improvements in the documentation: -* :gh:`310`: Rework API documentation. - Follow a more "semi-manual" attempt and add auto directives - into :file:`docs/api.rst`. + * New layout to distinguish from the semver2 development line. + * Create new logo. + * Remove any occurances of Python2. + * Describe changelog process with Towncrier. + * Update the release process. +* :gh:`304`: Several improvements in documentation: + * Reorganize API documentation. + * Add migration chapter from semver2 to semver3. + * Distinguish between changlog for version 2 and 3 -Improved Documentation ----------------------- +* :gh:`305`: Add note about :class:`~semver.version.Version` rename. * :gh:`312`: Rework "Usage" section. @@ -130,109 +169,31 @@ 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 * :gh:`315`: Improve release procedure text +* :gh:`335`: Add new section "Converting versions between PyPI and semver" the limitations + and possible use cases to convert from one into the other versioning scheme. +* :gh:`340`: Describe how to get version from a file -Trivial/Internal Changes ------------------------- - -* :gh:`309`: Some (private) functions from the :mod:`semver.version` - module has been changed. - - The following functions got renamed: - - * function ``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 - :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`` - -* :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. - - 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 - 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 - speed up things when some checks fails. - -* :gh:`322`: Switch from Travis CI to GitHub Actions. - -* :gh:`347`: Support Python 3.10 in GitHub Action and other config files. - - - ----- - - -Version 3.0.0-dev.2 -=================== - -:Released: 2020-11-01 -:Maintainer: Tom Schraitle - - -Deprecations ------------- - -* :gh:`169`: Deprecate CLI functions not imported from ``semver.cli``. - - - -Features --------- - -* :gh:`169`: Create semver package and split code among different modules in the packages. - - * Remove :file:`semver.py` - * Create :file:`src/semver/__init__.py` - * Create :file:`src/semver/cli.py` for all CLI methods - * 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/__about__.py` for all the metadata variables - -* :gh:`305`: Rename :class:`VersionInfo` to :class:`Version` but keep an alias for compatibility - - - -Improved Documentation ----------------------- +* :gh:`343`: Describe combining Pydantic with semver in the "Advanced topic" + section. -* :gh:`304`: Several improvements in documentation: +* :gh:`350`: Restructure usage section. Create subdirectory "usage/" and splitted + all section into different files. - * Reorganize API documentation. - * Add migration chapter from semver2 to semver3. - * Distinguish between changlog for version 2 and 3 +* :gh:`351`: Introduce new topics for: -* :gh:`305`: Add note about :class:`Version` rename. + * "Migration to semver3" + * "Advanced topics" +* :pr:`392`: Fix the example in the documentation for combining semver and pydantic. Trivial/Internal Changes @@ -247,6 +208,8 @@ Trivial/Internal Changes Increase coverage to 100% for all non-deprecated APIs +* :pr:`290`: Add supported Python versions to :command:`black`. + * :gh:`304`: Support PEP-561 :file:`py.typed`. According to the mentioned PEP: @@ -258,96 +221,61 @@ Trivial/Internal Changes Add package_data to :file:`setup.cfg` to include this marker in dist and whl file. +* :gh:`309`: Some (private) functions from the :mod:`semver.version` + module has been changed. + The following functions got renamed: ----- - - -Version 3.0.0-dev.1 -=================== - -:Released: 2020-10-26 -:Maintainer: Tom Schraitle - - -Deprecations ------------- - -* :pr:`290`: For semver 3.0.0-alpha0: - - * Remove anything related to Python2 - * In :file:`tox.ini` and :file:`.travis.yml` - Remove targets py27, py34, py35, and pypy. - Add py38, py39, and nightly (allow to fail) - * In :file:`setup.py` simplified file and remove - ``Tox`` and ``Clean`` classes - * Remove old Python versions (2.7, 3.4, 3.5, and pypy) - from Travis - -* :gh:`234`: In :file:`setup.py` simplified file and remove - ``Tox`` and ``Clean`` classes - - + * 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 :func:`semver.version.cmp` got renamed to + :func:`semver.version._cmp` as it is only useful + inside the :class:`~semver.version.Version` class. -Features --------- + The following functions got integrated into the + :class:`~semver.version.Version` class: -* :pr:`290`: Create semver 3.0.0-alpha0 - - * Update :file:`README.rst`, mention maintenance - branch ``maint/v2``. - * Remove old code mainly used for Python2 compatibility, - adjusted code to support Python3 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) - -* :gh:`270`: Configure Towncrier (:pr:`273`:) - - * Add :file:`changelog.d/.gitignore` to keep this directory - * Create :file:`changelog.d/README.rst` with some descriptions - * Add :file:`changelog.d/_template.rst` as Towncrier template - * Add ``[tool.towncrier]`` section in :file:`pyproject.toml` - * Add "changelog" target into :file:`tox.ini`. Use it like - :command:`tox -e changelog -- CMD` whereas ``CMD`` is a - Towncrier command. The default :command:`tox -e changelog` - calls Towncrier to create a draft of the changelog file - and output it to stdout. - * 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 + * function :func:`semver.version._nat_cmd` as a classmethod + * function :func:`semver.version.ensure_str` -* :gh:`213`: Add typing information +* :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. -Bug Fixes ---------- + 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 + not implemented with respect to the other type". -* :gh:`291`: Disallow negative numbers in VersionInfo arguments - for ``major``, ``minor``, and ``patch``. + .. _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 + speed up things when some checks fails. +* :gh:`322`: Switch from Travis CI to GitHub Actions. -Improved Documentation ----------------------- +* :gh:`347`: Support Python 3.10 in GitHub Action and other config files. -* :pr:`290`: Several improvements in the documentation: +* :gh:`378`: Fix some typos in Towncrier configuration - * New layout to distinguish from the semver2 development line. - * Create new logo. - * Remove any occurances of Python2. - * Describe changelog process with Towncrier. - * Update the release process. +* :gh:`388`: For pytest, switch to the more modern :mod:`importlib` approach + as it doesn't require to modify :data:`sys.path`: + https://docs.pytest.org/en/7.2.x/explanation/pythonpath.html +* :pr:`389`: Add public class variable :data:`Version.NAMES `. + This class variable contains a tuple of strings that contains the names of + all attributes of a Version (like ``"major"``, ``"minor"`` etc). -Trivial/Internal Changes ------------------------- + In cases we need to have dynamical values, this makes it easier to iterate. -* :pr:`290`: Add supported Python versions to :command:`black`. .. diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index fe531990..ce598e01 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -8,6 +8,7 @@ The semver source code is managed using Git and is hosted on GitHub:: git clone git://github.com/python-semver/python-semver + Reporting Bugs and Asking Questions ----------------------------------- @@ -34,193 +35,14 @@ consider the following general requirements: If not, ask on its GitHub project https://github.com/semver/semver. +More topics +----------- -Modifying the Code ------------------- - -We recommend the following workflow: - -#. Fork our project on GitHub using this link: - https://github.com/python-semver/python-semver/fork - -#. Clone your forked Git repository (replace ``GITHUB_USER`` with your - account name on GitHub):: - - $ git clone git@github.com:GITHUB_USER/python-semver.git - -#. Create a new branch. You can name your branch whatever you like, but we - recommend to use some meaningful name. If your fix is based on a - existing GitHub issue, add also the number. Good examples would be: - - * ``feature/123-improve-foo`` when implementing a new feature in issue 123 - * ``bugfix/234-fix-security-bar`` a bugfixes for issue 234 - - Use this :command:`git` command:: - - $ git checkout -b feature/NAME_OF_YOUR_FEATURE - -#. Work on your branch and create a pull request: - - a. Write test cases and run the complete test suite, see :ref:`testsuite` - for details. - - b. Write a changelog entry, see section :ref:`add-changelog`. - - c. If you have implemented a new feature, document it into our - documentation to help our reader. See section :ref:`doc` for - further details. - - d. Create a `pull request`_. Describe in the pull request what you did - and why. If you have open questions, ask. - -#. Wait for feedback. If you receive any comments, address these. - -#. After your pull request got accepted, delete your branch. - - -.. _testsuite: - -Running the Test Suite ----------------------- - -We use `pytest`_ and `tox`_ to run tests against all supported Python -versions. All test dependencies are resolved automatically. - -You can decide to run the complete test suite or only part of it: - -* To run all tests, use:: - - $ tox - - If you have not all Python interpreters installed on your system - it will probably give you some errors (``InterpreterNotFound``). - To avoid such errors, use:: - - $ tox --skip-missing-interpreters - - It is possible to use one or more specific Python versions. Use the ``-e`` - option and one or more abbreviations (``py37`` for Python 3.7, - ``py38`` for Python 3.8 etc.):: - - $ tox -e py37 - $ tox -e py37,py38 - - To get a complete list and a short description, run:: - - $ tox -av - -* To run only a specific test, pytest requires the syntax - ``TEST_FILE::TEST_FUNCTION``. - - For example, the following line tests only the function - :func:`test_immutable_major` in the file :file:`test_bump.py` for all - Python versions:: - - $ tox -e py37 -- tests/test_bump.py::test_should_bump_major - - By default, pytest prints only a dot for each test function. To - reveal the executed test function, use the following syntax:: - - $ tox -- -v - - You can combine the specific test function with the ``-e`` option, for - example, to limit the tests for Python 3.7 and 3.8 only:: - - $ tox -e py37,py38 -- tests/test_bump.py::test_should_bump_major - -Our code is checked against formatting, style, type, and docstring issues -(`black`_, `flake8`_, `mypy`_, and `docformatter`_). -It is recommended to run your tests in combination with :command:`checks`, -for example:: - - $ tox -e checks,py37,py38 - - -.. _doc: - -Documenting semver ------------------- - -Documenting the features of semver is very important. It gives our developers -an overview what is possible with semver, how it "feels", and how it is -used efficiently. - -.. note:: - - To build the documentation locally use the following command:: - - $ tox -e docs - - The built documentation is available in :file:`docs/_build/html`. - - -A new feature is *not* complete if it isn't proberly documented. A good -documentation includes: - - * **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:: - - def to_tuple(self) -> VersionTuple: - """ - Convert the Version object to a tuple. - - .. versionadded:: 2.10.0 - Renamed ``VersionInfo._astuple`` to ``VersionInfo.to_tuple`` to - make this function available in the public API. - - :return: a tuple with all the parts - - >>> semver.Version(5, 3, 1).to_tuple() - (5, 3, 1, None, None) - """ - - * **An optional directive** - - If you introduce a new feature, change a function/method, or remove something, - it is a good practice to introduce Sphinx directives into the docstring. - This gives the reader an idea what version is affected by this change. - - The first required argument, ``VERSION``, defines the version when this change - was introduced. You can choose from: - - * ``.. versionadded:: VERSION`` - - Use this directive to describe a new feature. - - * ``.. versionchanged:: VERSION`` - - Use this directive to describe when something has changed, for example, - new parameters were added, changed side effects, different return values, etc. - - * ``.. deprecated:: VERSION`` - - Use this directive when a feature is deprecated. Describe what should - be used instead, if appropriate. - - - Add such a directive *after* the summary line, as shown above. - - * **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. - - -.. _add-changelog: - -Adding a Changelog Entry ------------------------- - -.. include:: ../changelog.d/README.rst - :start-after: -text-begin- +* `Running the Test Suite `_ +* `Documenting semver `_ +* `Adding a Changelog Entry `_ +* `Preparing the Release `_ +* `Finish the Release `_ .. _black: https://black.rtfd.io diff --git a/CONTRIBUTORS b/CONTRIBUTORS index e5fef99b..0d90ab3a 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -22,30 +22,37 @@ Old maintainer: * Kostiantyn Rybnikov -Significant contributors -======================== +List of Contributors +==================== -* Alexander Puzynia -* Alexander Shorin -* Anton Talevnin -* Ben Finney +(in alphabetical order) + +* Jelo Agnasin * Carles Barrobés -* Craig Blaszczyk -* Damien Nadé * Eli Bishop -* George Sakkis -* Jan Pieter Waagmeester -* Jelo Agnasin -* Karol Werner * Peter Bittner -* robi-wan -* sbrudenell +* Craig Blaszczyk +* Tyler Cross +* Dennis Felsing +* Ben Finney +* Zane Geiger * T. Jameson Little +* Raphael Krupinski * Thomas Laferriere -* Tuure Laurinolli -* Tyler Cross * Zack Lalanne - +* Tuure Laurinolli +* Damien Nadé +* Jan Pieter Waagmeester +* Alexander Puzynia +* Lexi Robinson +* robi-wan +* George Sakkis +* Mike Salvatore +* sbrudenell +* Alexander Shorin +* Anton Talevnin +* Karol Werner + .. Local variables: coding: utf-8 diff --git a/README.rst b/README.rst index cad99a04..ede10a18 100644 --- a/README.rst +++ b/README.rst @@ -1,8 +1,3 @@ -.. warning:: - - This is a development version. Do **NOT** use it - in production before the final 3.0.0 is released. - Quickstart ========== diff --git a/changelog.d/pr384.bugfix.rst b/changelog.d/pr384.bugfix.rst deleted file mode 100644 index ca0b08d0..00000000 --- a/changelog.d/pr384.bugfix.rst +++ /dev/null @@ -1,11 +0,0 @@ -General cleanup, reformat files: - -* Reformat source code with black again as some config options - did accidentely exclude the semver source code. - Mostly remove some includes/excludes in the black config. -* Integrate concurrency in GH Action -* Ignore Python files on project dirs in .gitignore -* Remove unused patterns in MANIFEST.in -* Use ``extend-exclude`` for flake in :file:`setup.cfg`` and adapt list. -* Use ``skip_install=True`` in :file:`tox.ini` for black - diff --git a/docs/advanced/combine-pydantic-and-semver.rst b/docs/advanced/combine-pydantic-and-semver.rst index a9249daf..a00c2cff 100644 --- a/docs/advanced/combine-pydantic-and-semver.rst +++ b/docs/advanced/combine-pydantic-and-semver.rst @@ -17,10 +17,14 @@ To work with Pydantic, use the following steps: from semver import Version class PydanticVersion(Version): + @classmethod + def _parse(cls, version): + return cls.parse(version) + @classmethod def __get_validators__(cls): """Return a list of validator methods for pydantic models.""" - yield cls.parse + yield cls._parse @classmethod def __modify_schema__(cls, field_schema): diff --git a/docs/advanced/convert-pypi-to-semver.rst b/docs/advanced/convert-pypi-to-semver.rst index 76653ceb..04737d29 100644 --- a/docs/advanced/convert-pypi-to-semver.rst +++ b/docs/advanced/convert-pypi-to-semver.rst @@ -135,7 +135,7 @@ semver: def convert2semver(ver: packaging.version.Version) -> semver.Version: """Converts a PyPI version into a semver version - :param packaging.version.Version ver: the PyPI version + :param ver: the PyPI version :return: a semver version :raises ValueError: if epoch or post parts are used """ @@ -145,7 +145,7 @@ semver: raise ValueError("Can't convert a post part to semver") pre = None if not ver.pre else "".join([str(i) for i in ver.pre]) - semver.Version(*ver.release, prerelease=pre, build=ver.dev) + return semver.Version(*ver.release, prerelease=pre, build=ver.dev) .. _convert_semver_to_pypi: diff --git a/docs/advanced/create-subclasses-from-version.rst b/docs/advanced/create-subclasses-from-version.rst index 7c97ee6f..7e99e217 100644 --- a/docs/advanced/create-subclasses-from-version.rst +++ b/docs/advanced/create-subclasses-from-version.rst @@ -16,7 +16,8 @@ but the other behavior is the same, use the following code: The derived class :class:`SemVerWithVPrefix` can be used like -the original class: +the original class. Additionally, you can pass "incomplete" +version strings like ``v2.3``: .. code-block:: python @@ -24,7 +25,7 @@ the original class: >>> assert str(v1) == "v1.2.3" >>> print(v1) v1.2.3 - >>> v2 = SemVerWithVPrefix.parse("v2.3.4") + >>> v2 = SemVerWithVPrefix.parse("v2.3") >>> v2 > v1 True >>> bad = SemVerWithVPrefix.parse("1.2.4") diff --git a/docs/advanced/index.rst b/docs/advanced/index.rst index 8a45d361..47c23b9d 100644 --- a/docs/advanced/index.rst +++ b/docs/advanced/index.rst @@ -3,6 +3,7 @@ Advanced topics .. toctree:: + :maxdepth: 1 deal-with-invalid-versions create-subclasses-from-version diff --git a/docs/advanced/semverwithvprefix.py b/docs/advanced/semverwithvprefix.py index f2a7fecd..7e411d35 100644 --- a/docs/advanced/semverwithvprefix.py +++ b/docs/advanced/semverwithvprefix.py @@ -20,7 +20,7 @@ def parse(cls, version: str) -> "SemVerWithVPrefix": f"{version!r}: not a valid semantic version tag. " "Must start with 'v' or 'V'" ) - return super().parse(version[1:]) + return super().parse(version[1:], optional_minor_and_patch=True) def __str__(self) -> str: # Reconstruct the tag diff --git a/docs/advanced/version-from-file.rst b/docs/advanced/version-from-file.rst index 6dc9bb48..b49ff36b 100644 --- a/docs/advanced/version-from-file.rst +++ b/docs/advanced/version-from-file.rst @@ -8,16 +8,17 @@ is to use the following function: .. code-block:: python + import os + from typing import Union from semver.version import Version - def get_version(path: str = "version") -> Version: + def get_version(path: Union[str, os.PathLike]) -> semver.Version: """ - Construct a Version from a file + Construct a Version object from a file :param path: A text file only containing the semantic version :return: A :class:`Version` object containing the semantic version from the file. """ - with open(path,"r") as fh: - version = fh.read().strip() + version = open(path,"r").read().strip() return Version.parse(version) diff --git a/docs/api.rst b/docs/api.rst index 196e30a9..279df408 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -17,8 +17,37 @@ Deprecated Functions in :mod:`semver._deprecated` .. automodule:: semver._deprecated +.. autofunction:: semver._deprecated.bump_build + +.. autofunction:: semver._deprecated.bump_major + +.. autofunction:: semver._deprecated.bump_minor + +.. autofunction:: semver._deprecated.bump_patch + +.. autofunction:: semver._deprecated.bump_prerelease + +.. autofunction:: semver._deprecated.compare + .. autofunction:: semver._deprecated.deprecated +.. autofunction:: semver._deprecated.finalize_version + +.. autofunction:: semver._deprecated.format_version + +.. autofunction:: semver._deprecated.match + +.. autofunction:: semver._deprecated.max_ver + +.. autofunction:: semver._deprecated.min_ver + +.. autofunction:: semver._deprecated.parse + +.. autofunction:: semver._deprecated.parse_version_info + +.. autofunction:: semver._deprecated.replace + + CLI Parsing :mod:`semver.cli` ----------------------------- diff --git a/BUILDING.rst b/docs/build-semver.rst similarity index 99% rename from BUILDING.rst rename to docs/build-semver.rst index 61f3d4cb..c938a1ee 100644 --- a/BUILDING.rst +++ b/docs/build-semver.rst @@ -3,6 +3,7 @@ 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/ diff --git a/docs/build.rst b/docs/build.rst deleted file mode 100644 index ba0c84a4..00000000 --- a/docs/build.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../BUILDING.rst diff --git a/docs/changelog-semver3-devel.rst b/docs/changelog-semver3-devel.rst new file mode 100644 index 00000000..2d40635d --- /dev/null +++ b/docs/changelog-semver3-devel.rst @@ -0,0 +1,367 @@ + +############################# +Changelog semver3 development +############################# + +This site contains all the changes during the development phase. + +.. _semver-3.0.0-dev.4: + +Version 3.0.0-dev.4 +=================== + +:Released: 2022-12-18 +:Maintainer: + + +.. _semver-3.0.0-dev.4-bugfixes: + +Bug Fixes +--------- + +* :gh:`374`: Correct Towncrier's config entries in the :file:`pyproject.toml` file. + The old entries ``[[tool.towncrier.type]]`` are deprecated and need + to be replaced by ``[tool.towncrier.fragment.]``. + + + +.. _semver-3.0.0-dev.4-deprecations: + +Deprecations +------------ + +* :gh:`372`: Deprecate support for Python 3.6. + + Python 3.6 reached its end of life and isn't supported anymore. + At the time of writing (Dec 2022), the lowest version is 3.7. + + Although the `poll `_ + didn't cast many votes, the majority agree to remove support for + Python 3.6. + + +.. _semver-3.0.0-dev.4-doc: + +Improved Documentation +---------------------- + +* :gh:`335`: Add new section "Converting versions between PyPI and semver" the limitations + and possible use cases to convert from one into the other versioning scheme. + +* :gh:`340`: Describe how to get version from a file + +* :gh:`343`: Describe combining Pydantic with semver in the "Advanced topic" + section. + +* :gh:`350`: Restructure usage section. Create subdirectory "usage/" and splitted + all section into different files. + +* :gh:`351`: Introduce new topics for: + + * "Migration to semver3" + * "Advanced topics" + + +.. _semver-3.0.0-dev.4-features: + +Features +-------- + +* :pr:`359`: Add optional parameter ``optional_minor_and_patch`` in :meth:`.Version.parse` to allow optional + minor and patch parts. + +* :pr:`362`: Make :meth:`.Version.match` accept a bare version string as match expression, defaulting to + equality testing. + +* :gh:`364`: Enhance :file:`pyproject.toml` to make it possible to use the + :command:`pyproject-build` command from the build module. + For more information, see :ref:`build-semver`. + +* :gh:`365`: Improve :file:`pyproject.toml`. + + * Use setuptools, add metadata. Taken approach from + `A Practical Guide to Setuptools and Pyproject.toml + `_. + * Doc: Describe building of semver + * Remove :file:`.travis.yml` in :file:`MANIFEST.in` + (not needed anymore) + * Distinguish between Python 3.6 and others in :file:`tox.ini` + * Add skip_missing_interpreters option for :file:`tox.ini` + * GH Action: Upgrade setuptools and setuptools-scm and test + against 3.11.0-rc.2 + + +.. _semver-3.0.0-dev.4-internal: + +Trivial/Internal Changes +------------------------ + +* :gh:`378`: Fix some typos in Towncrier configuration + + + +---- + +.. _semver-3.0.0-dev.3: + +Version 3.0.0-dev.3 +=================== + +:Released: 2022-01-19 +:Maintainer: Tom Schraitle + + +.. _semver-3.0.0-dev.3-bugfixes: + +Bug Fixes +--------- + +* :gh:`310`: Rework API documentation. + Follow a more "semi-manual" attempt and add auto directives + into :file:`docs/api.rst`. + + +.. _semver-3.0.0-dev.3-docs: + +Improved Documentation +---------------------- + +* :gh:`312`: Rework "Usage" section. + + * Mention the rename of :class:`~semver.version.VersionInfo` to + :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. + * Remove inconsistencies and mention module level function as + deprecated and discouraged from using + * Make empty :py:func:`super` call in :file:`semverwithvprefix.py` example + +* :gh:`315`: Improve release procedure text + + +.. _semver-3.0.0-dev.3-trivial: + +Trivial/Internal Changes +------------------------ + +* :gh:`309`: Some (private) functions from the :mod:`semver.version` + module has been changed. + + The following functions got renamed: + + * function ``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 + :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`` + +* :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. + + 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 + 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 + speed up things when some checks fails. + +* :gh:`322`: Switch from Travis CI to GitHub Actions. + +* :gh:`347`: Support Python 3.10 in GitHub Action and other config files. + + + +---- + +.. _semver-3.0.0-dev.2: + +Version 3.0.0-dev.2 +=================== + +:Released: 2020-11-01 +:Maintainer: Tom Schraitle + + +.. _semver-3.0.0-dev.2-deprecations: + +Deprecations +------------ + +* :gh:`169`: Deprecate CLI functions not imported from ``semver.cli``. + + +.. _semver-3.0.0-dev.2-features: + +Features +-------- + +* :gh:`169`: Create semver package and split code among different modules in the packages. + + * Remove :file:`semver.py` + * Create :file:`src/semver/__init__.py` + * Create :file:`src/semver/cli.py` for all CLI methods + * 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/__about__.py` for all the metadata variables + +* :gh:`305`: Rename :class:`VersionInfo` to :class:`Version` but keep an alias for compatibility + + +.. _semver-3.0.0-dev.2-docs: + +Improved Documentation +---------------------- + +* :gh:`304`: Several improvements in documentation: + + * Reorganize API documentation. + * Add migration chapter from semver2 to semver3. + * Distinguish between changlog for version 2 and 3 + +* :gh:`305`: Add note about :class:`Version` rename. + + +.. _semver-3.0.0-dev.2-trivial: + +Trivial/Internal Changes +------------------------ + +* :gh:`169`: Adapted infrastructure code to the new project layout. + + * Replace :file:`setup.py` with :file:`setup.cfg` because the :file:`setup.cfg` is easier to use + * Adapt documentation code snippets where needed + * Adapt tests + * Changed the ``deprecated`` to hardcode the ``semver`` package name in the warning. + + Increase coverage to 100% for all non-deprecated APIs + +* :gh:`304`: Support PEP-561 :file:`py.typed`. + + According to the mentioned PEP: + + "Package maintainers who wish to support type checking + of their code MUST add a marker file named :file:`py.typed` + to their package supporting typing." + + Add package_data to :file:`setup.cfg` to include this marker in dist + and whl file. + + + +---- + +.. _semver-3.0.0-dev.1: + +Version 3.0.0-dev.1 +=================== + +:Released: 2020-10-26 +:Maintainer: Tom Schraitle + + +.. _semver-3.0.0-dev.1-deprecations: + +Deprecations +------------ + +* :pr:`290`: For semver 3.0.0-alpha0: + + * Remove anything related to Python2 + * In :file:`tox.ini` and :file:`.travis.yml` + Remove targets py27, py34, py35, and pypy. + Add py38, py39, and nightly (allow to fail) + * In :file:`setup.py` simplified file and remove + ``Tox`` and ``Clean`` classes + * Remove old Python versions (2.7, 3.4, 3.5, and pypy) + from Travis + +* :gh:`234`: In :file:`setup.py` simplified file and remove + ``Tox`` and ``Clean`` classes + + +.. _semver-3.0.0-dev.1-features: + +Features +-------- + +* :pr:`290`: Create semver 3.0.0-alpha0 + + * Update :file:`README.rst`, mention maintenance + branch ``maint/v2``. + * Remove old code mainly used for Python2 compatibility, + adjusted code to support Python3 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) + +* :gh:`270`: Configure Towncrier (:pr:`273`:) + + * Add :file:`changelog.d/.gitignore` to keep this directory + * Create :file:`changelog.d/README.rst` with some descriptions + * Add :file:`changelog.d/_template.rst` as Towncrier template + * Add ``[tool.towncrier]`` section in :file:`pyproject.toml` + * Add "changelog" target into :file:`tox.ini`. Use it like + :command:`tox -e changelog -- CMD` whereas ``CMD`` is a + Towncrier command. The default :command:`tox -e changelog` + calls Towncrier to create a draft of the changelog file + and output it to stdout. + * 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:`213`: Add typing information + + +.. _semver-3.0.0-dev.1-bugfixes: + +Bug Fixes +--------- + +* :gh:`291`: Disallow negative numbers in VersionInfo arguments + for ``major``, ``minor``, and ``patch``. + + +.. _semver-3.0.0-dev.1-docs: + +Improved Documentation +---------------------- + +* :pr:`290`: Several improvements in the documentation: + + * New layout to distinguish from the semver2 development line. + * Create new logo. + * Remove any occurances of Python2. + * Describe changelog process with Towncrier. + * Update the release process. + + +.. _semver-3.0.0-dev.1-trivial: + +Trivial/Internal Changes +------------------------ + +* :pr:`290`: Add supported Python versions to :command:`black`. diff --git a/docs/conf.py b/docs/conf.py index ed888361..9edfda4d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -118,8 +118,8 @@ def find_version(*file_paths): # Markup to shorten external links # See https://www.sphinx-doc.org/en/master/usage/extensions/extlinks.html extlinks = { - "gh": ("https://github.com/python-semver/python-semver/issues/%s", "#"), - "pr": ("https://github.com/python-semver/python-semver/pull/%s", "PR #"), + "gh": ("https://github.com/python-semver/python-semver/issues/%s", "#%s"), + "pr": ("https://github.com/python-semver/python-semver/pull/%s", "PR #%s"), } # -- Options for HTML output ---------------------------------------------- diff --git a/docs/contribute/add-changelog-entry.rst b/docs/contribute/add-changelog-entry.rst new file mode 100644 index 00000000..c0d426a8 --- /dev/null +++ b/docs/contribute/add-changelog-entry.rst @@ -0,0 +1,7 @@ +.. _add-changelog: + +Adding a Changelog Entry +======================== + +.. include:: ../../changelog.d/README.rst + :start-after: -text-begin- \ No newline at end of file diff --git a/docs/contribute/doc-semver.rst b/docs/contribute/doc-semver.rst new file mode 100644 index 00000000..fcc6c1ac --- /dev/null +++ b/docs/contribute/doc-semver.rst @@ -0,0 +1,80 @@ +.. _doc: + +Documenting semver +================== + +Documenting the features of semver is very important. It gives our developers +an overview what is possible with semver, how it "feels", and how it is +used efficiently. + +.. note:: + + To build the documentation locally use the following command:: + + $ tox -e docs + + The built documentation is available in :file:`docs/_build/html`. + + +A new feature is *not* complete if it isn't proberly documented. A good +documentation includes: + + * **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:: + + def to_tuple(self) -> VersionTuple: + """ + Convert the Version object to a tuple. + + .. versionadded:: 2.10.0 + Renamed ``VersionInfo._astuple`` to ``VersionInfo.to_tuple`` to + make this function available in the public API. + + :return: a tuple with all the parts + + >>> semver.Version(5, 3, 1).to_tuple() + (5, 3, 1, None, None) + + """ + + * **An optional directive** + + If you introduce a new feature, change a function/method, or remove something, + it is a good practice to introduce Sphinx directives into the docstring. + This gives the reader an idea what version is affected by this change. + + The first required argument, ``VERSION``, defines the version when this change + was introduced. You can choose from: + + * ``.. versionadded:: VERSION`` + + Use this directive to describe a new feature. + + * ``.. versionchanged:: VERSION`` + + Use this directive to describe when something has changed, for example, + new parameters were added, changed side effects, different return values, etc. + + * ``.. deprecated:: VERSION`` + + Use this directive when a feature is deprecated. Describe what should + be used instead, if appropriate. + + + Add such a directive *after* the summary line, as shown above. + + * **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. + + +.. _Sphinx style: https://sphinx-rtd-tutorial.rtfd.io/en/latest/docstrings.html diff --git a/docs/contribute/finish-release.rst b/docs/contribute/finish-release.rst new file mode 100644 index 00000000..947fcf96 --- /dev/null +++ b/docs/contribute/finish-release.rst @@ -0,0 +1,24 @@ +.. _finish-release: + +Finish the Release +================== + +1. Create a tag: + + $ git tag -a x.x.x + + It’s recommended to use the generated Tox output from the Changelog. + +2. Push the tag: + + $ git push –tags + +3. In `GitHub Release + page `_ + document the new release. Select the tag from the last step and copy + the content of the tag description into the release description. + +4. Announce it in + https://github.com/python-semver/python-semver/discussions/categories/announcements. + +You’re done! Celebrate! diff --git a/docs/contribute/index.rst b/docs/contribute/index.rst new file mode 100644 index 00000000..f09e6418 --- /dev/null +++ b/docs/contribute/index.rst @@ -0,0 +1,28 @@ +.. _contributing: + +Contributing to semver +====================== + +The semver source code is managed using Git and is hosted on GitHub:: + + git clone git://github.com/python-semver/python-semver + + +.. include:: prerequisites.rst + :start-after: -text-begin- + + +.. toctree:: + :maxdepth: 1 + :caption: More topics + :includehidden: + + report-bugs + run-test-suite + doc-semver + add-changelog-entry + release-procedure + finish-release + + +.. _pull request: https://github.com/python-semver/python-semver/pulls diff --git a/docs/contribute/prerequisites.rst b/docs/contribute/prerequisites.rst new file mode 100644 index 00000000..79bbb571 --- /dev/null +++ b/docs/contribute/prerequisites.rst @@ -0,0 +1,15 @@ +Prerequisites +------------- + +.. -text-begin- + +Before you make changes to the code, we would highly appreciate if you +consider the following general requirements: + +* Make sure your code adheres to the `Semantic Versioning`_ specification. + +* Check if your feature is covered by the Semantic Versioning specification. + If not, ask on its GitHub project https://github.com/semver/semver. + + +.. _Semantic Versioning: https://semver.org diff --git a/docs/contribute/release-procedure.rst b/docs/contribute/release-procedure.rst new file mode 100644 index 00000000..c02148fe --- /dev/null +++ b/docs/contribute/release-procedure.rst @@ -0,0 +1,123 @@ +Release Procedure +================= + +The following procedures gives a short overview of what steps are needed +to create a new release. + +These steps are interesting for the release manager only. + + +Prepare the Release +------------------- + +1. Verify that: + + - all issues for a new release are closed: + https://github.com/python-semver/python-semver/issues. + + - all pull requests that should be included in this release are + merged: https://github.com/python-semver/python-semver/pulls. + + - continuous integration for latest build was passing: + https://github.com/python-semver/python-semver/actions. + +2. Create a new branch ``release/``. + +3. If one or several supported Python versions have been removed or + added, verify that the following files have been updated: + + - :file:`setup.cfg` + - :file:`tox.ini` + - :file:`.git/workflows/pythonpackage.yml` + - :file:`.github/workflows/python-testing.yml` + +4. Verify that the version in file :file:`src/semver/__about__.py` + has been updated and follows the `Semver `_ + specification. + +5. Add eventually new contributor(s) to + `CONTRIBUTORS `_. + +6. Check if all changelog entries are created. If some are missing, + `create + them `__. + +7. Show the new draft + `CHANGELOG `_ entry for the latest release with: + + :: + + $ tox -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: + + :: + + $ tox -e changelog -- build + +8. Build the documentation and check the output: + + :: + + $ tox -e docs + +9. Commit all changes, push, and create a pull request. + +Create the New Release +---------------------- + +1. Ensure that long description + (`README.rst `_) + can be correctly rendered by Pypi using + ``restview --long-description`` + +2. Clean up your local Git repository. Be careful, as it **will remove + all files** which are not versioned by Git: + + :: + + $ git clean -xfd + + Before you create your distribution files, clean the directory too: + + :: + + $ rm dist/* + +3. Create the distribution files (wheel and source): + + :: + + $ tox -e prepare-dist + +4. Upload the wheel and source to TestPyPI first: + + .. code:: bash + + $ twine upload --repository-url https://test.pypi.org/legacy/ dist/* + + If you have a ``~/.pypirc`` with a ``testpypi`` section, the upload + can be simplified: + + :: + + $ twine upload --repository testpypi dist/* + +5. Check if everything is okay with the wheel. Check also the web site + ``https://test.pypi.org/project//`` + +6. If everything looks fine, merge the pull request. + +7. Upload to PyPI: + + .. code:: bash + + $ git clean -xfd + $ tox -e prepare-dist + $ twine upload dist/* + +8. Go to https://pypi.org/project/semver/ to verify that new version is + online and the page is rendered correctly. + diff --git a/docs/contribute/report-bugs.rst b/docs/contribute/report-bugs.rst new file mode 100644 index 00000000..fa14eb18 --- /dev/null +++ b/docs/contribute/report-bugs.rst @@ -0,0 +1,18 @@ +.. _report-bugs: + +Reporting Bugs and Asking Questions +----------------------------------- + +If you think you have encountered a bug in semver or have an idea for a new +feature? Great! We like to hear from you! + +There are several options to participate: + +* Open a new topic on our `GitHub discussion `_ page. + Tell us our ideas or ask your questions. + +* Look into our GitHub `issues`_ tracker or open a new issue. + + +.. _issues: https://github.com/python-semver/python-semver/issues +.. _gh_discussions: https://github.com/python-semver/python-semver/discussions diff --git a/docs/contribute/run-test-suite.rst b/docs/contribute/run-test-suite.rst new file mode 100644 index 00000000..07c49fff --- /dev/null +++ b/docs/contribute/run-test-suite.rst @@ -0,0 +1,64 @@ +.. _testsuite: + +Running the Test Suite +====================== + +We use `pytest`_ and `tox`_ to run tests against all supported Python +versions. All test dependencies are resolved automatically. + +You can decide to run the complete test suite or only part of it: + +* To run all tests, use:: + + $ tox + + If you have not all Python interpreters installed on your system + it will probably give you some errors (``InterpreterNotFound``). + To avoid such errors, use:: + + $ tox --skip-missing-interpreters + + It is possible to use one or more specific Python versions. Use the ``-e`` + option and one or more abbreviations (``py37`` for Python 3.7, + ``py38`` for Python 3.8 etc.):: + + $ tox -e py37 + $ tox -e py37,py38 + + To get a complete list and a short description, run:: + + $ tox -av + +* To run only a specific test, pytest requires the syntax + ``TEST_FILE::TEST_FUNCTION``. + + For example, the following line tests only the function + :func:`test_immutable_major` in the file :file:`test_bump.py` for all + Python versions:: + + $ tox -e py37 -- tests/test_bump.py::test_should_bump_major + + By default, pytest prints only a dot for each test function. To + reveal the executed test function, use the following syntax:: + + $ tox -- -v + + You can combine the specific test function with the ``-e`` option, for + example, to limit the tests for Python 3.7 and 3.8 only:: + + $ tox -e py37,py38 -- tests/test_bump.py::test_should_bump_major + +Our code is checked against formatting, style, type, and docstring issues +(`black`_, `flake8`_, `mypy`_, and `docformatter`_). +It is recommended to run your tests in combination with :command:`checks`, +for example:: + + $ tox -e checks,py37,py38 + + +.. _black: https://black.rtfd.io +.. _docformatter: https://pypi.org/project/docformatter/ +.. _flake8: https://flake8.rtfd.io +.. _mypy: http://mypy-lang.org/ +.. _pytest: http://pytest.org/ +.. _tox: https://tox.rtfd.org/ diff --git a/docs/development.rst b/docs/development.rst deleted file mode 100644 index e582053e..00000000 --- a/docs/development.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../CONTRIBUTING.rst diff --git a/docs/index.rst b/docs/index.rst index 2dce2a50..4c9ccff7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,13 +8,14 @@ Semver |version| -- Semantic Versioning :maxdepth: 2 :caption: Contents :hidden: + :numbered: - build + build-semver install usage/index migration/index advanced/index - development + contribute/index api .. toctree:: @@ -27,7 +28,7 @@ Semver |version| -- Semantic Versioning .. toctree:: :maxdepth: 1 - :caption: Changelogs + :caption: Development :hidden: changelog diff --git a/docs/migration/migratetosemver3.rst b/docs/migration/migratetosemver3.rst index f869cad3..e8b5c9ab 100644 --- a/docs/migration/migratetosemver3.rst +++ b/docs/migration/migratetosemver3.rst @@ -3,8 +3,9 @@ Migrating from semver2 to semver3 ================================= -This document describes the visible differences for +This section describes the visible differences for users and how your code stays compatible for semver3. +Some changes are backward incompatible. Although the development team tries to make the transition to semver3 as smooth as possible, at some point change @@ -17,10 +18,11 @@ to our :ref:`change-log`. Use Version instead of VersionInfo ---------------------------------- -The :class:`VersionInfo` has been renamed to :class:`Version` -to have a more succinct name. +The :class:`~semver.version.VersionInfo` has been renamed to +:class:`~semver.version.Version` to have a more succinct name. An alias has been created to preserve compatibility but -using the old name has been deprecated. +using the old name has been deprecated and will be removed +in future versions. If you still need the old version, use this line: @@ -34,9 +36,16 @@ Use semver.cli instead of semver -------------------------------- All functions related to CLI parsing are moved to :mod:`semver.cli`. -If you are such functions, like :func:`semver.cmd_bump `, +If you need such functions, like :meth:`~semver.cli.cmd_bump`, import it from :mod:`semver.cli` in the future: .. code-block:: python from semver.cli import cmd_bump + + +Use semver.Version.is_valid instead of semver.Version.isvalid +------------------------------------------------------------- + +The pull request :pr:`284` introduced the method :meth:`~semver.version.Version.is_compatible`. To keep consistency, the development team +decided to rename the :meth:`~semver.Version.isvalid` to :meth:`~semver.Version.is_valid`. diff --git a/docs/migration/replace-deprecated-functions.rst b/docs/migration/replace-deprecated-functions.rst index 9738001c..ebe8c354 100644 --- a/docs/migration/replace-deprecated-functions.rst +++ b/docs/migration/replace-deprecated-functions.rst @@ -6,7 +6,7 @@ Replacing Deprecated Functions .. versionchanged:: 2.10.0 The development team of semver has decided to deprecate certain functions on the module level. The preferred way of using semver is through the - :class:`semver.Version` class. + :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. @@ -17,10 +17,10 @@ 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` - Replace them with the respective methods of the :class:`Version ` + Replace them with the respective methods of the :class:`~semver.version.Version` class. For example, the function :func:`semver.bump_major` is replaced by - :func:`semver.Version.bump_major` and calling the ``str(versionobject)``: + :meth:`~semver.version.Version.bump_major` and calling the ``str(versionobject)``: .. code-block:: python @@ -31,9 +31,14 @@ 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` - Replace it with :func:`semver.Version.finalize_version`: + Replace it with :func:`semver.version.Version.finalize_version`: .. code-block:: python @@ -55,7 +60,7 @@ them with code which is compatible for future versions: * :func:`semver.max_ver` - Replace it with ``max(version1, version2, ...)`` or ``max([version1, version2, ...])``: + Replace it with ``max(version1, version2, ...)`` or ``max([version1, version2, ...])`` and a ``key``: .. code-block:: python @@ -77,8 +82,8 @@ them with code which is compatible for future versions: * :func:`semver.parse` - Replace it with :func:`semver.Version.parse` and - :func:`semver.Version.to_dict`: + Replace it with :meth:`semver.version.Version.parse` and call + :meth:`semver.version.Version.to_dict`: .. code-block:: python @@ -89,7 +94,7 @@ them with code which is compatible for future versions: * :func:`semver.parse_version_info` - Replace it with :func:`semver.Version.parse`: + Replace it with :meth:`semver.version.Version.parse`: .. code-block:: python @@ -100,7 +105,7 @@ them with code which is compatible for future versions: * :func:`semver.replace` - Replace it with :func:`semver.Version.replace`: + Replace it with :meth:`semver.version.Version.replace`: .. code-block:: python diff --git a/docs/usage/access-parts-through-index.rst b/docs/usage/access-parts-through-index.rst index a261fda4..c3651a5e 100644 --- a/docs/usage/access-parts-through-index.rst +++ b/docs/usage/access-parts-through-index.rst @@ -7,7 +7,7 @@ Accessing Parts Through Index Numbers Another way to access parts of a version is to use an index notation. The underlying :class:`~semver.version.Version` object allows to access its data through -the magic method :func:`~semver.version.Version.__getitem__`. +the magic method :meth:`~semver.version.Version.__getitem__`. For example, the ``major`` part can be accessed by index number 0 (zero). Likewise the other parts: diff --git a/docs/usage/check-compatible-semver-version.rst b/docs/usage/check-compatible-semver-version.rst new file mode 100644 index 00000000..20330456 --- /dev/null +++ b/docs/usage/check-compatible-semver-version.rst @@ -0,0 +1,95 @@ +Checking for a Compatible Semver Version +======================================== + +To check if a *change* from a semver version ``a`` to a semver +version ``b`` is *compatible* according to semver rule, use the method +:meth:`~semver.version.Version.is_compatible`. + +The expression ``a.is_compatible(b) is True`` if one of the following +statements is true: + +* both versions are equal, or +* both majors are equal and higher than 0. The same applies for both + minor parts. Both pre-releases are equal, or +* both majors are equal and higher than 0. The minor of ``b``'s + minor version is higher then ``a``'s. Both pre-releases are equal. + +In all other cases, the result is false. + +Keep in mind, the method *does not* check patches! + + +* Two different majors: + + .. code-block:: python + + >>> a = Version(1, 1, 1) + >>> b = Version(2, 0, 0) + >>> a.is_compatible(b) + False + >>> b.is_compatible(a) + False + +* Two different minors: + + .. code-block:: python + + >>> a = Version(1, 1, 0) + >>> b = Version(1, 0, 0) + >>> a.is_compatible(b) + False + >>> b.is_compatible(a) + True + +* The same two majors and minors: + + .. code-block:: python + + >>> a = Version(1, 1, 1) + >>> b = Version(1, 1, 0) + >>> a.is_compatible(b) + True + >>> b.is_compatible(a) + True + +* Release and pre-release: + + .. code-block:: python + + >>> a = Version(1, 1, 1) + >>> b = Version(1, 0, 0,'rc1') + >>> a.is_compatible(b) + False + >>> b.is_compatible(a) + False + +* Different pre-releases: + + .. code-block:: python + + >>> a = Version(1, 0, 0, 'rc1') + >>> b = Version(1, 0, 0, 'rc2') + >>> a.is_compatible(b) + False + >>> b.is_compatible(a) + False + +* Identical pre-releases: + + .. code-block:: python + + >>> a = Version(1, 0, 0,'rc1') + >>> b = Version(1, 0, 0,'rc1') + >>> a.is_compatible(b) + True + +* All major zero versions are incompatible with anything but itself: + + .. code-block:: python + + >>> Version(0, 1, 0).is_compatible(Version(0, 1, 1)) + False + + # Only identical versions are compatible for major zero versions: + >>> Version(0, 1, 0).is_compatible(Version(0, 1, 0)) + True diff --git a/docs/usage/check-valid-semver-version.rst b/docs/usage/check-valid-semver-version.rst index 7aa9615b..bdd57e7a 100644 --- a/docs/usage/check-valid-semver-version.rst +++ b/docs/usage/check-valid-semver-version.rst @@ -2,11 +2,11 @@ Checking for a Valid Semver Version =================================== If you need to check a string if it is a valid semver version, use the -classmethod :func:`Version.isvalid `: +classmethod :meth:`~semver.version.Version.is_valid`: .. code-block:: python - >>> Version.isvalid("1.0.0") + >>> Version.is_valid("1.0.0") True - >>> Version.isvalid("invalid") + >>> Version.is_valid("invalid") False diff --git a/docs/usage/compare-versions-through-expression.rst b/docs/usage/compare-versions-through-expression.rst index 28fad671..e2dee4d6 100644 --- a/docs/usage/compare-versions-through-expression.rst +++ b/docs/usage/compare-versions-through-expression.rst @@ -2,7 +2,7 @@ Comparing Versions through an Expression ======================================== If you need a more fine-grained approach of comparing two versions, -use the :func:`semver.match` function. It expects two arguments: +use the :meth:`~semver.version.Version.match` function. It expects two arguments: 1. a version string 2. a match expression @@ -20,9 +20,9 @@ That gives you the following possibilities to express your condition: .. code-block:: python - >>> semver.match("2.0.0", ">=1.0.0") + >>> Version.parse("2.0.0").match(">=1.0.0") True - >>> semver.match("1.0.0", ">1.0.0") + >>> Version.parse("1.0.0").match(">1.0.0") False If no operator is specified, the match expression is interpreted as a @@ -33,7 +33,7 @@ handle both cases: .. code-block:: python - >>> semver.match("2.0.0", "2.0.0") + >>> Version.parse("2.0.0").match("2.0.0") True - >>> semver.match("1.0.0", "3.5.1") + >>> Version.parse("1.0.0").match("3.5.1") False diff --git a/docs/usage/compare-versions.rst b/docs/usage/compare-versions.rst index b42ba1a7..cf55eae3 100644 --- a/docs/usage/compare-versions.rst +++ b/docs/usage/compare-versions.rst @@ -17,7 +17,7 @@ To compare two versions depends on your type: The return value is negative if ``version1 < version2``, zero if ``version1 == version2`` and strictly positive if ``version1 > version2``. -* **Two** :class:`Version ` **instances** +* **Two** :class:`~semver.version.Version` **instances** Use the specific operator. Currently, the operators ``<``, ``<=``, ``>``, ``>=``, ``==``, and ``!=`` are supported:: @@ -29,9 +29,9 @@ To compare two versions depends on your type: >>> v1 > v2 False -* **A** :class:`Version ` **type and a** :func:`tuple` **or** :func:`list` +* **A** :class:`~semver.version.Version` **type and a** :func:`tuple` **or** :func:`list` - Use the operator as with two :class:`Version ` types:: + Use the operator as with two :class:`~semver.version.Version` types:: >>> v = Version.parse("3.4.5") >>> v > (1, 0) @@ -46,7 +46,7 @@ To compare two versions depends on your type: >>> [3, 5] > v True -* **A** :class:`Version ` **type and a** :func:`str` +* **A** :class:`~semver.version.Version` **type and a** :func:`str` You can use also raw strings to compare:: @@ -69,7 +69,7 @@ To compare two versions depends on your type: ... ValueError: 1.0 is not valid SemVer string -* **A** :class:`Version ` **type and a** :func:`dict` +* **A** :class:`~semver.version.Version` **type and a** :func:`dict` You can also use a dictionary. In contrast to strings, you can have an "incomplete" version (as the other parts are set to zero):: diff --git a/docs/usage/convert-version-into-different-types.rst b/docs/usage/convert-version-into-different-types.rst index 976283d8..6948438c 100644 --- a/docs/usage/convert-version-into-different-types.rst +++ b/docs/usage/convert-version-into-different-types.rst @@ -3,23 +3,23 @@ Converting a Version instance into Different Types ================================================== -Sometimes it is needed to convert a :class:`Version ` instance into +Sometimes it is needed to convert a :class:`~semver.version.Version` instance into a different type. For example, for displaying or to access all parts. -It is possible to convert a :class:`Version ` instance: +It is possible to convert a :class:`~semver.version.Version` instance: * Into a string with the builtin function :func:`str`:: >>> str(Version.parse("3.4.5-pre.2+build.4")) '3.4.5-pre.2+build.4' -* Into a dictionary with :func:`to_dict `:: +* Into a dictionary with :meth:`~semver.version.Version.to_dict`:: >>> v = Version(major=3, minor=4, patch=5) >>> v.to_dict() OrderedDict([('major', 3), ('minor', 4), ('patch', 5), ('prerelease', None), ('build', None)]) -* Into a tuple with :func:`to_tuple `:: +* Into a tuple with :meth:`~semver.version.Version.to_tuple`:: >>> v = Version(major=5, minor=4, patch=2) >>> v.to_tuple() diff --git a/docs/usage/create-a-version.rst b/docs/usage/create-a-version.rst index 3acb4c03..48bb58a1 100644 --- a/docs/usage/create-a-version.rst +++ b/docs/usage/create-a-version.rst @@ -3,7 +3,7 @@ Creating a Version .. versionchanged:: 3.0.0 - The former :class:`~semver.version.VersionInfo` + The former :class:`~semver.version.VersionInfo` class has been renamed to :class:`~semver.version.Version`. The preferred way to create a new version is with the class @@ -15,7 +15,7 @@ The preferred way to create a new version is with the class create a version with module level functions. However, module level functions are marked as *deprecated* since version 2.x.y now. - These functions will be removed in semver 3.1.0. + These functions will be removed. For details, see the sections :ref:`sec_replace_deprecated_functions` and :ref:`sec_display_deprecation_warnings`. diff --git a/docs/usage/get-min-and-max-of-multiple-versions.rst b/docs/usage/get-min-and-max-of-multiple-versions.rst index 266ee50b..e143162a 100644 --- a/docs/usage/get-min-and-max-of-multiple-versions.rst +++ b/docs/usage/get-min-and-max-of-multiple-versions.rst @@ -7,9 +7,9 @@ Getting Minimum and Maximum of Multiple Versions The functions :func:`semver.max_ver` and :func:`semver.min_ver` are deprecated in favor of their builtin counterparts :func:`max` and :func:`min`. -Since :class:`Version ` implements -:func:`__gt__ ` and -:func:`__lt__ `, it can be used with builtins requiring: +Since :class:`~semver.version.Version` implements +:meth:`~semver.version.Version.__gt__` and +:meth:`~semver.version.Version.__lt__`, it can be used with builtins requiring: .. code-block:: python @@ -19,7 +19,7 @@ Since :class:`Version ` implements Version(major=0, minor=1, patch=0, prerelease=None, build=None) Incidentally, using :func:`map`, you can get the min or max version of any number of versions of the same type -(convertible to :class:`Version `). +(convertible to :class:`~semver.version.Version`). For example, here are the maximum and minimum versions of a list of version strings: @@ -40,12 +40,3 @@ And the same can be done with tuples: (0, 4, 99, None, None) For dictionaries, it is very similar to finding the max version tuple: see :ref:`sec.convert.versions`. - -The "old way" with :func:`semver.max_ver` or :func:`semver.min_ver` is still available, but not recommended: - -.. code-block:: python - - >>> semver.max_ver("1.0.0", "2.0.0") - '2.0.0' - >>> semver.min_ver("1.0.0", "2.0.0") - '1.0.0' diff --git a/docs/usage/increase-parts-of-a-version_prereleases.rst b/docs/usage/increase-parts-of-a-version_prereleases.rst index 98283937..845f2290 100644 --- a/docs/usage/increase-parts-of-a-version_prereleases.rst +++ b/docs/usage/increase-parts-of-a-version_prereleases.rst @@ -1,11 +1,13 @@ +.. _increase-parts-of-a-version: + Increasing Parts of a Version Taking into Account Prereleases ============================================================= .. versionadded:: 2.10.0 - Added :func:`Version.next_version `. + Added :meth:`~semver.version.Version.next_version`. If you want to raise your version and take prereleases into account, -the function :func:`next_version ` +the function :meth:`~semver.version.Version.next_version` would perhaps a better fit. diff --git a/docs/usage/index.rst b/docs/usage/index.rst index ddfc2284..4b8e3fc9 100644 --- a/docs/usage/index.rst +++ b/docs/usage/index.rst @@ -8,6 +8,7 @@ Using semver create-a-version parse-version-string check-valid-semver-version + check-compatible-semver-version access-parts-of-a-version access-parts-through-index replace-parts-of-a-version diff --git a/docs/usage/parse-version-string.rst b/docs/usage/parse-version-string.rst index 0a39c8a3..0cf02650 100644 --- a/docs/usage/parse-version-string.rst +++ b/docs/usage/parse-version-string.rst @@ -2,7 +2,7 @@ Parsing a Version String ======================== "Parsing" in this context means to identify the different parts in a string. -Use the function :func:`Version.parse `:: +Use the function :meth:`~semver.version.Version.parse`:: >>> Version.parse("3.4.5-pre.2+build.4") Version(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') diff --git a/docs/usage/raise-parts-of-a-version.rst b/docs/usage/raise-parts-of-a-version.rst index cc62fffb..be89cf8d 100644 --- a/docs/usage/raise-parts-of-a-version.rst +++ b/docs/usage/raise-parts-of-a-version.rst @@ -1,18 +1,32 @@ Raising Parts of a Version ========================== +.. note:: + + Keep in mind, "raising" the pre-release 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 + method :meth:`~semver.version.Version.next_version` + in section :ref:`increase-parts-of-a-version`. + + The ``semver`` module contains the following functions to raise parts of a version: -* :func:`Version.bump_major `: raises the major part and set all other parts to +* :meth:`~semver.version.Version.bump_major`: raises the major part and set all other parts to zero. Set ``prerelease`` and ``build`` to ``None``. -* :func:`Version.bump_minor `: raises the minor part and sets ``patch`` to zero. +* :meth:`~semver.version.Version.bump_minor`: raises the minor part and sets ``patch`` to zero. Set ``prerelease`` and ``build`` to ``None``. -* :func:`Version.bump_patch `: raises the patch part. Set ``prerelease`` and +* :meth:`~semver.version.Version.bump_patch`: raises the patch part. Set ``prerelease`` and ``build`` to ``None``. -* :func:`Version.bump_prerelease `: raises the prerelease part and set +* :meth:`~semver.version.Version.bump_prerelease`: raises the prerelease part and set ``build`` to ``None``. -* :func:`Version.bump_build `: raises the build part. +* :meth:`~semver.version.Version.bump_build`: raises the build part. + .. code-block:: python @@ -28,3 +42,29 @@ a version: '3.4.5-pre.2+build.5' Likewise the module level functions :func:`semver.bump_major`. + +For the methods :meth:`~semver.version.Version.bump_prerelease` +and :meth:`~semver.version.Version.bump_build` it's possible to pass an empty string or ``None``. +However, it gives different results: + +.. code-block:: python + + >>> str(Version.parse("3.4.5").bump_prerelease('')) + '3.4.5-1' + >>> str(Version.parse("3.4.5").bump_prerelease(None)) + '3.4.5-rc.1' + +An empty string removes any prefix whereas ``None`` is the same as calling +the method without any argument. + +If you already have a prerelease, the argument for the method +is not taken into account: + +.. code-block:: python + + >>> str(Version.parse("3.4.5-rc.1").bump_prerelease(None)) + '3.4.5-rc.2' + >>> str(Version.parse("3.4.5-rc.1").bump_prerelease('')) + '3.4.5-rc.2' + + diff --git a/docs/usage/replace-parts-of-a-version.rst b/docs/usage/replace-parts-of-a-version.rst index b6c38865..57ab65e9 100644 --- a/docs/usage/replace-parts-of-a-version.rst +++ b/docs/usage/replace-parts-of-a-version.rst @@ -4,25 +4,14 @@ Replacing Parts of a Version ============================ If you want to replace different parts of a version, but leave other parts -unmodified, use the function :func:`replace `: - -* From a :class:`Version ` instance:: +unmodified, use the function :meth:`~semver.version.Version.replace`: >>> version = semver.Version.parse("1.4.5-pre.1+build.6") >>> version.replace(major=2, minor=2) Version(major=2, minor=2, patch=5, prerelease='pre.1', build='build.6') -* From a version string:: - - >>> semver.replace("1.4.5-pre.1+build.6", major=2) - '2.4.5-pre.1+build.6' - If you pass invalid keys you get an exception:: - >>> semver.replace("1.2.3", invalidkey=2) - Traceback (most recent call last): - ... - TypeError: replace() got 1 unexpected keyword argument(s): invalidkey >>> version = semver.Version.parse("1.4.5-pre.1+build.6") >>> version.replace(invalidkey=2) Traceback (most recent call last): diff --git a/docs/usage/semver-version.rst b/docs/usage/semver-version.rst index 8eeab62f..e8cc92b3 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.0-dev.4' + '3.0.0-rc.1' diff --git a/setup.cfg b/setup.cfg index 87ec3b8f..0ee8564c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -39,7 +39,7 @@ license = BSD package_dir = =src packages = find: -python_requires = >=3.7.* +python_requires = >=3.7 include_package_data = True [options.entry_points] @@ -55,12 +55,12 @@ semver = py.typed [tool:pytest] norecursedirs = .git build .env/ env/ .pyenv/ .tmp/ .eggs/ venv/ testpaths = tests docs -# pythonpath = src +pythonpath = src tests filterwarnings = ignore:Function 'semver.*:DeprecationWarning # ' <- This apostroph is just to fix syntax highlighting addopts = - # --import-mode=importlib + --import-mode=importlib --no-cov-on-fail --cov=semver --cov-report=term-missing diff --git a/src/semver/__about__.py b/src/semver/__about__.py index 0f7150bf..dd671d02 100644 --- a/src/semver/__about__.py +++ b/src/semver/__about__.py @@ -16,7 +16,7 @@ """ #: Semver version -__version__ = "3.0.0-dev.4" +__version__ = "3.0.0-rc.1" #: Original semver author __author__ = "Kostiantyn Rybnikov" diff --git a/src/semver/__main__.py b/src/semver/__main__.py index a6d448aa..6cb11f09 100644 --- a/src/semver/__main__.py +++ b/src/semver/__main__.py @@ -25,4 +25,4 @@ def main(cliargs: Optional[List[str]] = None) -> int: if __name__ == "__main__": - sys.exit(main(sys.argv)) + sys.exit(main(sys.argv[1:])) diff --git a/src/semver/_deprecated.py b/src/semver/_deprecated.py index 5f51c8f3..8dfb1933 100644 --- a/src/semver/_deprecated.py +++ b/src/semver/_deprecated.py @@ -25,7 +25,7 @@ def deprecated( :param func: the function to decorate :param replace: the function to replace (use the full qualified - name like ``semver.Version.bump_major``. + name like ``semver.version.Version.bump_major``. :param version: the first version when this function was deprecated. :param category: allow you to specify the deprecation warning class of your choice. By default, it's :class:`DeprecationWarning`, but @@ -75,7 +75,7 @@ def parse(version): Parse version to major, minor, patch, pre-release, build parts. .. deprecated:: 2.10.0 - Use :func:`semver.Version.parse` instead. + Use :meth:`~semver.version.Version.parse` instead. :param version: version string :return: dictionary with the keys 'build', 'major', 'minor', 'patch', @@ -98,13 +98,13 @@ def parse(version): return Version.parse(version).to_dict() -@deprecated(replace="semver.Version.parse", version="2.10.0") +@deprecated(replace="semver.version.Version.parse", version="2.10.0") def parse_version_info(version): """ - Parse version string to a VersionInfo instance. + Parse version string to a Version instance. .. deprecated:: 2.10.0 - Use :func:`semver.VersionInfo.parse` instead. + Use :meth:`~semver.version.Version.parse` instead. .. versionadded:: 2.7.2 Added :func:`semver.parse_version_info` @@ -153,6 +153,9 @@ def match(version, match_expr): """ Compare two versions strings through a comparison. + .. deprecated:: 2.10.0 + Use :meth:`~semver.version.Version.match` instead. + :param str version: a version string :param str match_expr: operator and version; valid operators are < smaller than @@ -178,6 +181,9 @@ def max_ver(ver1, ver2): """ Returns the greater version of two versions strings. + .. deprecated:: 2.10.2 + Use :func:`max` instead. + :param ver1: version string 1 :param ver2: version string 2 :return: the greater version of the two @@ -202,6 +208,9 @@ def min_ver(ver1, ver2): """ Returns the smaller version of two versions strings. + .. deprecated:: 2.10.2 + Use Use :func:`min` instead. + :param ver1: version string 1 :param ver2: version string 2 :return: the smaller version of the two @@ -246,7 +255,7 @@ def bump_major(version): Raise the major part of the version string. .. deprecated:: 2.10.0 - Use :func:`semver.Version.bump_major` instead. + Use :meth:`~semver.version.Version.bump_major` instead. :param: version string :return: the raised version string @@ -264,7 +273,7 @@ def bump_minor(version): Raise the minor part of the version string. .. deprecated:: 2.10.0 - Use :func:`semver.Version.bump_minor` instead. + Use :meth:`~semver.version.Version.bump_minor` instead. :param: version string :return: the raised version string @@ -282,7 +291,7 @@ def bump_patch(version): Raise the patch part of the version string. .. deprecated:: 2.10.0 - Use :func:`semver.Version.bump_patch` instead. + Use :meth:`~semver.version.Version.bump_patch` instead. :param: version string :return: the raised version string @@ -300,7 +309,7 @@ def bump_prerelease(version, token="rc"): Raise the prerelease part of the version string. .. deprecated:: 2.10.0 - Use :func:`semver.Version.bump_prerelease` instead. + Use :meth:`~semver.version.Version.bump_prerelease` instead. :param version: version string :param token: defaults to 'rc' @@ -319,7 +328,7 @@ def bump_build(version, token="build"): Raise the build part of the version string. .. deprecated:: 2.10.0 - Use :func:`semver.Version.bump_build` instead. + Use :meth:`~semver.version.Version.bump_build` instead. :param version: version string :param token: defaults to 'build' @@ -338,7 +347,7 @@ def finalize_version(version): Remove any prerelease and build metadata from the version string. .. deprecated:: 2.10.0 - Use :func:`semver.Version.finalize_version` instead. + Use :meth:`~semver.version.Version.finalize_version` instead. .. versionadded:: 2.7.9 Added :func:`finalize_version` @@ -360,7 +369,7 @@ def replace(version, **parts): Replace one or more parts of a version and return the new string. .. deprecated:: 2.10.0 - Use :func:`semver.Version.replace` instead. + Use :meth:`~semver.version.Version.replace` instead. .. versionadded:: 2.9.0 Added :func:`replace` diff --git a/src/semver/cli.py b/src/semver/cli.py index 3c573d63..b2751429 100644 --- a/src/semver/cli.py +++ b/src/semver/cli.py @@ -54,7 +54,7 @@ def cmd_check(args: argparse.Namespace) -> None: :param args: The parsed arguments """ - if Version.isvalid(args.version): + if Version.is_valid(args.version): return None raise ValueError("Invalid version %r" % args.version) diff --git a/src/semver/version.py b/src/semver/version.py index 96281192..cca744a1 100644 --- a/src/semver/version.py +++ b/src/semver/version.py @@ -1,4 +1,4 @@ -"""Version handling.""" +"""Version handling by a semver compatible version class.""" import collections import re @@ -14,6 +14,8 @@ cast, Callable, Collection, + Type, + TypeVar, ) from ._types import ( @@ -28,6 +30,8 @@ Comparable = Union["Version", Dict[str, VersionPart], Collection[VersionPart], str] Comparator = Callable[["Version", Comparable], bool] +T = TypeVar("T", bound="Version") + def _comparator(operator: Comparator) -> Comparator: """Wrap a Version binary op method in a type-check.""" @@ -57,6 +61,8 @@ class Version: """ A semver compatible version class. + See specification at https://semver.org. + :param major: version when you make incompatible API changes. :param minor: version when you add functionality in a backwards-compatible manner. @@ -66,6 +72,10 @@ 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__]) + #: Regex for number in a prerelease _LAST_NUMBER = re.compile(r"(?:[^\d]*(\d+)[^\d]*)+") #: Regex template for a semver version @@ -197,7 +207,7 @@ def to_tuple(self) -> VersionTuple: Convert the Version object to a tuple. .. versionadded:: 2.10.0 - Renamed ``VersionInfo._astuple`` to ``VersionInfo.to_tuple`` to + Renamed :meth:`Version._astuple` to :meth:`Version.to_tuple` to make this function available in the public API. :return: a tuple with all the parts @@ -212,7 +222,7 @@ def to_dict(self) -> VersionDict: Convert the Version object to an OrderedDict. .. versionadded:: 2.10.0 - Renamed ``VersionInfo._asdict`` to ``VersionInfo.to_dict`` to + 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``, @@ -261,7 +271,6 @@ def bump_major(self) -> "Version": :return: new object with the raised major part - >>> ver = semver.parse("3.4.5") >>> ver.bump_major() Version(major=4, minor=0, patch=0, prerelease=None, build=None) @@ -297,30 +306,44 @@ def bump_patch(self) -> "Version": cls = type(self) return cls(self._major, self._minor, self._patch + 1) - def bump_prerelease(self, token: str = "rc") -> "Version": + def bump_prerelease(self, token: Optional[str] = "rc") -> "Version": """ Raise the prerelease part of the version, return a new object but leave self untouched. - :param token: defaults to ``rc`` - :return: new object with the raised prerelease part + :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() - Version(major=3, minor=4, patch=5, prerelease='rc.2', \ -build=None) + >>> ver.bump_prerelease().prerelease + 'rc.2' + >>> ver.bump_prerelease('').prerelease + '1' + >>> ver.bump_prerelease(None).prerelease + 'rc.1' """ cls = type(self) - prerelease = cls._increment_string(self._prerelease or (token or "rc") + ".0") + if self._prerelease is not None: + prerelease = self._prerelease + elif token == "": + prerelease = "0" + elif token is None: + prerelease = "rc.0" + else: + prerelease = str(token) + ".0" + + prerelease = cls._increment_string(prerelease) return cls(self._major, self._minor, self._patch, prerelease) - def bump_build(self, token: str = "build") -> "Version": + def bump_build(self, token: Optional[str] = "build") -> "Version": """ Raise the build part of the version, return a new object but leave self untouched. - :param token: defaults to ``build`` - :return: new object with the raised build part + :param token: defaults to ``'build'`` + :return: new :class:`Version` object with the raised build part. + The original object is not modified. >>> ver = semver.parse("3.4.5-rc.1+build.9") >>> ver.bump_build() @@ -328,7 +351,28 @@ def bump_build(self, token: str = "build") -> "Version": build='build.10') """ cls = type(self) - build = cls._increment_string(self._build or (token or "build") + ".0") + 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) + 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) def compare(self, other: Comparable) -> int: @@ -398,18 +442,12 @@ def next_version(self, part: str, prerelease_token: str = "rc") -> "Version": :param prerelease_token: prefix string of prerelease, defaults to 'rc' :return: new object with the appropriate part raised """ - validparts = { - "major", - "minor", - "patch", - "prerelease", - # "build", # currently not used - } + cls = type(self) + # "build" is currently not used, that's why we use [:-1] + validparts = cls.NAMES[:-1] if part not in validparts: raise ValueError( - "Invalid part. Expected one of {validparts}, but got {part!r}".format( - validparts=validparts, part=part - ) + f"Invalid part. Expected one of {validparts}, but got {part!r}" ) version = self if (version.prerelease or version.build) and ( @@ -419,7 +457,8 @@ def next_version(self, part: str, prerelease_token: str = "rc") -> "Version": ): return version.replace(prerelease=None, build=None) - if part in ("major", "minor", "patch"): + # Only check the main parts: + if part in cls.NAMES[:3]: return getattr(version, "bump_" + part)() if not version.prerelease: @@ -460,7 +499,7 @@ def __getitem__( is undefined, it will throw an index error. Negative indices are not supported. - :param Union[int, slice] index: a positive integer indicating the + :param index: a positive integer indicating the offset or a :func:`slice` object :raises IndexError: if index is beyond the range or a part is None :return: the requested part of the version at position index @@ -522,7 +561,7 @@ def match(self, match_expr: str) -> bool: Compare self to match a match expression. :param match_expr: optional operator and version; valid operators are - ``<``` smaller than + ``<`` smaller than ``>`` greater than ``>=`` greator or equal than ``<=`` smaller or equal than @@ -570,8 +609,8 @@ def match(self, match_expr: str) -> bool: @classmethod def parse( - cls, version: String, optional_minor_and_patch: bool = False - ) -> "Version": + cls: Type[T], version: String, optional_minor_and_patch: bool = False + ) -> T: """ Parse version string to a Version instance. @@ -579,8 +618,8 @@ def parse( Changed method from static to classmethod to allow subclasses. .. versionchanged:: 3.0.0 - Added optional parameter optional_minor_and_patch to allow optional - minor and patch parts. + Added optional parameter ``optional_minor_and_patch`` to allow + optional minor and patch parts. :param version: version string :param optional_minor_and_patch: if set to true, the version string to parse \ @@ -625,8 +664,8 @@ def replace(self, **parts: Union[int, Optional[str]]) -> "Version": :param parts: the parts to be updated. Valid keys are: ``major``, ``minor``, ``patch``, ``prerelease``, or ``build`` - :return: the new :class:`Version` object with the changed - parts + :return: the new :class:`~semver.version.Version` object with + the changed parts :raises TypeError: if ``parts`` contain invalid keys """ version = self.to_dict() @@ -642,12 +681,15 @@ def replace(self, **parts: Union[int, Optional[str]]) -> "Version": raise TypeError(error) @classmethod - def isvalid(cls, version: str) -> bool: + def is_valid(cls, version: str) -> bool: """ Check if the string is a valid semver version. .. versionadded:: 2.9.1 + .. versionchanged:: 3.0.0 + Renamed from :meth:`~semver.version.Version.isvalid` + :param version: the version string to check :return: True if the version string is a valid semver version, False otherwise. @@ -658,6 +700,44 @@ def isvalid(cls, version: str) -> bool: except ValueError: return False + def is_compatible(self, other: "Version") -> bool: + """ + Check if current version is compatible with other version. + + The result is True, if either of the following is true: + + * both versions are equal, or + * both majors are equal and higher than 0. Same for both minors. + Both pre-releases are equal, or + * both majors are equal and higher than 0. The minor of b's + minor version is higher then a's. Both pre-releases are equal. + + The algorithm does *not* check patches. + + .. versionadded:: 3.0.0 + + :param other: the version to check for compatibility + :return: True, if ``other`` is compatible with the old version, + otherwise False + + >>> Version(1, 1, 0).is_compatible(Version(1, 0, 0)) + False + >>> Version(1, 0, 0).is_compatible(Version(1, 1, 0)) + True + """ + if not isinstance(other, Version): + raise TypeError(f"Expected a Version type but got {type(other)}") + + # All major-0 versions should be incompatible with anything but itself + if (0 == self.major == other.major) and (self[:4] != other[:4]): + return False + + return ( + (self.major == other.major) + and (other.minor >= self.minor) + and (self.prerelease == other.prerelease) + ) + #: Keep the VersionInfo name for compatibility VersionInfo = Version diff --git a/tests/conftest.py b/tests/conftest.py index beecffc9..9017bbbe 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,8 +4,6 @@ import semver -# sys.path.insert(0, "docs/usage") - from coerce import coerce # noqa:E402 from semverwithvprefix import SemVerWithVPrefix # noqa:E402 import packaging.version diff --git a/tests/test_bump.py b/tests/test_bump.py index c28e1905..34e0b2ac 100644 --- a/tests/test_bump.py +++ b/tests/test_bump.py @@ -66,6 +66,30 @@ def test_should_versioninfo_bump_multiple(): assert v.bump_prerelease().bump_build().bump_build().bump_prerelease() == expected +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 + + +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 + + +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 + + +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 + + def test_should_ignore_extensions_for_bump(): assert bump_patch("3.4.5-rc1+build4") == "3.4.6" diff --git a/tests/test_semver.py b/tests/test_semver.py index b15bfeaf..782d5c79 100644 --- a/tests/test_semver.py +++ b/tests/test_semver.py @@ -73,10 +73,65 @@ def test_should_be_able_to_use_integers_as_prerelease_build(): def test_should_versioninfo_isvalid(): - assert Version.isvalid("1.0.0") is True - assert Version.isvalid("foo") is False + assert Version.is_valid("1.0.0") is True + assert Version.is_valid("foo") is False def test_versioninfo_compare_should_raise_when_passed_invalid_value(): with pytest.raises(TypeError): Version(1, 2, 3).compare(4) + + +@pytest.mark.parametrize( + "old, new", + [ + ((1, 2, 3), (1, 2, 3)), + ((1, 2, 3), (1, 2, 4)), + ((1, 2, 4), (1, 2, 3)), + ((1, 2, 3, "rc.0"), (1, 2, 4, "rc.0")), + ((0, 1, 0), (0, 1, 0)), + ], +) +def test_should_succeed_compatible_match(old, new): + old = Version(*old) + new = Version(*new) + assert old.is_compatible(new) + + +@pytest.mark.parametrize( + "old, new", + [ + ((1, 1, 0), (1, 0, 0)), + ((2, 0, 0), (1, 5, 0)), + ((1, 2, 3, "rc.1"), (1, 2, 3, "rc.0")), + ((1, 2, 3, "rc.1"), (1, 2, 4, "rc.0")), + ((0, 1, 0), (0, 1, 1)), + ((1, 0, 0), (1, 0, 0, "rc1")), + ((1, 0, 0, "rc1"), (1, 0, 0)), + ], +) +def test_should_fail_compatible_match(old, new): + old = Version(*old) + new = Version(*new) + assert not old.is_compatible(new) + + +@pytest.mark.parametrize( + "wrongtype", + [ + "wrongtype", + dict(a=2), + list(), + ], +) +def test_should_fail_with_incompatible_type_for_compatible_match(wrongtype): + with pytest.raises(TypeError, match="Expected a Version type .*"): + v = Version(1, 2, 3) + v.is_compatible(wrongtype) + + +def test_should_succeed_with_compatible_subclass_for_is_compatible(): + class CustomVersion(Version): + ... + + assert CustomVersion(1, 0, 0).is_compatible(Version(1, 0, 0)) diff --git a/tox.ini b/tox.ini index 8ca917b8..b71ae78e 100644 --- a/tox.ini +++ b/tox.ini @@ -18,6 +18,7 @@ python = [testenv] description = Run test suite for {basepython} +skip_install = true allowlist_externals = make commands = pytest {posargs:} deps = @@ -55,7 +56,7 @@ commands = mypy {posargs:--ignore-missing-imports --check-untyped-defs src} description = Check for PEP257 compatible docstrings basepython = python3 deps = docformatter -commands = docformatter --check {posargs:--pre-summary-newline -r src} +commands = docformatter --check --diff {posargs:--pre-summary-newline -r src} [testenv:checks]