Skip to content

Commit c7abb3b

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 c7abb3b

File tree

7 files changed

+203
-8
lines changed

7 files changed

+203
-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: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
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+
From PyPI to semver
48+
-------------------
49+
50+
We distinguish between the following use cases:
51+
52+
53+
* **"Incomplete" versions**
54+
55+
If you only have a major part, this shouldn't be a problem.
56+
The initializer of :class:`semver.Version <semver.version.Version>` takes
57+
care to fill missing parts with zeros (except for major).
58+
59+
.. code-block:: python
60+
61+
>>> from packaging.version import Version as PyPIVersion
62+
>>> from semver import Version
63+
64+
>>> p = PyPIVersion("3.2")
65+
>>> p.release
66+
(3, 2)
67+
>>> Version(*p.release)
68+
Version(major=3, minor=2, patch=0, prerelease=None, build=None)
69+
70+
* **Major, minor, and patch**
71+
72+
This is the simplest and most compatible approch. Both versioning
73+
schemes are compatible without information loss.
74+
75+
.. code-block:: python
76+
77+
>>> p = PyPIVersion("3.0.0")
78+
>>> p.base_version
79+
'3.0.0'
80+
>>> p.release
81+
(3, 0, 0)
82+
>>> Version(*p.release)
83+
Version(major=3, minor=0, patch=0, prerelease=None, build=None)
84+
85+
* **With** ``pre`` **part only**
86+
87+
A prerelease exists in both versioning schemes. As such, both are
88+
a natural candidate. A prelease in PyPI version terms is the same
89+
as a "release candidate", or "rc".
90+
91+
.. code-block:: python
92+
93+
>>> p = PyPIVersion("2.1.6.pre5")
94+
>>> p.base_version
95+
'2.1.6'
96+
>>> p.pre
97+
('rc', 5)
98+
>>> pre = "".join([str(i) for i in p.pre])
99+
>>> Version(*p.release, pre)
100+
Version(major=2, minor=1, patch=6, prerelease='rc5', build=None)
101+
102+
* **With only development version**
103+
104+
Semver doesn't have a "development" version.
105+
However, we could use Semver's ``build`` part:
106+
107+
.. code-block:: python
108+
109+
>>> p = PyPIVersion("3.0.0.dev2")
110+
>>> p.base_version
111+
'3.0.0'
112+
>>> p.dev
113+
2
114+
>>> Version(*p.release, build=f"dev{p.dev}")
115+
Version(major=3, minor=0, patch=0, prerelease=None, build='dev2')
116+
117+
* **With a** ``post`` **version**
118+
119+
Semver doesn't know the concept of a post version. As such, there
120+
is currently no way to convert it reliably.
121+
122+
* **Any combination**
123+
124+
There is currently no way to convert a PyPI version which consists
125+
of, for example, development *and* post parts.
126+
127+
128+
129+
From semver to PyPI
130+
-------------------
131+
132+
We distinguish between the following use cases:
133+
134+
135+
* **Major, minor, and patch**
136+
137+
.. code-block:: python
138+
139+
>>> from packaging.version import Version as PyPIVersion
140+
>>> from semver import Version
141+
142+
>>> v = Version(1, 2, 3)
143+
>>> PyPIVersion(str(v.finalize_version()))
144+
<Version('1.2.3')>
145+
146+
* **With** ``pre`` **part only**
147+
148+
.. code-block:: python
149+
150+
>>> v = Version(2, 1, 4, prerelease="rc1")
151+
>>> PyPIVersion(str(v))
152+
<Version('2.1.4rc1')>
153+
154+
* **With only development version**
155+
156+
.. code-block:: python
157+
158+
>>> v = Version(3, 2, 8, build="dev4")
159+
>>> PyPIVersion(f"{v.finalize_version()}{v.build}")
160+
<Version('3.2.8.dev4')>
161+
162+
If you are unsure about the parts of the version, the following
163+
function helps to convert the different parts:
164+
165+
.. code-block:: python
166+
167+
def convert2pypi(ver: semver.Version) -> packaging.version.Version:
168+
"""Converts a semver version into a version from PyPI
169+
170+
A semver prerelease will be converted into a
171+
prerelease of PyPI.
172+
A semver build will be converted into a development
173+
part of PyPI
174+
:param semver.Version ver: the semver version
175+
:return: a PyPI version
176+
"""
177+
v = ver.finalize_version()
178+
prerelease = ver.prerelease if ver.prerelease else ""
179+
build = ver.build if ver.build else ""
180+
return PyPIVersion(f"{v}{prerelease}{build}")
181+
182+
183+
.. _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