Skip to content

Commit cb054ac

Browse files
committed
Describe conversion between PyPI and semver
Add new section "Converting versions between PyPI and semver" the limitations and possible use cases to convert from one into the other versioning scheme.
1 parent 53f721a commit cb054ac

File tree

7 files changed

+206
-8
lines changed

7 files changed

+206
-8
lines changed

changelog.d/335.doc.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add new section "Converting versions between PyPI and semver" the limitations
2+
and possible use cases to convert from one into the other versioning scheme.
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
Converting versions between PyPI and semver
2+
===========================================
3+
4+
.. Link
5+
https://packaging.pypa.io/en/latest/_modules/packaging/version.html#InvalidVersion
6+
7+
When packaging for PyPI, your versions are defined through `PEP440`_.
8+
This is the standard version scheme for Python packages and
9+
implemented by the :class:`packaging.version.Version` class.
10+
11+
However, versions definied by `PEP440`_ are different from semver
12+
versions (cited from `PEP440`_):
13+
14+
* The "Major.Minor.Patch" (described in this PEP as "major.minor.micro")
15+
aspects of semantic versioning (clauses 1-8 in the 2.0.0
16+
specification) are fully compatible with the version scheme defined
17+
in this PEP, and abiding by these aspects is encouraged.
18+
19+
* Semantic versions containing a hyphen (pre-releases - clause 10)
20+
or a plus sign (builds - clause 11) are *not* compatible with this PEP
21+
and are not permitted in the public version field.
22+
23+
In other words, it's not always possible to convert between these different
24+
versioning schemes without information loss. It depends on what parts are
25+
used. The following table gives a mapping between these two versioning
26+
schemes:
27+
28+
+--------------+----------------+
29+
| PyPI Version | Semver version |
30+
+==============+================+
31+
| ``epoch`` | n/a |
32+
+--------------+----------------+
33+
| ``major`` | ``major`` |
34+
+--------------+----------------+
35+
| ``minor`` | ``minor`` |
36+
+--------------+----------------+
37+
| ``micro`` | ``patch`` |
38+
+--------------+----------------+
39+
| ``pre`` | ``prerelease`` |
40+
+--------------+----------------+
41+
| ``dev`` | ``build`` |
42+
+--------------+----------------+
43+
| ``post`` | n/a |
44+
+--------------+----------------+
45+
46+
47+
.. _convert_pypi_to_semver:
48+
49+
From PyPI to semver
50+
-------------------
51+
52+
We distinguish between the following use cases:
53+
54+
55+
* **"Incomplete" versions**
56+
57+
If you only have a major part, this shouldn't be a problem.
58+
The initializer of :class:`semver.Version <semver.version.Version>` takes
59+
care to fill missing parts with zeros (except for major).
60+
61+
.. code-block:: python
62+
63+
>>> from packaging.version import Version as PyPIVersion
64+
>>> from semver import Version
65+
66+
>>> p = PyPIVersion("3.2")
67+
>>> p.release
68+
(3, 2)
69+
>>> Version(*p.release)
70+
Version(major=3, minor=2, patch=0, prerelease=None, build=None)
71+
72+
* **Major, minor, and patch**
73+
74+
This is the simplest and most compatible approch. Both versioning
75+
schemes are compatible without information loss.
76+
77+
.. code-block:: python
78+
79+
>>> p = PyPIVersion("3.0.0")
80+
>>> p.base_version
81+
'3.0.0'
82+
>>> p.release
83+
(3, 0, 0)
84+
>>> Version(*p.release)
85+
Version(major=3, minor=0, patch=0, prerelease=None, build=None)
86+
87+
* **With** ``pre`` **part only**
88+
89+
A prerelease exists in both versioning schemes. As such, both are
90+
a natural candidate. A prelease in PyPI version terms is the same
91+
as a "release candidate", or "rc".
92+
93+
.. code-block:: python
94+
95+
>>> p = PyPIVersion("2.1.6.pre5")
96+
>>> p.base_version
97+
'2.1.6'
98+
>>> p.pre
99+
('rc', 5)
100+
>>> pre = "".join([str(i) for i in p.pre])
101+
>>> Version(*p.release, pre)
102+
Version(major=2, minor=1, patch=6, prerelease='rc5', build=None)
103+
104+
* **With only development version**
105+
106+
Semver doesn't have a "development" version.
107+
However, we could use Semver's ``build`` part:
108+
109+
.. code-block:: python
110+
111+
>>> p = PyPIVersion("3.0.0.dev2")
112+
>>> p.base_version
113+
'3.0.0'
114+
>>> p.dev
115+
2
116+
>>> Version(*p.release, build=f"dev{p.dev}")
117+
Version(major=3, minor=0, patch=0, prerelease=None, build='dev2')
118+
119+
* **With a** ``post`` **version**
120+
121+
Semver doesn't know the concept of a post version. As such, there
122+
is currently no way to convert it reliably.
123+
124+
* **Any combination**
125+
126+
There is currently no way to convert a PyPI version which consists
127+
of, for example, development *and* post parts.
128+
129+
130+
.. _convert_semver_to_pypi:
131+
132+
From semver to PyPI
133+
-------------------
134+
135+
We distinguish between the following use cases:
136+
137+
138+
* **Major, minor, and patch**
139+
140+
.. code-block:: python
141+
142+
>>> from packaging.version import Version as PyPIVersion
143+
>>> from semver import Version
144+
145+
>>> v = Version(1, 2, 3)
146+
>>> PyPIVersion(str(v.finalize_version()))
147+
<Version('1.2.3')>
148+
149+
* **With** ``pre`` **part only**
150+
151+
.. code-block:: python
152+
153+
>>> v = Version(2, 1, 4, prerelease="rc1")
154+
>>> PyPIVersion(str(v))
155+
<Version('2.1.4rc1')>
156+
157+
* **With only development version**
158+
159+
.. code-block:: python
160+
161+
>>> v = Version(3, 2, 8, build="dev4")
162+
>>> PyPIVersion(f"{v.finalize_version()}{v.build}")
163+
<Version('3.2.8.dev4')>
164+
165+
If you are unsure about the parts of the version, the following
166+
function helps to convert the different parts:
167+
168+
.. code-block:: python
169+
170+
def convert2pypi(ver: semver.Version) -> packaging.version.Version:
171+
"""Converts a semver version into a version from PyPI
172+
173+
A semver prerelease will be converted into a
174+
prerelease of PyPI.
175+
A semver build will be converted into a development
176+
part of PyPI
177+
:param semver.Version ver: the semver version
178+
:return: a PyPI version
179+
"""
180+
v = ver.finalize_version()
181+
prerelease = ver.prerelease if ver.prerelease else ""
182+
build = ver.build if ver.build else ""
183+
return PyPIVersion(f"{v}{prerelease}{build}")
184+
185+
186+
.. _PEP440: https://www.python.org/dev/peps/pep-0440/

docs/advanced/index.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ Advanced topics
77

88
deal-with-invalid-versions
99
create-subclasses-from-version
10-
display-deprecation-warnings
10+
display-deprecation-warnings
11+
convert-pypi-to-semver

docs/conf.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717
# documentation root, use os.path.abspath to make it absolute, like shown here.
1818
#
1919
import codecs
20+
from datetime import date
2021
import os
2122
import re
2223
import sys
2324

2425
SRC_DIR = os.path.abspath("../src/")
2526
sys.path.insert(0, SRC_DIR)
2627
# from semver import __version__ # noqa: E402
28+
YEAR = date.today().year
2729

2830

2931
def read(*parts):
@@ -83,7 +85,7 @@ def find_version(*file_paths):
8385

8486
# General information about the project.
8587
project = "python-semver"
86-
copyright = "2018, Kostiantyn Rybnikov and all"
88+
copyright = f"{YEAR}, Kostiantyn Rybnikov and all"
8789
author = "Kostiantyn Rybnikov and all"
8890

8991
# The version info for the project you're documenting, acts as replacement for

docs/install.rst

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,13 @@ This line avoids surprises. You will get any updates within the major 2 release
1818
Keep in mind, as this line avoids any major version updates, you also will never
1919
get new exciting features or bug fixes.
2020

21-
You can add this line in your file :file:`setup.py`, :file:`requirements.txt`, or any other
22-
file that lists your dependencies.
21+
Same applies for semver v3, if you want to get all updates for the semver v3
22+
development line, but not a major update to semver v4::
23+
24+
semver>=3,<4
25+
26+
You can add this line in your file :file:`setup.py`, :file:`requirements.txt`,
27+
:file:`pyproject.toml`, or any other file that lists your dependencies.
2328

2429
Pip
2530
---
@@ -28,12 +33,12 @@ Pip
2833
2934
pip3 install semver
3035
31-
If you want to install this specific version (for example, 2.10.0), use the command :command:`pip`
36+
If you want to install this specific version (for example, 3.0.0), use the command :command:`pip`
3237
with an URL and its version:
3338

3439
.. parsed-literal::
3540
36-
pip3 install git+https://github.com/python-semver/python-semver.git@2.11.0
41+
pip3 install git+https://github.com/python-semver/python-semver.git@3.0.0
3742
3843
3944
Linux Distributions

docs/migration/replace-deprecated-functions.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ them with code which is compatible for future versions:
6060
.. code-block:: python
6161
6262
>>> s1 = semver.max_ver("1.2.3", "1.2.4")
63-
>>> s2 = str(max(map(Version.parse, ("1.2.3", "1.2.4"))))
63+
>>> s2 = max("1.2.3", "1.2.4", key=Version.parse)
6464
>>> s1 == s2
6565
True
6666
@@ -71,7 +71,7 @@ them with code which is compatible for future versions:
7171
.. code-block:: python
7272
7373
>>> s1 = semver.min_ver("1.2.3", "1.2.4")
74-
>>> s2 = str(min(map(Version.parse, ("1.2.3", "1.2.4"))))
74+
>>> s2 = min("1.2.3", "1.2.4", key=Version.parse)
7575
>>> s1 == s2
7676
True
7777

tests/conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from coerce import coerce # noqa:E402
1010
from semverwithvprefix import SemVerWithVPrefix # noqa:E402
11+
import packaging.version
1112

1213

1314
@pytest.fixture(autouse=True)
@@ -16,6 +17,7 @@ def add_semver(doctest_namespace):
1617
doctest_namespace["semver"] = semver
1718
doctest_namespace["coerce"] = coerce
1819
doctest_namespace["SemVerWithVPrefix"] = SemVerWithVPrefix
20+
doctest_namespace["PyPIVersion"] = packaging.version.Version
1921

2022

2123
@pytest.fixture

0 commit comments

Comments
 (0)
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