diff --git a/changelog.d/335.doc.rst b/changelog.d/335.doc.rst new file mode 100644 index 00000000..1d29fb87 --- /dev/null +++ b/changelog.d/335.doc.rst @@ -0,0 +1,2 @@ +Add new section "Converting versions between PyPI and semver" the limitations +and possible use cases to convert from one into the other versioning scheme. diff --git a/docs/advanced/convert-pypi-to-semver.rst b/docs/advanced/convert-pypi-to-semver.rst new file mode 100644 index 00000000..76653ceb --- /dev/null +++ b/docs/advanced/convert-pypi-to-semver.rst @@ -0,0 +1,207 @@ +Converting versions between PyPI and semver +=========================================== + +.. Link + https://packaging.pypa.io/en/latest/_modules/packaging/version.html#InvalidVersion + +When packaging for PyPI, your versions are defined through `PEP 440`_. +This is the standard version scheme for Python packages and +implemented by the :class:`packaging.version.Version` class. + +However, these versions are different from semver versions +(cited from `PEP 440`_): + +* The "Major.Minor.Patch" (described in this PEP as "major.minor.micro") + aspects of semantic versioning (clauses 1-8 in the 2.0.0 + specification) are fully compatible with the version scheme defined + in this PEP, and abiding by these aspects is encouraged. + +* Semantic versions containing a hyphen (pre-releases - clause 10) + or a plus sign (builds - clause 11) are *not* compatible with this PEP + and are not permitted in the public version field. + +In other words, it's not always possible to convert between these different +versioning schemes without information loss. It depends on what parts are +used. The following table gives a mapping between these two versioning +schemes: + ++--------------+----------------+ +| PyPI Version | Semver version | ++==============+================+ +| ``epoch`` | n/a | ++--------------+----------------+ +| ``major`` | ``major`` | ++--------------+----------------+ +| ``minor`` | ``minor`` | ++--------------+----------------+ +| ``micro`` | ``patch`` | ++--------------+----------------+ +| ``pre`` | ``prerelease`` | ++--------------+----------------+ +| ``dev`` | ``build`` | ++--------------+----------------+ +| ``post`` | n/a | ++--------------+----------------+ + + +.. _convert_pypi_to_semver: + +From PyPI to semver +------------------- + +We distinguish between the following use cases: + + +* **"Incomplete" versions** + + If you only have a major part, this shouldn't be a problem. + The initializer of :class:`semver.Version ` takes + care to fill missing parts with zeros (except for major). + + .. code-block:: python + + >>> from packaging.version import Version as PyPIVersion + >>> from semver import Version + + >>> p = PyPIVersion("3.2") + >>> p.release + (3, 2) + >>> Version(*p.release) + Version(major=3, minor=2, patch=0, prerelease=None, build=None) + +* **Major, minor, and patch** + + This is the simplest and most compatible approch. Both versioning + schemes are compatible without information loss. + + .. code-block:: python + + >>> p = PyPIVersion("3.0.0") + >>> p.base_version + '3.0.0' + >>> p.release + (3, 0, 0) + >>> Version(*p.release) + Version(major=3, minor=0, patch=0, prerelease=None, build=None) + +* **With** ``pre`` **part only** + + A prerelease exists in both versioning schemes. As such, both are + a natural candidate. A prelease in PyPI version terms is the same + as a "release candidate", or "rc". + + .. code-block:: python + + >>> p = PyPIVersion("2.1.6.pre5") + >>> p.base_version + '2.1.6' + >>> p.pre + ('rc', 5) + >>> pre = "".join([str(i) for i in p.pre]) + >>> Version(*p.release, pre) + Version(major=2, minor=1, patch=6, prerelease='rc5', build=None) + +* **With only development version** + + Semver doesn't have a "development" version. + However, we could use Semver's ``build`` part: + + .. code-block:: python + + >>> p = PyPIVersion("3.0.0.dev2") + >>> p.base_version + '3.0.0' + >>> p.dev + 2 + >>> Version(*p.release, build=f"dev{p.dev}") + Version(major=3, minor=0, patch=0, prerelease=None, build='dev2') + +* **With a** ``post`` **version** + + Semver doesn't know the concept of a post version. As such, there + is currently no way to convert it reliably. + +* **Any combination** + + There is currently no way to convert a PyPI version which consists + of, for example, development *and* post parts. + + +You can use the following function to convert a PyPI version into +semver: + +.. code-block:: python + + def convert2semver(ver: packaging.version.Version) -> semver.Version: + """Converts a PyPI version into a semver version + + :param packaging.version.Version ver: the PyPI version + :return: a semver version + :raises ValueError: if epoch or post parts are used + """ + if not ver.epoch: + raise ValueError("Can't convert an epoch to semver") + if not ver.post: + 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) + + +.. _convert_semver_to_pypi: + +From semver to PyPI +------------------- + +We distinguish between the following use cases: + + +* **Major, minor, and patch** + + .. code-block:: python + + >>> from packaging.version import Version as PyPIVersion + >>> from semver import Version + + >>> v = Version(1, 2, 3) + >>> PyPIVersion(str(v.finalize_version())) + + +* **With** ``pre`` **part only** + + .. code-block:: python + + >>> v = Version(2, 1, 4, prerelease="rc1") + >>> PyPIVersion(str(v)) + + +* **With only development version** + + .. code-block:: python + + >>> v = Version(3, 2, 8, build="dev4") + >>> PyPIVersion(f"{v.finalize_version()}{v.build}") + + +If you are unsure about the parts of the version, the following +function helps to convert the different parts: + +.. code-block:: python + + def convert2pypi(ver: semver.Version) -> packaging.version.Version: + """Converts a semver version into a version from PyPI + + A semver prerelease will be converted into a + prerelease of PyPI. + A semver build will be converted into a development + part of PyPI + :param semver.Version ver: the semver version + :return: a PyPI version + """ + v = ver.finalize_version() + prerelease = ver.prerelease if ver.prerelease else "" + build = ver.build if ver.build else "" + return PyPIVersion(f"{v}{prerelease}{build}") + + +.. _PEP 440: https://www.python.org/dev/peps/pep-0440/ diff --git a/docs/advanced/index.rst b/docs/advanced/index.rst index f45a2f9e..d82da1a1 100644 --- a/docs/advanced/index.rst +++ b/docs/advanced/index.rst @@ -7,4 +7,5 @@ Advanced topics deal-with-invalid-versions create-subclasses-from-version display-deprecation-warnings - combine-pydantic-and-semver \ No newline at end of file + combine-pydantic-and-semver + convert-pypi-to-semver diff --git a/docs/conf.py b/docs/conf.py index 52a46704..ed888361 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import codecs +from datetime import date import os import re import sys @@ -24,6 +25,7 @@ SRC_DIR = os.path.abspath("../src/") sys.path.insert(0, SRC_DIR) # from semver import __version__ # noqa: E402 +YEAR = date.today().year def read(*parts): @@ -83,7 +85,7 @@ def find_version(*file_paths): # General information about the project. project = "python-semver" -copyright = "2018, Kostiantyn Rybnikov and all" +copyright = f"{YEAR}, Kostiantyn Rybnikov and all" author = "Kostiantyn Rybnikov and all" # The version info for the project you're documenting, acts as replacement for diff --git a/docs/install.rst b/docs/install.rst index b603703c..f23186a2 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -18,8 +18,13 @@ This line avoids surprises. You will get any updates within the major 2 release Keep in mind, as this line avoids any major version updates, you also will never get new exciting features or bug fixes. -You can add this line in your file :file:`setup.py`, :file:`requirements.txt`, or any other -file that lists your dependencies. +Same applies for semver v3, if you want to get all updates for the semver v3 +development line, but not a major update to semver v4:: + + semver>=3,<4 + +You can add this line in your file :file:`setup.py`, :file:`requirements.txt`, +:file:`pyproject.toml`, or any other file that lists your dependencies. Pip --- @@ -28,12 +33,12 @@ Pip pip3 install semver -If you want to install this specific version (for example, 2.10.0), use the command :command:`pip` +If you want to install this specific version (for example, 3.0.0), use the command :command:`pip` with an URL and its version: .. parsed-literal:: - pip3 install git+https://github.com/python-semver/python-semver.git@2.11.0 + pip3 install git+https://github.com/python-semver/python-semver.git@3.0.0 Linux Distributions diff --git a/docs/migration/replace-deprecated-functions.rst b/docs/migration/replace-deprecated-functions.rst index a8f2f8f6..9738001c 100644 --- a/docs/migration/replace-deprecated-functions.rst +++ b/docs/migration/replace-deprecated-functions.rst @@ -60,7 +60,7 @@ them with code which is compatible for future versions: .. code-block:: python >>> s1 = semver.max_ver("1.2.3", "1.2.4") - >>> s2 = str(max(map(Version.parse, ("1.2.3", "1.2.4")))) + >>> s2 = max("1.2.3", "1.2.4", key=Version.parse) >>> s1 == s2 True @@ -71,7 +71,7 @@ them with code which is compatible for future versions: .. code-block:: python >>> s1 = semver.min_ver("1.2.3", "1.2.4") - >>> s2 = str(min(map(Version.parse, ("1.2.3", "1.2.4")))) + >>> s2 = min("1.2.3", "1.2.4", key=Version.parse) >>> s1 == s2 True diff --git a/tests/conftest.py b/tests/conftest.py index 40b56ab1..beecffc9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,6 +8,7 @@ from coerce import coerce # noqa:E402 from semverwithvprefix import SemVerWithVPrefix # noqa:E402 +import packaging.version @pytest.fixture(autouse=True) @@ -16,6 +17,7 @@ def add_semver(doctest_namespace): doctest_namespace["semver"] = semver doctest_namespace["coerce"] = coerce doctest_namespace["SemVerWithVPrefix"] = SemVerWithVPrefix + doctest_namespace["PyPIVersion"] = packaging.version.Version @pytest.fixture pFad - Phonifier reborn

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

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


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy