From 769083c17773256e66b0c9a98d19c4f365b22d53 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Fri, 21 Jan 2022 08:04:20 +0100 Subject: [PATCH 01/22] Improve release procedure * Missing merge step * Introduce the subsection "Finish the release" --- release-procedure.md | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/release-procedure.md b/release-procedure.md index 251f23f0..d6c1701e 100644 --- a/release-procedure.md +++ b/release-procedure.md @@ -11,7 +11,8 @@ create a new release. * that all pull requests that should be included in this release are merged: . - * that continuous integration for latest build was passing: . + * that continuous integration for latest build was passing: + . 1. Create a new branch `release/`. @@ -81,6 +82,8 @@ create a new release. 1. Check if everything is okay with the wheel. Check also the web site `https://test.pypi.org/project//` +1. If everything looks fine, merge the pull request. + 1. Upload to PyPI: ```bash @@ -91,16 +94,24 @@ create a new release. 1. Go to https://pypi.org/project/semver/ to verify that new version is online and the page is rendered correctly. -1. Tag commit and push to GitHub using command line interface: +# Finish the release - ```bash - $ git tag -a x.x.x -m 'Version x.x.x' - $ git push python-semver master --tags - ``` +1. Create a tag: + + $ git tag -a x.x.x + + It's recommended to use the generated Tox output + from the Changelog. + +1. Push the tag: + + $ git push --tags 1. In [GitHub Release page](https://github.com/python-semver/python-semver/release) document the new release. - Usually it's enough to take it from a commit message or the tag description. + Select the tag from the last step and copy the + content of the tag description into the release + description. 1. Announce it in . From 098dccf699943dd0ae4e5e001e9b3f2c7cb40d42 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sat, 22 Jan 2022 19:39:38 +0100 Subject: [PATCH 02/22] Split usage section into different files The usage.rst file become quite big and it is hard to maintain. It's now splitted up into different files in the subfolder "usage/". The index.rst collects all these files. --- docs/index.rst | 2 +- docs/usage.rst | 789 ------------------ docs/usage/access-parts-of-a-version.rst | 43 + docs/usage/access-parts-through-index.rst | 48 ++ docs/usage/check-valid-semver-version.rst | 12 + docs/{ => usage}/coerce.py | 0 .../compare-versions-through-expression.rst | 26 + docs/usage/compare-versions.rst | 99 +++ .../convert-version-into-different-types.rst | 26 + docs/usage/create-a-version.rst | 100 +++ docs/usage/create-subclasses-from-version.rst | 33 + docs/usage/deal-with-invalid-versions.rst | 32 + docs/usage/determine-version-equality.rst | 25 + docs/usage/display-deprecation-warnings.rst | 34 + .../get-min-and-max-of-multiple-versions.rst | 51 ++ ...ncrease-parts-of-a-version_prereleases.rst | 22 + docs/usage/index.rst | 24 + docs/usage/parse-version-string.rst | 8 + docs/usage/raise-parts-of-a-version.rst | 30 + docs/usage/replace-deprecated-functions.rst | 110 +++ docs/usage/replace-parts-of-a-version.rst | 30 + docs/usage/semver-version.rst | 7 + docs/usage/semver_org-version.rst | 10 + docs/{ => usage}/semverwithvprefix.py | 0 tests/conftest.py | 2 +- 25 files changed, 772 insertions(+), 791 deletions(-) delete mode 100644 docs/usage.rst create mode 100644 docs/usage/access-parts-of-a-version.rst create mode 100644 docs/usage/access-parts-through-index.rst create mode 100644 docs/usage/check-valid-semver-version.rst rename docs/{ => usage}/coerce.py (100%) create mode 100644 docs/usage/compare-versions-through-expression.rst create mode 100644 docs/usage/compare-versions.rst create mode 100644 docs/usage/convert-version-into-different-types.rst create mode 100644 docs/usage/create-a-version.rst create mode 100644 docs/usage/create-subclasses-from-version.rst create mode 100644 docs/usage/deal-with-invalid-versions.rst create mode 100644 docs/usage/determine-version-equality.rst create mode 100644 docs/usage/display-deprecation-warnings.rst create mode 100644 docs/usage/get-min-and-max-of-multiple-versions.rst create mode 100644 docs/usage/increase-parts-of-a-version_prereleases.rst create mode 100644 docs/usage/index.rst create mode 100644 docs/usage/parse-version-string.rst create mode 100644 docs/usage/raise-parts-of-a-version.rst create mode 100644 docs/usage/replace-deprecated-functions.rst create mode 100644 docs/usage/replace-parts-of-a-version.rst create mode 100644 docs/usage/semver-version.rst create mode 100644 docs/usage/semver_org-version.rst rename docs/{ => usage}/semverwithvprefix.py (100%) diff --git a/docs/index.rst b/docs/index.rst index 405d9e27..3e2771a0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,7 +10,7 @@ Semver |version| -- Semantic Versioning :hidden: install - usage + usage/index migratetosemver3 development api diff --git a/docs/usage.rst b/docs/usage.rst deleted file mode 100644 index f6983d17..00000000 --- a/docs/usage.rst +++ /dev/null @@ -1,789 +0,0 @@ -Using semver -============ - -The :mod:`semver` module can store a version in the :class:`~semver.version.Version` class. -For historical reasons, a version can be also stored as a string or dictionary. - -Each type can be converted into the other, if the minimum requirements -are met. - - -Getting the Implemented semver.org Version ------------------------------------------- - -The semver.org page is the authoritative specification of how semantic -versioning is defined. -To know which version of semver.org is implemented in the semver library, -use the following constant:: - - >>> semver.SEMVER_SPEC_VERSION - '2.0.0' - - -Getting the Version of semver ------------------------------ - -To know the version of semver itself, use the following construct:: - - >>> semver.__version__ - '3.0.0-dev.3' - - -Creating a Version ------------------- - -.. versionchanged:: 3.0.0 - - The former :class:`~semver.version.VersionInfo` - has been renamed to :class:`~semver.version.Version`. - -The preferred way to create a new version is with the class -:class:`~semver.version.Version`. - -.. note:: - - In the previous major release semver 2 it was possible to - 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. - For details, see the sections :ref:`sec_replace_deprecated_functions` - and :ref:`sec_display_deprecation_warnings`. - -A :class:`~semver.version.Version` instance can be created in different ways: - -* From a Unicode string:: - - >>> from semver.version import Version - >>> Version.parse("3.4.5-pre.2+build.4") - Version(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') - >>> Version.parse(u"5.3.1") - Version(major=5, minor=3, patch=1, prerelease=None, build=None) - -* From a byte string:: - - >>> Version.parse(b"2.3.4") - Version(major=2, minor=3, patch=4, prerelease=None, build=None) - -* From individual parts by a dictionary:: - - >>> d = {'major': 3, 'minor': 4, 'patch': 5, 'prerelease': 'pre.2', 'build': 'build.4'} - >>> Version(**d) - Version(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') - - Keep in mind, the ``major``, ``minor``, ``patch`` parts has to - be positive integers or strings: - - >>> d = {'major': -3, 'minor': 4, 'patch': 5, 'prerelease': 'pre.2', 'build': 'build.4'} - >>> Version(**d) - Traceback (most recent call last): - ... - ValueError: 'major' is negative. A version can only be positive. - - As a minimum requirement, your dictionary needs at least the ``major`` - key, others can be omitted. You get a ``TypeError`` if your - dictionary contains invalid keys. - Only the keys ``major``, ``minor``, ``patch``, ``prerelease``, and ``build`` - are allowed. - -* From a tuple:: - - >>> t = (3, 5, 6) - >>> Version(*t) - Version(major=3, minor=5, patch=6, prerelease=None, build=None) - - You can pass either an integer or a string for ``major``, ``minor``, or - ``patch``:: - - >>> Version("3", "5", 6) - Version(major=3, minor=5, patch=6, prerelease=None, build=None) - -The old, deprecated module level functions are still available but -using them are discoraged. They are available to convert old code -to semver3. - -If you need them, they return different builtin objects (string and dictionary). -Keep in mind, once you have converted a version into a string or dictionary, -it's an ordinary builtin object. It's not a special version object like -the :class:`~semver.version.Version` class anymore. - -Depending on your use case, the following methods are available: - -* From individual version parts into a string - - In some cases you only need a string from your version data:: - - >>> semver.format_version(3, 4, 5, 'pre.2', 'build.4') - '3.4.5-pre.2+build.4' - -* From a string into a dictionary - - To access individual parts, you can use the function :func:`semver.parse`:: - - >>> semver.parse("3.4.5-pre.2+build.4") - OrderedDict([('major', 3), ('minor', 4), ('patch', 5), ('prerelease', 'pre.2'), ('build', 'build.4')]) - - If you pass an invalid version string you will get a :py:exc:`ValueError`:: - - >>> semver.parse("1.2") - Traceback (most recent call last): - ... - ValueError: 1.2 is not valid SemVer string - - -Parsing a Version String ------------------------- - -"Parsing" in this context means to identify the different parts in a string. -Use the function :func:`Version.parse `:: - - >>> Version.parse("3.4.5-pre.2+build.4") - Version(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') - - -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 `: - -.. code-block:: python - - >>> Version.isvalid("1.0.0") - True - >>> Version.isvalid("invalid") - False - - -.. _sec.properties.parts: - -Accessing Parts of a Version Through Names ------------------------------------------- - -The :class:`~semver.version.Version` class contains attributes to access the different -parts of a version: - -.. code-block:: python - - >>> v = Version.parse("3.4.5-pre.2+build.4") - >>> v.major - 3 - >>> v.minor - 4 - >>> v.patch - 5 - >>> v.prerelease - 'pre.2' - >>> v.build - 'build.4' - -However, the attributes are read-only. You cannot change any of the above attributes. -If you do, you get an :py:exc:`AttributeError`:: - - >>> v.minor = 5 - Traceback (most recent call last): - ... - AttributeError: attribute 'minor' is readonly - -If you need to replace different parts of a version, refer to section :ref:`sec.replace.parts`. - -In case you need the different parts of a version stepwise, iterate over the :class:`~semver.version.Version` instance:: - - >>> for item in Version.parse("3.4.5-pre.2+build.4"): - ... print(item) - 3 - 4 - 5 - pre.2 - build.4 - >>> list(Version.parse("3.4.5-pre.2+build.4")) - [3, 4, 5, 'pre.2', 'build.4'] - - -.. _sec.getitem.parts: - -Accessing Parts Through Index Numbers -------------------------------------- - -.. versionadded:: 2.10.0 - -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__`. - -For example, the ``major`` part can be accessed by index number 0 (zero). -Likewise the other parts: - -.. code-block:: python - - >>> ver = Version.parse("10.3.2-pre.5+build.10") - >>> ver[0], ver[1], ver[2], ver[3], ver[4] - (10, 3, 2, 'pre.5', 'build.10') - -If you need more than one part at the same time, use the slice notation: - -.. code-block:: python - - >>> ver[0:3] - (10, 3, 2) - -Or, as an alternative, you can pass a :func:`slice` object: - -.. code-block:: python - - >>> sl = slice(0,3) - >>> ver[sl] - (10, 3, 2) - -Negative numbers or undefined parts raise an :py:exc:`IndexError` exception: - -.. code-block:: python - - >>> ver = Version.parse("10.3.2") - >>> ver[3] - Traceback (most recent call last): - ... - IndexError: Version part undefined - >>> ver[-2] - Traceback (most recent call last): - ... - IndexError: Version index cannot be negative - -.. _sec.replace.parts: - -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:: - - >>> 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): - ... - TypeError: replace() got 1 unexpected keyword argument(s): invalidkey - - -.. _sec.convert.versions: - -Converting a Version instance into Different Types ------------------------------------------------------- - -Sometimes it is needed to convert a :class:`Version ` instance into -a different type. For example, for displaying or to access all parts. - -It is possible to convert a :class:`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 `:: - - >>> 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 `:: - - >>> v = Version(major=5, minor=4, patch=2) - >>> v.to_tuple() - (5, 4, 2, None, None) - - -Raising 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 - zero. Set ``prerelease`` and ``build`` to ``None``. -* :func:`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 - ``build`` to ``None``. -* :func:`Version.bump_prerelease `: raises the prerelease part and set - ``build`` to ``None``. -* :func:`Version.bump_build `: raises the build part. - -.. code-block:: python - - >>> str(Version.parse("3.4.5-pre.2+build.4").bump_major()) - '4.0.0' - >>> str(Version.parse("3.4.5-pre.2+build.4").bump_minor()) - '3.5.0' - >>> str(Version.parse("3.4.5-pre.2+build.4").bump_patch()) - '3.4.6' - >>> str(Version.parse("3.4.5-pre.2+build.4").bump_prerelease()) - '3.4.5-pre.3' - >>> str(Version.parse("3.4.5-pre.2+build.4").bump_build()) - '3.4.5-pre.2+build.5' - -Likewise the module level functions :func:`semver.bump_major`. - - -Increasing Parts of a Version Taking into Account Prereleases -------------------------------------------------------------- - -.. versionadded:: 2.10.0 - Added :func:`Version.next_version `. - -If you want to raise your version and take prereleases into account, -the function :func:`next_version ` -would perhaps a better fit. - - -.. code-block:: python - - >>> v = Version.parse("3.4.5-pre.2+build.4") - >>> str(v.next_version(part="prerelease")) - '3.4.5-pre.3' - >>> str(Version.parse("3.4.5-pre.2+build.4").next_version(part="patch")) - '3.4.5' - >>> str(Version.parse("3.4.5+build.4").next_version(part="patch")) - '3.4.5' - >>> str(Version.parse("0.1.4").next_version("prerelease")) - '0.1.5-rc.1' - - -Comparing Versions ------------------- - -To compare two versions depends on your type: - -* **Two strings** - - Use :func:`semver.compare`:: - - >>> semver.compare("1.0.0", "2.0.0") - -1 - >>> semver.compare("2.0.0", "1.0.0") - 1 - >>> semver.compare("2.0.0", "2.0.0") - 0 - - The return value is negative if ``version1 < version2``, zero if - ``version1 == version2`` and strictly positive if ``version1 > version2``. - -* **Two** :class:`Version ` **instances** - - Use the specific operator. Currently, the operators ``<``, - ``<=``, ``>``, ``>=``, ``==``, and ``!=`` are supported:: - - >>> v1 = Version.parse("3.4.5") - >>> v2 = Version.parse("3.5.1") - >>> v1 < v2 - True - >>> v1 > v2 - False - -* **A** :class:`Version ` **type and a** :func:`tuple` **or** :func:`list` - - Use the operator as with two :class:`Version ` types:: - - >>> v = Version.parse("3.4.5") - >>> v > (1, 0) - True - >>> v < [3, 5] - True - - The opposite does also work:: - - >>> (1, 0) < v - True - >>> [3, 5] > v - True - -* **A** :class:`Version ` **type and a** :func:`str` - - You can use also raw strings to compare:: - - >>> v > "1.0.0" - True - >>> v < "3.5.0" - True - - The opposite does also work:: - - >>> "1.0.0" < v - True - >>> "3.5.0" > v - True - - However, if you compare incomplete strings, you get a :py:exc:`ValueError` exception:: - - >>> v > "1.0" - Traceback (most recent call last): - ... - ValueError: 1.0 is not valid SemVer string - -* **A** :class:`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):: - - >>> v > dict(major=1) - True - - The opposite does also work:: - - >>> dict(major=1) < v - True - - If the dictionary contains unknown keys, you get a :py:exc:`TypeError` exception:: - - >>> v > dict(major=1, unknown=42) - Traceback (most recent call last): - ... - TypeError: ... got an unexpected keyword argument 'unknown' - - -Other types cannot be compared. - -If you need to convert some types into others, refer to :ref:`sec.convert.versions`. - -The use of these comparison operators also implies that you can use builtin -functions that leverage this capability; builtins including, but not limited to: :func:`max`, :func:`min` -(for examples, see :ref:`sec_max_min`) and :func:`sorted`. - - -Determining Version Equality ----------------------------- - -Version equality means for semver, that major, minor, patch, and prerelease -parts are equal in both versions you compare. The build part is ignored. -For example:: - - >>> v = Version.parse("1.2.3-rc4+1e4664d") - >>> v == "1.2.3-rc4+dedbeef" - True - -This also applies when a :class:`Version ` is a member of a set, or a -dictionary key:: - - >>> d = {} - >>> v1 = Version.parse("1.2.3-rc4+1e4664d") - >>> v2 = Version.parse("1.2.3-rc4+dedbeef") - >>> d[v1] = 1 - >>> d[v2] - 1 - >>> s = set() - >>> s.add(v1) - >>> v2 in s - True - - - -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: - -1. a version string -2. a match expression - -Currently, the match expression supports the following operators: - -* ``<`` smaller than -* ``>`` greater than -* ``>=`` greater or equal than -* ``<=`` smaller or equal than -* ``==`` equal -* ``!=`` not equal - -That gives you the following possibilities to express your condition: - -.. code-block:: python - - >>> semver.match("2.0.0", ">=1.0.0") - True - >>> semver.match("1.0.0", ">1.0.0") - False - -.. _sec_max_min: - -Getting Minimum and Maximum of Multiple Versions ------------------------------------------------- -.. versionchanged:: 2.10.2 - 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: - -.. code-block:: python - - >>> max([Version(0, 1, 0), Version(0, 2, 0), Version(0, 1, 3)]) - Version(major=0, minor=2, patch=0, prerelease=None, build=None) - >>> min([Version(0, 1, 0), Version(0, 2, 0), Version(0, 1, 3)]) - 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 `). - -For example, here are the maximum and minimum versions of a list of version strings: - -.. code-block:: python - - >>> max(['1.1.0', '1.2.0', '2.1.0', '0.5.10', '0.4.99'], key=Version.parse) - '2.1.0' - >>> min(['1.1.0', '1.2.0', '2.1.0', '0.5.10', '0.4.99'], key=Version.parse) - '0.4.99' - -And the same can be done with tuples: - -.. code-block:: python - - >>> max(map(lambda v: Version(*v), [(1, 1, 0), (1, 2, 0), (2, 1, 0), (0, 5, 10), (0, 4, 99)])).to_tuple() - (2, 1, 0, None, None) - >>> min(map(lambda v: Version(*v), [(1, 1, 0), (1, 2, 0), (2, 1, 0), (0, 5, 10), (0, 4, 99)])).to_tuple() - (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' - - -.. _sec_dealing_with_invalid_versions: - -Dealing with Invalid Versions ------------------------------ - -As semver follows the semver specification, it cannot parse version -strings which are considered "invalid" by that specification. The semver -library cannot know all the possible variations so you need to help the -library a bit. - -For example, if you have a version string ``v1.2`` would be an invalid -semver version. -However, "basic" version strings consisting of major, minor, -and patch part, can be easy to convert. The following function extract this -information and returns a tuple with two items: - -.. literalinclude:: coerce.py - :language: python - - -The function returns a *tuple*, containing a :class:`Version ` -instance or None as the first element and the rest as the second element. -The second element (the rest) can be used to make further adjustments. - -For example: - -.. code-block:: python - - >>> coerce("v1.2") - (Version(major=1, minor=2, patch=0, prerelease=None, build=None), '') - >>> coerce("v2.5.2-bla") - (Version(major=2, minor=5, patch=2, prerelease=None, build=None), '-bla') - - -.. _sec_replace_deprecated_functions: - -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. - -The deprecated functions can still be used in version 2.10.0 and above. In version 3 of -semver, the deprecated functions will be removed. - -The following list shows the deprecated functions and how you can replace -them with code which is compatible for future versions: - - -* :func:`semver.bump_major`, :func:`semver.bump_minor`, :func:`semver.bump_patch`, :func:`semver.bump_prerelease`, :func:`semver.bump_build` - - Replace them with the respective methods of the :class:`Version ` - class. - For example, the function :func:`semver.bump_major` is replaced by - :func:`semver.Version.bump_major` and calling the ``str(versionobject)``: - - .. code-block:: python - - >>> s1 = semver.bump_major("3.4.5") - >>> s2 = str(Version.parse("3.4.5").bump_major()) - >>> s1 == s2 - True - - Likewise with the other module level functions. - -* :func:`semver.finalize_version` - - Replace it with :func:`semver.Version.finalize_version`: - - .. code-block:: python - - >>> s1 = semver.finalize_version('1.2.3-rc.5') - >>> s2 = str(semver.Version.parse('1.2.3-rc.5').finalize_version()) - >>> s1 == s2 - True - -* :func:`semver.format_version` - - Replace it with ``str(versionobject)``: - - .. code-block:: python - - >>> s1 = semver.format_version(5, 4, 3, 'pre.2', 'build.1') - >>> s2 = str(Version(5, 4, 3, 'pre.2', 'build.1')) - >>> s1 == s2 - True - -* :func:`semver.max_ver` - - Replace it with ``max(version1, version2, ...)`` or ``max([version1, version2, ...])``: - - .. 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")))) - >>> s1 == s2 - True - -* :func:`semver.min_ver` - - Replace it with ``min(version1, version2, ...)`` or ``min([version1, version2, ...])``: - - .. 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")))) - >>> s1 == s2 - True - -* :func:`semver.parse` - - Replace it with :func:`semver.Version.parse` and - :func:`semver.Version.to_dict`: - - .. code-block:: python - - >>> v1 = semver.parse("1.2.3") - >>> v2 = Version.parse("1.2.3").to_dict() - >>> v1 == v2 - True - -* :func:`semver.parse_version_info` - - Replace it with :func:`semver.Version.parse`: - - .. code-block:: python - - >>> v1 = semver.parse_version_info("3.4.5") - >>> v2 = Version.parse("3.4.5") - >>> v1 == v2 - True - -* :func:`semver.replace` - - Replace it with :func:`semver.Version.replace`: - - .. code-block:: python - - >>> s1 = semver.replace("1.2.3", major=2, patch=10) - >>> s2 = str(Version.parse('1.2.3').replace(major=2, patch=10)) - >>> s1 == s2 - True - - -.. _sec_display_deprecation_warnings: - -Displaying Deprecation Warnings -------------------------------- - -By default, deprecation warnings are `ignored in Python `_. -This also affects semver's own warnings. - -It is recommended that you turn on deprecation warnings in your scripts. Use one of -the following methods: - -* Use the option `-Wd `_ - to enable default warnings: - - * Directly running the Python command:: - - $ python3 -Wd scriptname.py - - * Add the option in the shebang line (something like ``#!/usr/bin/python3``) - after the command:: - - #!/usr/bin/python3 -Wd - -* In your own scripts add a filter to ensure that *all* warnings are displayed: - - .. code-block:: python - - import warnings - warnings.simplefilter("default") - # Call your semver code - - For further details, see the section - `Overriding the default filter `_ - of the Python documentation. - - -.. _sec_creating_subclasses_from_versioninfo: - -Creating Subclasses from Version ------------------------------------- - -If you do not like creating functions to modify the behavior of semver -(as shown in section :ref:`sec_dealing_with_invalid_versions`), you can -also create a subclass of the :class:`Version ` class. - -For example, if you want to output a "v" prefix before a version, -but the other behavior is the same, use the following code: - -.. literalinclude:: semverwithvprefix.py - :language: python - :lines: 4- - - -The derived class :class:`SemVerWithVPrefix` can be used like -the original class: - -.. code-block:: python - - >>> v1 = SemVerWithVPrefix.parse("v1.2.3") - >>> assert str(v1) == "v1.2.3" - >>> print(v1) - v1.2.3 - >>> v2 = SemVerWithVPrefix.parse("v2.3.4") - >>> v2 > v1 - True - >>> bad = SemVerWithVPrefix.parse("1.2.4") - Traceback (most recent call last): - ... - ValueError: '1.2.4': not a valid semantic version tag. Must start with 'v' or 'V' diff --git a/docs/usage/access-parts-of-a-version.rst b/docs/usage/access-parts-of-a-version.rst new file mode 100644 index 00000000..4eb9274f --- /dev/null +++ b/docs/usage/access-parts-of-a-version.rst @@ -0,0 +1,43 @@ +.. _sec.properties.parts: + +Accessing Parts of a Version Through Names +========================================== + +The :class:`~semver.version.Version` class contains attributes to access the different +parts of a version: + +.. code-block:: python + + >>> v = Version.parse("3.4.5-pre.2+build.4") + >>> v.major + 3 + >>> v.minor + 4 + >>> v.patch + 5 + >>> v.prerelease + 'pre.2' + >>> v.build + 'build.4' + +However, the attributes are read-only. You cannot change any of the above attributes. +If you do, you get an :py:exc:`AttributeError`:: + + >>> v.minor = 5 + Traceback (most recent call last): + ... + AttributeError: attribute 'minor' is readonly + +If you need to replace different parts of a version, refer to section :ref:`sec.replace.parts`. + +In case you need the different parts of a version stepwise, iterate over the :class:`~semver.version.Version` instance:: + + >>> for item in Version.parse("3.4.5-pre.2+build.4"): + ... print(item) + 3 + 4 + 5 + pre.2 + build.4 + >>> list(Version.parse("3.4.5-pre.2+build.4")) + [3, 4, 5, 'pre.2', 'build.4'] diff --git a/docs/usage/access-parts-through-index.rst b/docs/usage/access-parts-through-index.rst new file mode 100644 index 00000000..a261fda4 --- /dev/null +++ b/docs/usage/access-parts-through-index.rst @@ -0,0 +1,48 @@ +.. _sec.getitem.parts: + +Accessing Parts Through Index Numbers +===================================== + +.. versionadded:: 2.10.0 + +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__`. + +For example, the ``major`` part can be accessed by index number 0 (zero). +Likewise the other parts: + +.. code-block:: python + + >>> ver = Version.parse("10.3.2-pre.5+build.10") + >>> ver[0], ver[1], ver[2], ver[3], ver[4] + (10, 3, 2, 'pre.5', 'build.10') + +If you need more than one part at the same time, use the slice notation: + +.. code-block:: python + + >>> ver[0:3] + (10, 3, 2) + +Or, as an alternative, you can pass a :func:`slice` object: + +.. code-block:: python + + >>> sl = slice(0,3) + >>> ver[sl] + (10, 3, 2) + +Negative numbers or undefined parts raise an :py:exc:`IndexError` exception: + +.. code-block:: python + + >>> ver = Version.parse("10.3.2") + >>> ver[3] + Traceback (most recent call last): + ... + IndexError: Version part undefined + >>> ver[-2] + Traceback (most recent call last): + ... + IndexError: Version index cannot be negative diff --git a/docs/usage/check-valid-semver-version.rst b/docs/usage/check-valid-semver-version.rst new file mode 100644 index 00000000..7aa9615b --- /dev/null +++ b/docs/usage/check-valid-semver-version.rst @@ -0,0 +1,12 @@ +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 `: + +.. code-block:: python + + >>> Version.isvalid("1.0.0") + True + >>> Version.isvalid("invalid") + False diff --git a/docs/coerce.py b/docs/usage/coerce.py similarity index 100% rename from docs/coerce.py rename to docs/usage/coerce.py diff --git a/docs/usage/compare-versions-through-expression.rst b/docs/usage/compare-versions-through-expression.rst new file mode 100644 index 00000000..43a5a182 --- /dev/null +++ b/docs/usage/compare-versions-through-expression.rst @@ -0,0 +1,26 @@ +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: + +1. a version string +2. a match expression + +Currently, the match expression supports the following operators: + +* ``<`` smaller than +* ``>`` greater than +* ``>=`` greater or equal than +* ``<=`` smaller or equal than +* ``==`` equal +* ``!=`` not equal + +That gives you the following possibilities to express your condition: + +.. code-block:: python + + >>> semver.match("2.0.0", ">=1.0.0") + True + >>> semver.match("1.0.0", ">1.0.0") + False diff --git a/docs/usage/compare-versions.rst b/docs/usage/compare-versions.rst new file mode 100644 index 00000000..b42ba1a7 --- /dev/null +++ b/docs/usage/compare-versions.rst @@ -0,0 +1,99 @@ +Comparing Versions +================== + +To compare two versions depends on your type: + +* **Two strings** + + Use :func:`semver.compare`:: + + >>> semver.compare("1.0.0", "2.0.0") + -1 + >>> semver.compare("2.0.0", "1.0.0") + 1 + >>> semver.compare("2.0.0", "2.0.0") + 0 + + The return value is negative if ``version1 < version2``, zero if + ``version1 == version2`` and strictly positive if ``version1 > version2``. + +* **Two** :class:`Version ` **instances** + + Use the specific operator. Currently, the operators ``<``, + ``<=``, ``>``, ``>=``, ``==``, and ``!=`` are supported:: + + >>> v1 = Version.parse("3.4.5") + >>> v2 = Version.parse("3.5.1") + >>> v1 < v2 + True + >>> v1 > v2 + False + +* **A** :class:`Version ` **type and a** :func:`tuple` **or** :func:`list` + + Use the operator as with two :class:`Version ` types:: + + >>> v = Version.parse("3.4.5") + >>> v > (1, 0) + True + >>> v < [3, 5] + True + + The opposite does also work:: + + >>> (1, 0) < v + True + >>> [3, 5] > v + True + +* **A** :class:`Version ` **type and a** :func:`str` + + You can use also raw strings to compare:: + + >>> v > "1.0.0" + True + >>> v < "3.5.0" + True + + The opposite does also work:: + + >>> "1.0.0" < v + True + >>> "3.5.0" > v + True + + However, if you compare incomplete strings, you get a :py:exc:`ValueError` exception:: + + >>> v > "1.0" + Traceback (most recent call last): + ... + ValueError: 1.0 is not valid SemVer string + +* **A** :class:`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):: + + >>> v > dict(major=1) + True + + The opposite does also work:: + + >>> dict(major=1) < v + True + + If the dictionary contains unknown keys, you get a :py:exc:`TypeError` exception:: + + >>> v > dict(major=1, unknown=42) + Traceback (most recent call last): + ... + TypeError: ... got an unexpected keyword argument 'unknown' + + +Other types cannot be compared. + +If you need to convert some types into others, refer to :ref:`sec.convert.versions`. + +The use of these comparison operators also implies that you can use builtin +functions that leverage this capability; builtins including, but not limited to: :func:`max`, :func:`min` +(for examples, see :ref:`sec_max_min`) and :func:`sorted`. diff --git a/docs/usage/convert-version-into-different-types.rst b/docs/usage/convert-version-into-different-types.rst new file mode 100644 index 00000000..976283d8 --- /dev/null +++ b/docs/usage/convert-version-into-different-types.rst @@ -0,0 +1,26 @@ +.. _sec.convert.versions: + +Converting a Version instance into Different Types +================================================== + +Sometimes it is needed to convert a :class:`Version ` instance into +a different type. For example, for displaying or to access all parts. + +It is possible to convert a :class:`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 `:: + + >>> 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 `:: + + >>> v = Version(major=5, minor=4, patch=2) + >>> v.to_tuple() + (5, 4, 2, None, None) diff --git a/docs/usage/create-a-version.rst b/docs/usage/create-a-version.rst new file mode 100644 index 00000000..3acb4c03 --- /dev/null +++ b/docs/usage/create-a-version.rst @@ -0,0 +1,100 @@ +Creating a Version +================== + +.. versionchanged:: 3.0.0 + + The former :class:`~semver.version.VersionInfo` + has been renamed to :class:`~semver.version.Version`. + +The preferred way to create a new version is with the class +:class:`~semver.version.Version`. + +.. note:: + + In the previous major release semver 2 it was possible to + 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. + For details, see the sections :ref:`sec_replace_deprecated_functions` + and :ref:`sec_display_deprecation_warnings`. + +A :class:`~semver.version.Version` instance can be created in different ways: + +* From a Unicode string:: + + >>> from semver.version import Version + >>> Version.parse("3.4.5-pre.2+build.4") + Version(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') + >>> Version.parse(u"5.3.1") + Version(major=5, minor=3, patch=1, prerelease=None, build=None) + +* From a byte string:: + + >>> Version.parse(b"2.3.4") + Version(major=2, minor=3, patch=4, prerelease=None, build=None) + +* From individual parts by a dictionary:: + + >>> d = {'major': 3, 'minor': 4, 'patch': 5, 'prerelease': 'pre.2', 'build': 'build.4'} + >>> Version(**d) + Version(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') + + Keep in mind, the ``major``, ``minor``, ``patch`` parts has to + be positive integers or strings: + + >>> d = {'major': -3, 'minor': 4, 'patch': 5, 'prerelease': 'pre.2', 'build': 'build.4'} + >>> Version(**d) + Traceback (most recent call last): + ... + ValueError: 'major' is negative. A version can only be positive. + + As a minimum requirement, your dictionary needs at least the ``major`` + key, others can be omitted. You get a ``TypeError`` if your + dictionary contains invalid keys. + Only the keys ``major``, ``minor``, ``patch``, ``prerelease``, and ``build`` + are allowed. + +* From a tuple:: + + >>> t = (3, 5, 6) + >>> Version(*t) + Version(major=3, minor=5, patch=6, prerelease=None, build=None) + + You can pass either an integer or a string for ``major``, ``minor``, or + ``patch``:: + + >>> Version("3", "5", 6) + Version(major=3, minor=5, patch=6, prerelease=None, build=None) + +The old, deprecated module level functions are still available but +using them are discoraged. They are available to convert old code +to semver3. + +If you need them, they return different builtin objects (string and dictionary). +Keep in mind, once you have converted a version into a string or dictionary, +it's an ordinary builtin object. It's not a special version object like +the :class:`~semver.version.Version` class anymore. + +Depending on your use case, the following methods are available: + +* From individual version parts into a string + + In some cases you only need a string from your version data:: + + >>> semver.format_version(3, 4, 5, 'pre.2', 'build.4') + '3.4.5-pre.2+build.4' + +* From a string into a dictionary + + To access individual parts, you can use the function :func:`semver.parse`:: + + >>> semver.parse("3.4.5-pre.2+build.4") + OrderedDict([('major', 3), ('minor', 4), ('patch', 5), ('prerelease', 'pre.2'), ('build', 'build.4')]) + + If you pass an invalid version string you will get a :py:exc:`ValueError`:: + + >>> semver.parse("1.2") + Traceback (most recent call last): + ... + ValueError: 1.2 is not valid SemVer string diff --git a/docs/usage/create-subclasses-from-version.rst b/docs/usage/create-subclasses-from-version.rst new file mode 100644 index 00000000..7c97ee6f --- /dev/null +++ b/docs/usage/create-subclasses-from-version.rst @@ -0,0 +1,33 @@ +.. _sec_creating_subclasses_from_versioninfo: + +Creating Subclasses from Version +================================ + +If you do not like creating functions to modify the behavior of semver +(as shown in section :ref:`sec_dealing_with_invalid_versions`), you can +also create a subclass of the :class:`Version ` class. + +For example, if you want to output a "v" prefix before a version, +but the other behavior is the same, use the following code: + +.. literalinclude:: semverwithvprefix.py + :language: python + :lines: 4- + + +The derived class :class:`SemVerWithVPrefix` can be used like +the original class: + +.. code-block:: python + + >>> v1 = SemVerWithVPrefix.parse("v1.2.3") + >>> assert str(v1) == "v1.2.3" + >>> print(v1) + v1.2.3 + >>> v2 = SemVerWithVPrefix.parse("v2.3.4") + >>> v2 > v1 + True + >>> bad = SemVerWithVPrefix.parse("1.2.4") + Traceback (most recent call last): + ... + ValueError: '1.2.4': not a valid semantic version tag. Must start with 'v' or 'V' diff --git a/docs/usage/deal-with-invalid-versions.rst b/docs/usage/deal-with-invalid-versions.rst new file mode 100644 index 00000000..ee5e5704 --- /dev/null +++ b/docs/usage/deal-with-invalid-versions.rst @@ -0,0 +1,32 @@ +.. _sec_dealing_with_invalid_versions: + +Dealing with Invalid Versions +============================= + +As semver follows the semver specification, it cannot parse version +strings which are considered "invalid" by that specification. The semver +library cannot know all the possible variations so you need to help the +library a bit. + +For example, if you have a version string ``v1.2`` would be an invalid +semver version. +However, "basic" version strings consisting of major, minor, +and patch part, can be easy to convert. The following function extract this +information and returns a tuple with two items: + +.. literalinclude:: coerce.py + :language: python + + +The function returns a *tuple*, containing a :class:`Version ` +instance or None as the first element and the rest as the second element. +The second element (the rest) can be used to make further adjustments. + +For example: + +.. code-block:: python + + >>> coerce("v1.2") + (Version(major=1, minor=2, patch=0, prerelease=None, build=None), '') + >>> coerce("v2.5.2-bla") + (Version(major=2, minor=5, patch=2, prerelease=None, build=None), '-bla') diff --git a/docs/usage/determine-version-equality.rst b/docs/usage/determine-version-equality.rst new file mode 100644 index 00000000..211743c9 --- /dev/null +++ b/docs/usage/determine-version-equality.rst @@ -0,0 +1,25 @@ +Determining Version Equality +============================ + +Version equality means for semver, that major, minor, patch, and prerelease +parts are equal in both versions you compare. The build part is ignored. +For example:: + + >>> v = Version.parse("1.2.3-rc4+1e4664d") + >>> v == "1.2.3-rc4+dedbeef" + True + +This also applies when a :class:`Version ` is a member of a set, or a +dictionary key:: + + >>> d = {} + >>> v1 = Version.parse("1.2.3-rc4+1e4664d") + >>> v2 = Version.parse("1.2.3-rc4+dedbeef") + >>> d[v1] = 1 + >>> d[v2] + 1 + >>> s = set() + >>> s.add(v1) + >>> v2 in s + True + diff --git a/docs/usage/display-deprecation-warnings.rst b/docs/usage/display-deprecation-warnings.rst new file mode 100644 index 00000000..825bbe76 --- /dev/null +++ b/docs/usage/display-deprecation-warnings.rst @@ -0,0 +1,34 @@ +.. _sec_display_deprecation_warnings: + +Displaying Deprecation Warnings +=============================== + +By default, deprecation warnings are `ignored in Python `_. +This also affects semver's own warnings. + +It is recommended that you turn on deprecation warnings in your scripts. Use one of +the following methods: + +* Use the option `-Wd `_ + to enable default warnings: + + * Directly running the Python command:: + + $ python3 -Wd scriptname.py + + * Add the option in the shebang line (something like ``#!/usr/bin/python3``) + after the command:: + + #!/usr/bin/python3 -Wd + +* In your own scripts add a filter to ensure that *all* warnings are displayed: + + .. code-block:: python + + import warnings + warnings.simplefilter("default") + # Call your semver code + + For further details, see the section + `Overriding the default filter `_ + of the Python documentation. diff --git a/docs/usage/get-min-and-max-of-multiple-versions.rst b/docs/usage/get-min-and-max-of-multiple-versions.rst new file mode 100644 index 00000000..266ee50b --- /dev/null +++ b/docs/usage/get-min-and-max-of-multiple-versions.rst @@ -0,0 +1,51 @@ +.. _sec_max_min: + +Getting Minimum and Maximum of Multiple Versions +================================================ + +.. versionchanged:: 2.10.2 + 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: + +.. code-block:: python + + >>> max([Version(0, 1, 0), Version(0, 2, 0), Version(0, 1, 3)]) + Version(major=0, minor=2, patch=0, prerelease=None, build=None) + >>> min([Version(0, 1, 0), Version(0, 2, 0), Version(0, 1, 3)]) + 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 `). + +For example, here are the maximum and minimum versions of a list of version strings: + +.. code-block:: python + + >>> max(['1.1.0', '1.2.0', '2.1.0', '0.5.10', '0.4.99'], key=Version.parse) + '2.1.0' + >>> min(['1.1.0', '1.2.0', '2.1.0', '0.5.10', '0.4.99'], key=Version.parse) + '0.4.99' + +And the same can be done with tuples: + +.. code-block:: python + + >>> max(map(lambda v: Version(*v), [(1, 1, 0), (1, 2, 0), (2, 1, 0), (0, 5, 10), (0, 4, 99)])).to_tuple() + (2, 1, 0, None, None) + >>> min(map(lambda v: Version(*v), [(1, 1, 0), (1, 2, 0), (2, 1, 0), (0, 5, 10), (0, 4, 99)])).to_tuple() + (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 new file mode 100644 index 00000000..98283937 --- /dev/null +++ b/docs/usage/increase-parts-of-a-version_prereleases.rst @@ -0,0 +1,22 @@ +Increasing Parts of a Version Taking into Account Prereleases +============================================================= + +.. versionadded:: 2.10.0 + Added :func:`Version.next_version `. + +If you want to raise your version and take prereleases into account, +the function :func:`next_version ` +would perhaps a better fit. + + +.. code-block:: python + + >>> v = Version.parse("3.4.5-pre.2+build.4") + >>> str(v.next_version(part="prerelease")) + '3.4.5-pre.3' + >>> str(Version.parse("3.4.5-pre.2+build.4").next_version(part="patch")) + '3.4.5' + >>> str(Version.parse("3.4.5+build.4").next_version(part="patch")) + '3.4.5' + >>> str(Version.parse("0.1.4").next_version("prerelease")) + '0.1.5-rc.1' diff --git a/docs/usage/index.rst b/docs/usage/index.rst new file mode 100644 index 00000000..b843809c --- /dev/null +++ b/docs/usage/index.rst @@ -0,0 +1,24 @@ +Using semver +============ + +.. toctree:: + + semver_org-version + semver-version + create-a-version + parse-version-string + check-valid-semver-version + access-parts-of-a-version + access-parts-through-index + replace-parts-of-a-version + convert-version-into-different-types + raise-parts-of-a-version + increase-parts-of-a-version_prereleases + compare-versions + determine-version-equality + compare-versions-through-expression + get-min-and-max-of-multiple-versions + deal-with-invalid-versions + replace-deprecated-functions + display-deprecation-warnings + create-subclasses-from-version diff --git a/docs/usage/parse-version-string.rst b/docs/usage/parse-version-string.rst new file mode 100644 index 00000000..ddd421e7 --- /dev/null +++ b/docs/usage/parse-version-string.rst @@ -0,0 +1,8 @@ +Parsing a Version String +======================== + +"Parsing" in this context means to identify the different parts in a string. +Use the function :func:`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 new file mode 100644 index 00000000..cc62fffb --- /dev/null +++ b/docs/usage/raise-parts-of-a-version.rst @@ -0,0 +1,30 @@ +Raising 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 + zero. Set ``prerelease`` and ``build`` to ``None``. +* :func:`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 + ``build`` to ``None``. +* :func:`Version.bump_prerelease `: raises the prerelease part and set + ``build`` to ``None``. +* :func:`Version.bump_build `: raises the build part. + +.. code-block:: python + + >>> str(Version.parse("3.4.5-pre.2+build.4").bump_major()) + '4.0.0' + >>> str(Version.parse("3.4.5-pre.2+build.4").bump_minor()) + '3.5.0' + >>> str(Version.parse("3.4.5-pre.2+build.4").bump_patch()) + '3.4.6' + >>> str(Version.parse("3.4.5-pre.2+build.4").bump_prerelease()) + '3.4.5-pre.3' + >>> str(Version.parse("3.4.5-pre.2+build.4").bump_build()) + '3.4.5-pre.2+build.5' + +Likewise the module level functions :func:`semver.bump_major`. diff --git a/docs/usage/replace-deprecated-functions.rst b/docs/usage/replace-deprecated-functions.rst new file mode 100644 index 00000000..a8f2f8f6 --- /dev/null +++ b/docs/usage/replace-deprecated-functions.rst @@ -0,0 +1,110 @@ +.. _sec_replace_deprecated_functions: + +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. + +The deprecated functions can still be used in version 2.10.0 and above. In version 3 of +semver, the deprecated functions will be removed. + +The following list shows the deprecated functions and how you can replace +them with code which is compatible for future versions: + + +* :func:`semver.bump_major`, :func:`semver.bump_minor`, :func:`semver.bump_patch`, :func:`semver.bump_prerelease`, :func:`semver.bump_build` + + Replace them with the respective methods of the :class:`Version ` + class. + For example, the function :func:`semver.bump_major` is replaced by + :func:`semver.Version.bump_major` and calling the ``str(versionobject)``: + + .. code-block:: python + + >>> s1 = semver.bump_major("3.4.5") + >>> s2 = str(Version.parse("3.4.5").bump_major()) + >>> s1 == s2 + True + + Likewise with the other module level functions. + +* :func:`semver.finalize_version` + + Replace it with :func:`semver.Version.finalize_version`: + + .. code-block:: python + + >>> s1 = semver.finalize_version('1.2.3-rc.5') + >>> s2 = str(semver.Version.parse('1.2.3-rc.5').finalize_version()) + >>> s1 == s2 + True + +* :func:`semver.format_version` + + Replace it with ``str(versionobject)``: + + .. code-block:: python + + >>> s1 = semver.format_version(5, 4, 3, 'pre.2', 'build.1') + >>> s2 = str(Version(5, 4, 3, 'pre.2', 'build.1')) + >>> s1 == s2 + True + +* :func:`semver.max_ver` + + Replace it with ``max(version1, version2, ...)`` or ``max([version1, version2, ...])``: + + .. 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")))) + >>> s1 == s2 + True + +* :func:`semver.min_ver` + + Replace it with ``min(version1, version2, ...)`` or ``min([version1, version2, ...])``: + + .. 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")))) + >>> s1 == s2 + True + +* :func:`semver.parse` + + Replace it with :func:`semver.Version.parse` and + :func:`semver.Version.to_dict`: + + .. code-block:: python + + >>> v1 = semver.parse("1.2.3") + >>> v2 = Version.parse("1.2.3").to_dict() + >>> v1 == v2 + True + +* :func:`semver.parse_version_info` + + Replace it with :func:`semver.Version.parse`: + + .. code-block:: python + + >>> v1 = semver.parse_version_info("3.4.5") + >>> v2 = Version.parse("3.4.5") + >>> v1 == v2 + True + +* :func:`semver.replace` + + Replace it with :func:`semver.Version.replace`: + + .. code-block:: python + + >>> s1 = semver.replace("1.2.3", major=2, patch=10) + >>> s2 = str(Version.parse('1.2.3').replace(major=2, patch=10)) + >>> s1 == s2 + True diff --git a/docs/usage/replace-parts-of-a-version.rst b/docs/usage/replace-parts-of-a-version.rst new file mode 100644 index 00000000..b6c38865 --- /dev/null +++ b/docs/usage/replace-parts-of-a-version.rst @@ -0,0 +1,30 @@ +.. _sec.replace.parts: + +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:: + + >>> 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): + ... + TypeError: replace() got 1 unexpected keyword argument(s): invalidkey diff --git a/docs/usage/semver-version.rst b/docs/usage/semver-version.rst new file mode 100644 index 00000000..b3d2c274 --- /dev/null +++ b/docs/usage/semver-version.rst @@ -0,0 +1,7 @@ +Getting the Version of semver +============================= + +To know the version of semver itself, use the following construct:: + + >>> semver.__version__ + '3.0.0-dev.3' diff --git a/docs/usage/semver_org-version.rst b/docs/usage/semver_org-version.rst new file mode 100644 index 00000000..b0a1ad87 --- /dev/null +++ b/docs/usage/semver_org-version.rst @@ -0,0 +1,10 @@ +Getting the Implemented semver.org Version +========================================== + +The semver.org page is the authoritative specification of how semantic +versioning is defined. +To know which version of semver.org is implemented in the semver library, +use the following constant:: + + >>> semver.SEMVER_SPEC_VERSION + '2.0.0' diff --git a/docs/semverwithvprefix.py b/docs/usage/semverwithvprefix.py similarity index 100% rename from docs/semverwithvprefix.py rename to docs/usage/semverwithvprefix.py diff --git a/tests/conftest.py b/tests/conftest.py index 0450e0ee..eb1911d1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,7 @@ import semver -sys.path.insert(0, "docs") +sys.path.insert(0, "docs/usage") from coerce import coerce # noqa:E402 from semverwithvprefix import SemVerWithVPrefix # noqa:E402 From 68553feff8e886250c40df12a2af45daea40c138 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sun, 23 Jan 2022 20:26:56 +0100 Subject: [PATCH 03/22] Add missing changelog file for #350 --- changelog.d/350.doc.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelog.d/350.doc.rst diff --git a/changelog.d/350.doc.rst b/changelog.d/350.doc.rst new file mode 100644 index 00000000..2fa92f0a --- /dev/null +++ b/changelog.d/350.doc.rst @@ -0,0 +1,2 @@ +Restructure usage section. Create subdirectory "usage/" and splitted +all section into different files. From 838527bf6bb34d3ed2ecbb2bc8b418af3012e44d Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sun, 23 Jan 2022 16:53:40 +0100 Subject: [PATCH 04/22] Introduce new topics for doc * Move some files that better fit into an "Advanced topic" * Introduce "Migration to semver3" topic --- CONTRIBUTING.rst | 4 ++-- changelog.d/351.doc.rst | 4 ++++ docs/{usage => advanced}/coerce.py | 0 .../create-subclasses-from-version.rst | 0 .../{usage => advanced}/deal-with-invalid-versions.rst | 0 .../display-deprecation-warnings.rst | 0 docs/advanced/index.rst | 10 ++++++++++ docs/{usage => advanced}/semverwithvprefix.py | 3 ++- docs/changelog.rst | 2 ++ docs/index.rst | 4 +++- docs/migration/index.rst | 9 +++++++++ docs/{ => migration}/migratetosemver3.rst | 4 ++-- .../replace-deprecated-functions.rst | 0 docs/usage/index.rst | 4 ---- tests/coerce.py | 1 + tests/conftest.py | 2 +- tests/semverwithvprefix.py | 1 + 17 files changed, 37 insertions(+), 11 deletions(-) create mode 100644 changelog.d/351.doc.rst rename docs/{usage => advanced}/coerce.py (100%) rename docs/{usage => advanced}/create-subclasses-from-version.rst (100%) rename docs/{usage => advanced}/deal-with-invalid-versions.rst (100%) rename docs/{usage => advanced}/display-deprecation-warnings.rst (100%) create mode 100644 docs/advanced/index.rst rename docs/{usage => advanced}/semverwithvprefix.py (86%) create mode 100644 docs/migration/index.rst rename docs/{ => migration}/migratetosemver3.rst (93%) rename docs/{usage => migration}/replace-deprecated-functions.rst (100%) create mode 120000 tests/coerce.py create mode 120000 tests/semverwithvprefix.py diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 5fd75ab2..e0210cc9 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -64,7 +64,7 @@ We recommend the following workflow: a. Write test cases and run the complete test suite, see :ref:`testsuite` for details. - b. Write a changelog entry, see section :ref:`changelog`. + 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 @@ -214,7 +214,7 @@ documentation includes: edge cases. -.. _changelog: +.. _add-changelog: Adding a Changelog Entry ------------------------ diff --git a/changelog.d/351.doc.rst b/changelog.d/351.doc.rst new file mode 100644 index 00000000..0b5199fa --- /dev/null +++ b/changelog.d/351.doc.rst @@ -0,0 +1,4 @@ +Introduce new topics for: + +* "Migration to semver3" +* "Advanced topics" diff --git a/docs/usage/coerce.py b/docs/advanced/coerce.py similarity index 100% rename from docs/usage/coerce.py rename to docs/advanced/coerce.py diff --git a/docs/usage/create-subclasses-from-version.rst b/docs/advanced/create-subclasses-from-version.rst similarity index 100% rename from docs/usage/create-subclasses-from-version.rst rename to docs/advanced/create-subclasses-from-version.rst diff --git a/docs/usage/deal-with-invalid-versions.rst b/docs/advanced/deal-with-invalid-versions.rst similarity index 100% rename from docs/usage/deal-with-invalid-versions.rst rename to docs/advanced/deal-with-invalid-versions.rst diff --git a/docs/usage/display-deprecation-warnings.rst b/docs/advanced/display-deprecation-warnings.rst similarity index 100% rename from docs/usage/display-deprecation-warnings.rst rename to docs/advanced/display-deprecation-warnings.rst diff --git a/docs/advanced/index.rst b/docs/advanced/index.rst new file mode 100644 index 00000000..de7da166 --- /dev/null +++ b/docs/advanced/index.rst @@ -0,0 +1,10 @@ +Advanced topics +=============== + + + +.. toctree:: + + deal-with-invalid-versions + create-subclasses-from-version + display-deprecation-warnings \ No newline at end of file diff --git a/docs/usage/semverwithvprefix.py b/docs/advanced/semverwithvprefix.py similarity index 86% rename from docs/usage/semverwithvprefix.py rename to docs/advanced/semverwithvprefix.py index 5e375031..4395a95e 100644 --- a/docs/usage/semverwithvprefix.py +++ b/docs/advanced/semverwithvprefix.py @@ -17,7 +17,8 @@ def parse(cls, version: str) -> "SemVerWithVPrefix": """ if not version[0] in ("v", "V"): raise ValueError( - "{v!r}: not a valid semantic version tag. Must start with 'v' or 'V'".format( + "{v!r}: not a valid semantic version tag. " + "Must start with 'v' or 'V'".format( v=version ) ) diff --git a/docs/changelog.rst b/docs/changelog.rst index 565b0521..e1e273b4 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1 +1,3 @@ +.. _change-log: + .. include:: ../CHANGELOG.rst diff --git a/docs/index.rst b/docs/index.rst index 3e2771a0..deac1cd0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -11,7 +11,8 @@ Semver |version| -- Semantic Versioning install usage/index - migratetosemver3 + migration/index + advanced/index development api @@ -31,6 +32,7 @@ Semver |version| -- Semantic Versioning changelog changelog-semver2 + Indices and Tables ================== diff --git a/docs/migration/index.rst b/docs/migration/index.rst new file mode 100644 index 00000000..c6af7c05 --- /dev/null +++ b/docs/migration/index.rst @@ -0,0 +1,9 @@ +Migrating to semver3 +==================== + + +.. toctree:: + :maxdepth: 1 + + migratetosemver3 + replace-deprecated-functions.rst diff --git a/docs/migratetosemver3.rst b/docs/migration/migratetosemver3.rst similarity index 93% rename from docs/migratetosemver3.rst rename to docs/migration/migratetosemver3.rst index d977bc03..f869cad3 100644 --- a/docs/migratetosemver3.rst +++ b/docs/migration/migratetosemver3.rst @@ -3,7 +3,7 @@ Migrating from semver2 to semver3 ================================= -This chapter describes the visible differences for +This document describes the visible differences for users and how your code stays compatible for semver3. Although the development team tries to make the transition @@ -11,7 +11,7 @@ to semver3 as smooth as possible, at some point change is inevitable. For a more detailed overview of all the changes, refer -to our :ref:`changelog`. +to our :ref:`change-log`. Use Version instead of VersionInfo diff --git a/docs/usage/replace-deprecated-functions.rst b/docs/migration/replace-deprecated-functions.rst similarity index 100% rename from docs/usage/replace-deprecated-functions.rst rename to docs/migration/replace-deprecated-functions.rst diff --git a/docs/usage/index.rst b/docs/usage/index.rst index b843809c..ddfc2284 100644 --- a/docs/usage/index.rst +++ b/docs/usage/index.rst @@ -18,7 +18,3 @@ Using semver determine-version-equality compare-versions-through-expression get-min-and-max-of-multiple-versions - deal-with-invalid-versions - replace-deprecated-functions - display-deprecation-warnings - create-subclasses-from-version diff --git a/tests/coerce.py b/tests/coerce.py new file mode 120000 index 00000000..e79106a2 --- /dev/null +++ b/tests/coerce.py @@ -0,0 +1 @@ +../docs/advanced/coerce.py \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index eb1911d1..40b56ab1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,7 @@ import semver -sys.path.insert(0, "docs/usage") +# sys.path.insert(0, "docs/usage") from coerce import coerce # noqa:E402 from semverwithvprefix import SemVerWithVPrefix # noqa:E402 diff --git a/tests/semverwithvprefix.py b/tests/semverwithvprefix.py new file mode 120000 index 00000000..d1a8d995 --- /dev/null +++ b/tests/semverwithvprefix.py @@ -0,0 +1 @@ +../docs/advanced/semverwithvprefix.py \ No newline at end of file From 73bd190a1cb06517868c9b176ce386f95f66cdd9 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Thu, 27 Jan 2022 11:22:43 +0100 Subject: [PATCH 05/22] Describe Pydantic and semver in "Advanced topics" Related to issue #343 Co-authored-by: Caleb Stewart --- changelog.d/343.doc.rst | 2 + docs/advanced/combine-pydantic-and-semver.rst | 53 +++++++++++++++++++ docs/advanced/index.rst | 4 +- 3 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 changelog.d/343.doc.rst create mode 100644 docs/advanced/combine-pydantic-and-semver.rst diff --git a/changelog.d/343.doc.rst b/changelog.d/343.doc.rst new file mode 100644 index 00000000..630d7474 --- /dev/null +++ b/changelog.d/343.doc.rst @@ -0,0 +1,2 @@ +Describe combining Pydantic with semver in the "Advanced topic" +section. diff --git a/docs/advanced/combine-pydantic-and-semver.rst b/docs/advanced/combine-pydantic-and-semver.rst new file mode 100644 index 00000000..a9249daf --- /dev/null +++ b/docs/advanced/combine-pydantic-and-semver.rst @@ -0,0 +1,53 @@ +Combining Pydantic and semver +============================= + +According to its homepage, `Pydantic `_ +"enforces type hints at runtime, and provides user friendly errors when data +is invalid." + +To work with Pydantic, use the following steps: + + +1. Derive a new class from :class:`~semver.version.Version` + first and add the magic methods :py:meth:`__get_validators__` + and :py:meth:`__modify_schema__` like this: + + .. code-block:: python + + from semver import Version + + class PydanticVersion(Version): + @classmethod + def __get_validators__(cls): + """Return a list of validator methods for pydantic models.""" + yield cls.parse + + @classmethod + def __modify_schema__(cls, field_schema): + """Inject/mutate the pydantic field schema in-place.""" + field_schema.update(examples=["1.0.2", + "2.15.3-alpha", + "21.3.15-beta+12345", + ] + ) + +2. Create a new model (in this example :class:`MyModel`) and derive + it from :class:`pydantic.BaseModel`: + + .. code-block:: python + + import pydantic + + class MyModel(pydantic.BaseModel): + version: PydanticVersion + +3. Use your model like this: + + .. code-block:: python + + model = MyModel.parse_obj({"version": "1.2.3"}) + + The attribute :py:attr:`model.version` will be an instance of + :class:`~semver.version.Version`. + If the version is invalid, the construction will raise a + :py:exc:`pydantic.ValidationError`. diff --git a/docs/advanced/index.rst b/docs/advanced/index.rst index de7da166..f45a2f9e 100644 --- a/docs/advanced/index.rst +++ b/docs/advanced/index.rst @@ -2,9 +2,9 @@ Advanced topics =============== - .. toctree:: deal-with-invalid-versions create-subclasses-from-version - display-deprecation-warnings \ No newline at end of file + display-deprecation-warnings + combine-pydantic-and-semver \ No newline at end of file From 0c4985c91d5ae9d0085d27a9484397ac47a3b437 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Tue, 25 Jan 2022 21:40:45 +0100 Subject: [PATCH 06/22] 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. --- changelog.d/335.doc.rst | 2 + docs/advanced/convert-pypi-to-semver.rst | 207 ++++++++++++++++++ docs/advanced/index.rst | 3 +- docs/conf.py | 4 +- docs/install.rst | 13 +- .../replace-deprecated-functions.rst | 4 +- tests/conftest.py | 2 + 7 files changed, 227 insertions(+), 8 deletions(-) create mode 100644 changelog.d/335.doc.rst create mode 100644 docs/advanced/convert-pypi-to-semver.rst 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 From 57690e827077a831ee6e6aebfd63ff5d5cf175c8 Mon Sep 17 00:00:00 2001 From: Thomas <616052+b0uh@users.noreply.github.com> Date: Thu, 24 Feb 2022 10:06:57 +0100 Subject: [PATCH 07/22] Use HTTPS instead of HTTP for the website URL --- README.rst | 2 +- docs/install.rst | 2 +- setup.cfg | 2 +- src/semver/__about__.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 5c28cc69..5d939976 100644 --- a/README.rst +++ b/README.rst @@ -112,7 +112,7 @@ There are other functions to discover. Read on! .. |docs| image:: https://readthedocs.org/projects/python-semver/badge/?version=latest :target: http://python-semver.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status -.. _semantic versioning: http://semver.org/ +.. _semantic versioning: https://semver.org/ .. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/psf/black :alt: Black Formatter diff --git a/docs/install.rst b/docs/install.rst index f23186a2..5404882f 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -121,4 +121,4 @@ Ubuntu $ sudo apt-get install python3-semver -.. _semantic versioning: http://semver.org/ +.. _semantic versioning: https://semver.org/ diff --git a/setup.cfg b/setup.cfg index de2d226c..8991f1c6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,7 +6,7 @@ [metadata] name = semver version = attr: semver.__about__.__version__ -description = Python helper for Semantic Versioning (http://semver.org) +description = Python helper for Semantic Versioning (https://semver.org) long_description = file: README.rst long_description_content_type = text/x-rst author = Kostiantyn Rybnikov diff --git a/src/semver/__about__.py b/src/semver/__about__.py index fa448ebe..d1dc8e3f 100644 --- a/src/semver/__about__.py +++ b/src/semver/__about__.py @@ -31,7 +31,7 @@ __maintainer_email__ = "s.celles@gmail.com" #: Short description about semver -__description__ = "Python helper for Semantic Versioning (http://semver.org)" +__description__ = "Python helper for Semantic Versioning (https://semver.org)" #: Supported semver specification SEMVER_SPEC_VERSION = "2.0.0" From ffe686a9b1d5adae75239da3592aa077a9970728 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Thu, 24 Feb 2022 21:36:49 +0100 Subject: [PATCH 08/22] Add topic to read version from file Fix #340 --- changelog.d/340.doc.rst | 1 + docs/advanced/index.rst | 1 + docs/advanced/version-from-file.rst | 23 +++++++++++++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 changelog.d/340.doc.rst create mode 100644 docs/advanced/version-from-file.rst diff --git a/changelog.d/340.doc.rst b/changelog.d/340.doc.rst new file mode 100644 index 00000000..807e401c --- /dev/null +++ b/changelog.d/340.doc.rst @@ -0,0 +1 @@ +Describe how to get version from a file \ No newline at end of file diff --git a/docs/advanced/index.rst b/docs/advanced/index.rst index d82da1a1..8a45d361 100644 --- a/docs/advanced/index.rst +++ b/docs/advanced/index.rst @@ -9,3 +9,4 @@ Advanced topics display-deprecation-warnings combine-pydantic-and-semver convert-pypi-to-semver + version-from-file diff --git a/docs/advanced/version-from-file.rst b/docs/advanced/version-from-file.rst new file mode 100644 index 00000000..6dc9bb48 --- /dev/null +++ b/docs/advanced/version-from-file.rst @@ -0,0 +1,23 @@ +.. _sec_reading_versions_from_file: + +Reading versions from file +========================== + +In cases where a version is stored inside a file, one possible solution +is to use the following function: + +.. code-block:: python + + from semver.version import Version + + def get_version(path: str = "version") -> Version: + """ + Construct a Version 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() + return Version.parse(version) From 2aeb61b667f1df9c1bd98cf5822a8254e23ac993 Mon Sep 17 00:00:00 2001 From: OidaTiftla Date: Tue, 24 May 2022 10:42:19 +0200 Subject: [PATCH 09/22] Allow optional minor and patch parts (#359) * Change Version.parse to allow optional minor and patch parts * Add documentation and changelog entry Co-authored-by: Tom Schraitle --- changelog.d/pr359.feature.rst | 2 ++ docs/usage/parse-version-string.rst | 7 ++++ src/semver/version.py | 48 +++++++++++++++++++++------ tests/test_parsing.py | 50 +++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 9 deletions(-) create mode 100644 changelog.d/pr359.feature.rst diff --git a/changelog.d/pr359.feature.rst b/changelog.d/pr359.feature.rst new file mode 100644 index 00000000..5c18c9d2 --- /dev/null +++ b/changelog.d/pr359.feature.rst @@ -0,0 +1,2 @@ +Add optional parameter ``optional_minor_and_patch`` in :meth:`.Version.parse` to allow optional +minor and patch parts. diff --git a/docs/usage/parse-version-string.rst b/docs/usage/parse-version-string.rst index ddd421e7..0a39c8a3 100644 --- a/docs/usage/parse-version-string.rst +++ b/docs/usage/parse-version-string.rst @@ -6,3 +6,10 @@ Use the function :func:`Version.parse `:: >>> Version.parse("3.4.5-pre.2+build.4") Version(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') + +Set the parameter ``optional_minor_and_patch=True`` to allow optional +minor and patch parts. Optional parts are set to zero. By default (False), the +version string to parse has to follow the semver specification:: + + >>> Version.parse("1.2", optional_minor_and_patch=True) + Version(major=1, minor=2, patch=0, prerelease=None, build=None) diff --git a/src/semver/version.py b/src/semver/version.py index 9e02544f..096acdf2 100644 --- a/src/semver/version.py +++ b/src/semver/version.py @@ -68,15 +68,19 @@ class Version: __slots__ = ("_major", "_minor", "_patch", "_prerelease", "_build") #: Regex for number in a prerelease _LAST_NUMBER = re.compile(r"(?:[^\d]*(\d+)[^\d]*)+") - #: Regex for a semver version - _REGEX = re.compile( + #: Regex template for a semver version + _REGEX_TEMPLATE = \ r""" ^ (?P0|[1-9]\d*) - \. - (?P0|[1-9]\d*) - \. - (?P0|[1-9]\d*) + (?: + \. + (?P0|[1-9]\d*) + (?: + \. + (?P0|[1-9]\d*) + ){opt_patch} + ){opt_minor} (?:-(?P (?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*) (?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))* @@ -86,7 +90,15 @@ class Version: (?:\.[0-9a-zA-Z-]+)* ))? $ - """, + """ + #: Regex for a semver version + _REGEX = re.compile( + _REGEX_TEMPLATE.format(opt_patch='', opt_minor=''), + re.VERBOSE, + ) + #: Regex for a semver version that might be shorter + _REGEX_OPTIONAL_MINOR_AND_PATCH = re.compile( + _REGEX_TEMPLATE.format(opt_patch='?', opt_minor='?'), re.VERBOSE, ) @@ -553,15 +565,26 @@ def match(self, match_expr: str) -> bool: return cmp_res in possibilities @classmethod - def parse(cls, version: String) -> "Version": + def parse( + cls, + version: String, + optional_minor_and_patch: bool = False + ) -> "Version": """ Parse version string to a Version instance. .. versionchanged:: 2.11.0 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. :param version: version string + :param optional_minor_and_patch: if set to true, the version string to parse \ + can contain optional minor and patch parts. Optional parts are set to zero. + By default (False), the version string to parse has to follow the semver + specification. :return: a new :class:`Version` instance :raises ValueError: if version is invalid :raises TypeError: if version contains the wrong type @@ -575,11 +598,18 @@ def parse(cls, version: String) -> "Version": elif not isinstance(version, String.__args__): # type: ignore raise TypeError("not expecting type '%s'" % type(version)) - match = cls._REGEX.match(version) + if optional_minor_and_patch: + match = cls._REGEX_OPTIONAL_MINOR_AND_PATCH.match(version) + else: + match = cls._REGEX.match(version) if match is None: raise ValueError(f"{version} is not valid SemVer string") matched_version_parts: Dict[str, Any] = match.groupdict() + if not matched_version_parts['minor']: + matched_version_parts['minor'] = 0 + if not matched_version_parts['patch']: + matched_version_parts['patch'] = 0 return cls(**matched_version_parts) diff --git a/tests/test_parsing.py b/tests/test_parsing.py index 25c55c74..ddf52196 100644 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -53,6 +53,56 @@ def test_should_parse_version(version, expected): assert result == expected +@pytest.mark.parametrize( + "version,expected", + [ + # no. 1 + ( + "1.2-alpha.1.2+build.11.e0f985a", + { + "major": 1, + "minor": 2, + "patch": 0, + "prerelease": "alpha.1.2", + "build": "build.11.e0f985a", + }, + ), + # no. 2 + ( + "1-alpha-1+build.11.e0f985a", + { + "major": 1, + "minor": 0, + "patch": 0, + "prerelease": "alpha-1", + "build": "build.11.e0f985a", + }, + ), + ( + "0.1-0f", + {"major": 0, "minor": 1, "patch": 0, "prerelease": "0f", "build": None}, + ), + ( + "0-0foo.1", + {"major": 0, "minor": 0, "patch": 0, "prerelease": "0foo.1", "build": None}, + ), + ( + "0-0foo.1+build.1", + { + "major": 0, + "minor": 0, + "patch": 0, + "prerelease": "0foo.1", + "build": "build.1", + }, + ), + ], +) +def test_should_parse_version_with_optional_minor_and_patch(version, expected): + result = Version.parse(version, optional_minor_and_patch=True) + assert result == expected + + def test_parse_version_info_str_hash(): s_version = "1.2.3-alpha.1.2+build.11.e0f985a" v = parse_version_info(s_version) From b5317af9a7e99e6a86df98320e73be72d5adf0de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Miguel=20S=C3=A1nchez=20Garc=C3=ADa?= Date: Mon, 4 Jul 2022 15:51:44 +0200 Subject: [PATCH 10/22] Support matching 'equal' when no operator is provided (#362) * Add tests for new default equality match behavior * Change documentation and add changelog --- changelog.d/pr362.feature.rst | 2 ++ docs/usage/compare-versions-through-expression.rst | 13 +++++++++++++ src/semver/version.py | 7 ++++++- tests/test_match.py | 14 +++++++++++++- 4 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 changelog.d/pr362.feature.rst diff --git a/changelog.d/pr362.feature.rst b/changelog.d/pr362.feature.rst new file mode 100644 index 00000000..1b7cc120 --- /dev/null +++ b/changelog.d/pr362.feature.rst @@ -0,0 +1,2 @@ +Make :meth:`.Version.match` accept a bare version string as match expression, defaulting to +equality testing. diff --git a/docs/usage/compare-versions-through-expression.rst b/docs/usage/compare-versions-through-expression.rst index 43a5a182..28fad671 100644 --- a/docs/usage/compare-versions-through-expression.rst +++ b/docs/usage/compare-versions-through-expression.rst @@ -24,3 +24,16 @@ That gives you the following possibilities to express your condition: True >>> semver.match("1.0.0", ">1.0.0") False + +If no operator is specified, the match expression is interpreted as a +version to be compared for equality. This allows handling the common +case of version compatibility checking through either an exact version +or a match expression very easy to implement, as the same code will +handle both cases: + +.. code-block:: python + + >>> semver.match("2.0.0", "2.0.0") + True + >>> semver.match("1.0.0", "3.5.1") + False diff --git a/src/semver/version.py b/src/semver/version.py index 096acdf2..34eb51e0 100644 --- a/src/semver/version.py +++ b/src/semver/version.py @@ -522,7 +522,7 @@ def match(self, match_expr: str) -> bool: """ Compare self to match a match expression. - :param match_expr: operator and version; valid operators are + :param match_expr: optional operator and version; valid operators are ``<``` smaller than ``>`` greater than ``>=`` greator or equal than @@ -535,6 +535,8 @@ def match(self, match_expr: str) -> bool: True >>> semver.Version.parse("1.0.0").match(">1.0.0") False + >>> semver.Version.parse("4.0.4").match("4.0.4") + True """ prefix = match_expr[:2] if prefix in (">=", "<=", "==", "!="): @@ -542,6 +544,9 @@ def match(self, match_expr: str) -> bool: elif prefix and prefix[0] in (">", "<"): prefix = prefix[0] match_version = match_expr[1:] + elif match_expr and match_expr[0] in "0123456789": + prefix = "==" + match_version = match_expr else: raise ValueError( "match_expr parameter should be in format , " diff --git a/tests/test_match.py b/tests/test_match.py index b4cc50cc..2476eb01 100644 --- a/tests/test_match.py +++ b/tests/test_match.py @@ -23,6 +23,18 @@ def test_should_match_not_equal(left, right, expected): assert match(left, right) is expected +@pytest.mark.parametrize( + "left,right,expected", + [ + ("2.3.7", "2.3.7", True), + ("2.3.6", "2.3.6", True), + ("2.3.7", "4.3.7", False), + ], +) +def test_should_match_equal_by_default(left, right, expected): + assert match(left, right) is expected + + @pytest.mark.parametrize( "left,right,expected", [ @@ -49,7 +61,7 @@ def test_should_raise_value_error_for_unexpected_match_expression(left, right): @pytest.mark.parametrize( - "left,right", [("1.0.0", ""), ("1.0.0", "!"), ("1.0.0", "1.0.0")] + "left,right", [("1.0.0", ""), ("1.0.0", "!")] ) def test_should_raise_value_error_for_invalid_match_expression(left, right): with pytest.raises(ValueError): From 3eae18c2a4ac3236b2370b5be47d2078e03f6582 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Mon, 9 Nov 2020 00:06:22 +0100 Subject: [PATCH 11/22] Fix #365: Improve pyproject.toml * Improve pyproject.toml * Use setuptools * Add metadata * Taken approach from https://godatadriven.com/blog/a-practical-guide-to-setuptools-and-pyproject-toml/ * Doc: Describe building of semver * Correct small glitches * Remove .travis.yml in MANIFEST.in (not needed anymore) * Distinguish between Python3.6 and others in tox.ini * Add skip_missing_interpreters option for tox.ini * Add changelog entry * GH Action: * Upgrade setuptools and setuptools-scm * Also test against 3.11.0-rc.2 --- .github/workflows/python-testing.yml | 8 ++-- BUILDING.rst | 59 ++++++++++++++++++++++++++++ MANIFEST.in | 3 +- changelog.d/364.feature.rst | 3 ++ docs/build.rst | 1 + docs/index.rst | 1 + pyproject.toml | 14 ++++++- setup.cfg | 2 + tox.ini | 16 ++++++-- 9 files changed, 97 insertions(+), 10 deletions(-) create mode 100644 BUILDING.rst create mode 100644 changelog.d/364.feature.rst create mode 100644 docs/build.rst diff --git a/.github/workflows/python-testing.yml b/.github/workflows/python-testing.yml index 8f36dbc9..336e4980 100644 --- a/.github/workflows/python-testing.yml +++ b/.github/workflows/python-testing.yml @@ -34,10 +34,10 @@ jobs: - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: - python-version: 3.6 + python-version: 3.7 - name: Install dependencies run: | - python3 -m pip install --upgrade pip + python3 -m pip install --upgrade pip setuptools setuptools-scm pip install tox tox-gh-actions - name: Check run: | @@ -49,7 +49,9 @@ jobs: strategy: max-parallel: 5 matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11.0-rc.2", + # "3.12" + ] steps: - uses: actions/checkout@v1 diff --git a/BUILDING.rst b/BUILDING.rst new file mode 100644 index 00000000..6de412f8 --- /dev/null +++ b/BUILDING.rst @@ -0,0 +1,59 @@ +.. _build-semver: + +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/ +.. _Declarative config: https://setuptools.rtfd.io/en/latest/userguide/declarative_config.html + + +This project changed slightly its way how it is built. The reason for this +was to still support the "traditional" way with :command:`setup.py`, +but at the same time try out the newer way with :file:`pyproject.toml`. +Over time, once Python 3.6 gets deprecated, we will support only the newer way. + + +Background information +---------------------- + +Skip this section and head over to :ref:`build-pyproject-build` if you just +want to know how to build semver. +This section gives some background information how this project is set up. + +The traditional way with :command:`setup.py` in this project uses a +`Declarative config`_. With this approach, the :command:`setup.py` is +stripped down to its bare minimum and all the metadata is stored in +:file:`setup.cfg`. + +The new :file:`pyproject.toml` contains only information about the build backend, currently setuptools.build_meta. The idea is taken from +`A Practical Guide to Setuptools and Pyproject.toml`_. +Setuptools-specific configuration keys as defined in `PEP 621`_ are currently +not used. + + +.. _build-pyproject-build: + +Building with pyproject-build +----------------------------- + +To build semver you need: + +* The :mod:`build` module which implements the `PEP 517`_ build + frontend. + Install it with:: + + pip install build + + Some Linux distributions has already packaged it. If you prefer + to use the module with your package manager, search for + :file:`python-build` or :file:`python3-build` and install it. + +* The command :command:`pyproject-build` from the :mod:`build` module. + +To build semver, run:: + + pyproject-build + +After the command is finished, you can find two files in the :file:`dist` folder: a ``.tar.gz`` and a ``.whl`` file. \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in index 80257f1f..7b2a7b61 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,8 +1,7 @@ include *.rst include *.txt -include test_*.py +include tests/test_*.py -exclude .travis.yml prune docs/_build recursive-exclude .github * diff --git a/changelog.d/364.feature.rst b/changelog.d/364.feature.rst new file mode 100644 index 00000000..885a6c85 --- /dev/null +++ b/changelog.d/364.feature.rst @@ -0,0 +1,3 @@ +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`. diff --git a/docs/build.rst b/docs/build.rst new file mode 100644 index 00000000..ba0c84a4 --- /dev/null +++ b/docs/build.rst @@ -0,0 +1 @@ +.. include:: ../BUILDING.rst diff --git a/docs/index.rst b/docs/index.rst index deac1cd0..2dce2a50 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,6 +9,7 @@ Semver |version| -- Semantic Versioning :caption: Contents :hidden: + build install usage/index migration/index diff --git a/pyproject.toml b/pyproject.toml index 769b13d7..ba4be51b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,22 @@ +# +# +# See also https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html +# +# General idea taken from +# https://godatadriven.com/blog/a-practical-guide-to-setuptools-and-pyproject-toml/ + [build-system] requires = [ # sync with setup.py until we discard non-pep-517/518 - "setuptools>=40.0", + "setuptools", "setuptools-scm", "wheel", + "build", ] build-backend = "setuptools.build_meta" + + [tool.black] line-length = 88 target-version = ['py36', 'py37', 'py38', 'py39', 'py310'] @@ -22,7 +32,7 @@ include = ''' [tool.towncrier] package = "semver" -# package_dir = "src" +package_dir = "src" filename = "CHANGELOG.rst" directory = "changelog.d/" title_format = "Version {version}" diff --git a/setup.cfg b/setup.cfg index 8991f1c6..4087e787 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,6 +31,7 @@ classifiers = Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 Topic :: Software Development :: Libraries :: Python Modules license = BSD @@ -56,6 +57,7 @@ norecursedirs = .git build .env/ env/ .pyenv/ .tmp/ .eggs/ venv/ testpaths = tests docs filterwarnings = ignore:Function 'semver.*:DeprecationWarning + # ' <- This apostroph is just to fix syntax highlighting addopts = --no-cov-on-fail --cov=semver diff --git a/tox.ini b/tox.ini index 8c7eb5e5..8213cd55 100644 --- a/tox.ini +++ b/tox.ini @@ -1,28 +1,38 @@ [tox] envlist = checks - py{36,37,38,39,310} + py3{6,7,8,9,10,11,12} isolated_build = True +skip_missing_interpreters = True [gh-actions] python = 3.6: py36 - 3.7: py37 + # setuptools >=62 needs Python >=3.7 + 3.7: py37,check 3.8: py38 3.9: py39 3.10: py310 + 3.11: py311 + 3.12: py312 [testenv] -description = Run test suite for {basepython} +description = + py36: Run a slightly different test suite for {basepython} + !py36: Run test suite for {basepython} allowlist_externals = make commands = pytest {posargs:} deps = pytest pytest-cov + # py36: dataclasses + !py36: setuptools>=62.0 + !py36: setuptools-scm setenv = PIP_DISABLE_PIP_VERSION_CHECK = 1 + [testenv:black] description = Check for formatting changes basepython = python3 From f7a6eda6f151d6df8db987425c25459dc87c7b20 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Thu, 10 Nov 2022 11:33:23 +0100 Subject: [PATCH 12/22] CI: Update GH Actions * Use v3 for some Actions * Move to 3.11 instead of RC --- .github/workflows/python-testing.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/python-testing.yml b/.github/workflows/python-testing.yml index 336e4980..48352b12 100644 --- a/.github/workflows/python-testing.yml +++ b/.github/workflows/python-testing.yml @@ -11,7 +11,7 @@ jobs: check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Output env variables run: | echo "Default branch=${default-branch}" @@ -32,7 +32,7 @@ jobs: echo "\n" echo "::debug::---end" - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: 3.7 - name: Install dependencies @@ -49,14 +49,19 @@ jobs: strategy: max-parallel: 5 matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11.0-rc.2", + python-version: ["3.6", + "3.7", + "3.8", + "3.9", + "3.10", + "3.11", # "3.12" ] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} - name: Install dependencies From 98e845c804635ad533bd1cc7707297c5c1af9dd9 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Thu, 10 Nov 2022 14:27:09 +0100 Subject: [PATCH 13/22] Add missing Optional type annotation --- src/semver/__main__.py | 4 ++-- src/semver/_deprecated.py | 8 ++++---- src/semver/cli.py | 4 ++-- src/semver/version.py | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/semver/__main__.py b/src/semver/__main__.py index 7fde54d7..a6d448aa 100644 --- a/src/semver/__main__.py +++ b/src/semver/__main__.py @@ -11,12 +11,12 @@ """ import os.path import sys -from typing import List +from typing import List, Optional from semver import cli -def main(cliargs: List[str] = None) -> int: +def main(cliargs: Optional[List[str]] = None) -> int: if __package__ == "": path = os.path.dirname(os.path.dirname(__file__)) sys.path[0:0] = [path] diff --git a/src/semver/_deprecated.py b/src/semver/_deprecated.py index 61ceae12..5f51c8f3 100644 --- a/src/semver/_deprecated.py +++ b/src/semver/_deprecated.py @@ -7,7 +7,7 @@ import warnings from functools import partial, wraps from types import FrameType -from typing import Type, Callable, cast +from typing import Type, Callable, Optional, cast from . import cli from .version import Version @@ -15,9 +15,9 @@ def deprecated( - func: F = None, - replace: str = None, - version: str = None, + func: Optional[F] = None, + replace: Optional[str] = None, + version: Optional[str] = None, category: Type[Warning] = DeprecationWarning, ) -> Decorator: """ diff --git a/src/semver/cli.py b/src/semver/cli.py index 65ca5187..3c573d63 100644 --- a/src/semver/cli.py +++ b/src/semver/cli.py @@ -12,7 +12,7 @@ import argparse import sys -from typing import cast, List +from typing import cast, List, Optional from .version import Version from .__about__ import __version__ @@ -152,7 +152,7 @@ def process(args: argparse.Namespace) -> str: return args.func(args) -def main(cliargs: List[str] = None) -> int: +def main(cliargs: Optional[List[str]] = None) -> int: """ Entry point for the application script. diff --git a/src/semver/version.py b/src/semver/version.py index 34eb51e0..04e7faae 100644 --- a/src/semver/version.py +++ b/src/semver/version.py @@ -107,8 +107,8 @@ def __init__( major: SupportsInt, minor: SupportsInt = 0, patch: SupportsInt = 0, - prerelease: Union[String, int] = None, - build: Union[String, int] = None, + prerelease: Optional[Union[String, int]] = None, + build: Optional[Union[String, int]] = None, ): # Build a dictionary of the arguments except prerelease and build version_parts = {"major": int(major), "minor": int(minor), "patch": int(patch)} From 62a2fef61f78e9b558c57abf6452f475db10057d Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Wed, 16 Nov 2022 21:42:06 +0100 Subject: [PATCH 14/22] CI: Raise version for GH Actions To avoid deprecation warnings, the GitHub workflow file is adjusted to newer versions. --- .github/workflows/codeql-analysis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index bff8173b..f310a7e3 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -35,11 +35,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -50,7 +50,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v2 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -64,4 +64,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 From 5d606141ebc32a6a1cff82a87a077ed0f6c69090 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Thu, 17 Nov 2022 09:10:31 +0100 Subject: [PATCH 15/22] Revert "CI: Raise version for GH Actions" This reverts commit 62a2fef61f78e9b558c57abf6452f475db10057d. --- .github/workflows/codeql-analysis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index f310a7e3..bff8173b 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -35,11 +35,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v1 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -50,7 +50,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v1 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -64,4 +64,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v1 From 90bff70d4521bce2656efd1ce67f88023324009e Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Wed, 16 Nov 2022 21:42:06 +0100 Subject: [PATCH 16/22] CI: Raise version for GH Actions To avoid deprecation warnings, the GitHub workflow file is adjusted to newer versions. --- .github/workflows/codeql-analysis.yml | 8 ++++---- .github/workflows/python-testing.yml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index bff8173b..f310a7e3 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -35,11 +35,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -50,7 +50,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v2 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -64,4 +64,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/python-testing.yml b/.github/workflows/python-testing.yml index 48352b12..9159ea07 100644 --- a/.github/workflows/python-testing.yml +++ b/.github/workflows/python-testing.yml @@ -55,13 +55,13 @@ jobs: "3.9", "3.10", "3.11", - # "3.12" + # "3.12-dev" ] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies From 24e70e902bf189ad84ef97018156485ca4935277 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sat, 26 Nov 2022 11:59:49 +0100 Subject: [PATCH 17/22] Fix #374: Adapt pyproject.tom for Towncrier * Replace the old, deprecated ``[[tool.towncrier.type]]`` entries with ``[tool.towncrier.fragment.]``. Described in https://towncrier.readthedocs.io/en/stable/configuration.html#deprecated-defining-custom-fragment-types-with-an-array-of-toml-tables * Add a changelog news file Co-authored-by: Nagidal --- changelog.d/374.bugfix.rst | 3 +++ pyproject.toml | 46 ++++++++++++-------------------------- 2 files changed, 17 insertions(+), 32 deletions(-) create mode 100644 changelog.d/374.bugfix.rst diff --git a/changelog.d/374.bugfix.rst b/changelog.d/374.bugfix.rst new file mode 100644 index 00000000..fd4e7ea4 --- /dev/null +++ b/changelog.d/374.bugfix.rst @@ -0,0 +1,3 @@ +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.]``. diff --git a/pyproject.toml b/pyproject.toml index ba4be51b..03e4873a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,42 +40,24 @@ template = "changelog.d/_template.rst" # issue_format = "`#{issue} `_" # issue_format = ":gh:`{issue}`" - # [[tool.towncrier.type]] - # directory = "breaking" - # name = "Breaking Changes" - # showcontent = true - [[tool.towncrier.type]] - directory = "deprecation" - name = "Deprecations" - showcontent = true +[tool.towncrier.fragment.breaking] +name = "Breaking Changes" - [[tool.towncrier.type]] - directory = "feature" - name = "Features" - showcontent = true +[tool.towncrier.fragment.bugfix] +name = "Bug fixes" - # [[tool.towncrier.type]] - # directory = "improvement" - # name = "Improvements" - # showcontent = true +[tool.towncrier.fragment.deprecation] +name = "Deprecations" - [[tool.towncrier.type]] - directory = "bugfix" - name = "Bug Fixes" - showcontent = true +[tool.towncrier.fragment.doc] +name = "Improved documentation" - [[tool.towncrier.type]] - directory = "doc" - name = "Improved Documentation" - showcontent = true +[tool.towncrier.fragment.feature] +name = "Features" - [[tool.towncrier.type]] - directory = "trivial" - name = "Trivial/Internal Changes" - showcontent = true +[tool.towncrier.fragment.removal] +name = "Removals" - [[tool.towncrier.type]] - directory = "removal" - name = "Removals" - showcontent = true +[tool.towncrier.fragment.trivial] +name = "Trivial/Internal Changes" From ae716f02d610039d389afa6f056e629b6372a212 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sun, 18 Dec 2022 09:57:25 +0100 Subject: [PATCH 18/22] Fix #372: Remove 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. --- .github/workflows/python-testing.yml | 3 +-- BUILDING.rst | 3 ++- CONTRIBUTING.rst | 16 ++++++++-------- README.rst | 4 ++-- pyproject.toml | 2 +- setup.cfg | 4 ++-- setup.py | 4 ---- tox.ini | 17 ++++++++--------- 8 files changed, 24 insertions(+), 29 deletions(-) delete mode 100644 setup.py diff --git a/.github/workflows/python-testing.yml b/.github/workflows/python-testing.yml index 9159ea07..b875bb9c 100644 --- a/.github/workflows/python-testing.yml +++ b/.github/workflows/python-testing.yml @@ -49,8 +49,7 @@ jobs: strategy: max-parallel: 5 matrix: - python-version: ["3.6", - "3.7", + python-version: ["3.7", "3.8", "3.9", "3.10", diff --git a/BUILDING.rst b/BUILDING.rst index 6de412f8..61f3d4cb 100644 --- a/BUILDING.rst +++ b/BUILDING.rst @@ -12,7 +12,8 @@ Building semver This project changed slightly its way how it is built. The reason for this was to still support the "traditional" way with :command:`setup.py`, but at the same time try out the newer way with :file:`pyproject.toml`. -Over time, once Python 3.6 gets deprecated, we will support only the newer way. +As Python 3.6 got deprecated, this project does support from now on only +:file:`pyproject.toml`. Background information diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index e0210cc9..fe531990 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -99,11 +99,11 @@ You can decide to run the complete test suite or only part of it: $ tox --skip-missing-interpreters It is possible to use one or more specific Python versions. Use the ``-e`` - option and one or more abbreviations (``py36`` for Python 3.6, ``py37`` for - Python 3.7 etc.):: + option and one or more abbreviations (``py37`` for Python 3.7, + ``py38`` for Python 3.8 etc.):: - $ tox -e py36 - $ tox -e py36,py37 + $ tox -e py37 + $ tox -e py37,py38 To get a complete list and a short description, run:: @@ -116,7 +116,7 @@ You can decide to run the complete test suite or only part of it: :func:`test_immutable_major` in the file :file:`test_bump.py` for all Python versions:: - $ tox -e py36 -- tests/test_bump.py::test_should_bump_major + $ 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:: @@ -124,16 +124,16 @@ You can decide to run the complete test suite or only part of it: $ tox -- -v You can combine the specific test function with the ``-e`` option, for - example, to limit the tests for Python 3.6 and 3.7 only:: + example, to limit the tests for Python 3.7 and 3.8 only:: - $ tox -e py36,py37 -- tests/test_bump.py::test_should_bump_major + $ 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,py36,py37 + $ tox -e checks,py37,py38 .. _doc: diff --git a/README.rst b/README.rst index 5d939976..cad99a04 100644 --- a/README.rst +++ b/README.rst @@ -17,7 +17,7 @@ A Python module for `semantic versioning`_. Simplifies comparing versions. .. note:: - This project works for Python 3.6 and greater only. If you are + This project works for Python 3.7 and greater only. If you are looking for a compatible version for Python 2, use the maintenance branch |MAINT|_. @@ -25,7 +25,7 @@ A Python module for `semantic versioning`_. Simplifies comparing versions. 2.x.y However, keep in mind, the major 2 release is frozen: no new features nor backports will be integrated. - We recommend to upgrade your workflow to Python 3.x to gain support, + We recommend to upgrade your workflow to Python 3 to gain support, bugfixes, and new features. .. |MAINT| replace:: ``maint/v2`` diff --git a/pyproject.toml b/pyproject.toml index 03e4873a..249facab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ build-backend = "setuptools.build_meta" [tool.black] line-length = 88 -target-version = ['py36', 'py37', 'py38', 'py39', 'py310'] +target-version = ['py37', 'py38', 'py39', 'py310', 'py311'] # diff = true extend-exclude = ''' # A regex preceded with ^/ will apply only to files and directories diff --git a/setup.cfg b/setup.cfg index 4087e787..0181697d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,12 +26,12 @@ classifiers = Operating System :: OS Independent Programming Language :: Python Programming Language :: Python :: 3 - Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 Topic :: Software Development :: Libraries :: Python Modules license = BSD @@ -39,7 +39,7 @@ license = BSD package_dir = =src packages = find: -python_requires = >=3.6.* +python_requires = >=3.7.* include_package_data = True [options.entry_points] diff --git a/setup.py b/setup.py deleted file mode 100644 index 88990ad8..00000000 --- a/setup.py +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env python3 -import setuptools - -setuptools.setup() # For compatibility with python 3.6 diff --git a/tox.ini b/tox.ini index 8213cd55..f55becd4 100644 --- a/tox.ini +++ b/tox.ini @@ -1,13 +1,12 @@ [tox] envlist = checks - py3{6,7,8,9,10,11,12} + py3{7,8,9,10,11,12} isolated_build = True skip_missing_interpreters = True [gh-actions] python = - 3.6: py36 # setuptools >=62 needs Python >=3.7 3.7: py37,check 3.8: py38 @@ -18,17 +17,14 @@ python = [testenv] -description = - py36: Run a slightly different test suite for {basepython} - !py36: Run test suite for {basepython} +description = Run test suite for {basepython} allowlist_externals = make commands = pytest {posargs:} deps = pytest pytest-cov - # py36: dataclasses - !py36: setuptools>=62.0 - !py36: setuptools-scm + setuptools>=62.0 + setuptools-scm setenv = PIP_DISABLE_PIP_VERSION_CHECK = 1 @@ -103,8 +99,11 @@ basepython = python3 deps = wheel twine + # PEP 517 build frontend + build commands = - python3 setup.py sdist bdist_wheel + # Same as python3 -m build + pyproject-build twine check dist/* From d8d80fa64cde14b22afe1aa5f220e1fc20b113f5 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sun, 18 Dec 2022 09:44:15 +0100 Subject: [PATCH 19/22] Fix #378: Typos in Towncrier config Co-authored-by: Nagidal --- changelog.d/378.trivial.rst | 1 + pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 changelog.d/378.trivial.rst diff --git a/changelog.d/378.trivial.rst b/changelog.d/378.trivial.rst new file mode 100644 index 00000000..2ecd2b73 --- /dev/null +++ b/changelog.d/378.trivial.rst @@ -0,0 +1 @@ +Fix some typos in Towncrier configuration diff --git a/pyproject.toml b/pyproject.toml index 249facab..0dd64853 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,13 +45,13 @@ template = "changelog.d/_template.rst" name = "Breaking Changes" [tool.towncrier.fragment.bugfix] -name = "Bug fixes" +name = "Bug Fixes" [tool.towncrier.fragment.deprecation] name = "Deprecations" [tool.towncrier.fragment.doc] -name = "Improved documentation" +name = "Improved Documentation" [tool.towncrier.fragment.feature] name = "Features" From 223e027182121bc2622aa8408facffdaea81bab5 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sun, 18 Dec 2022 12:40:10 +0100 Subject: [PATCH 20/22] Add changelog entry for #372 --- changelog.d/372.deprecation.rst | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 changelog.d/372.deprecation.rst diff --git a/changelog.d/372.deprecation.rst b/changelog.d/372.deprecation.rst new file mode 100644 index 00000000..c475f532 --- /dev/null +++ b/changelog.d/372.deprecation.rst @@ -0,0 +1,8 @@ +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. From 61335e0953dc4dbae2884aee5e96d45ac009f80f Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sat, 17 Dec 2022 15:44:07 +0100 Subject: [PATCH 21/22] General cleanup, reformat files * Reformat source code with black as some config options did accidently 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 setup.cfg and adapt list. * Use skip_install=True in tox.ini for black --- .github/workflows/python-testing.yml | 7 +++++++ .gitignore | 10 +++++++--- MANIFEST.in | 2 +- changelog.d/pr384.bugfix.rst | 11 +++++++++++ docs/advanced/semverwithvprefix.py | 6 ++---- pyproject.toml | 9 +-------- setup.cfg | 15 +++++++-------- src/semver/version.py | 19 ++++++++----------- tests/test_match.py | 4 +--- tox.ini | 1 + 10 files changed, 46 insertions(+), 38 deletions(-) create mode 100644 changelog.d/pr384.bugfix.rst diff --git a/.github/workflows/python-testing.yml b/.github/workflows/python-testing.yml index b875bb9c..fb23fcf8 100644 --- a/.github/workflows/python-testing.yml +++ b/.github/workflows/python-testing.yml @@ -7,6 +7,13 @@ on: pull_request: branches: [ master ] +concurrency: + # only cancel in-progress runs of the same workflow + group: ${{ github.workflow }}-${{ github.ref }} + # ${{ github.head_ref || github.run_id }} + cancel-in-progress: true + + jobs: check: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index d26b5515..c0859cd3 100644 --- a/.gitignore +++ b/.gitignore @@ -259,8 +259,12 @@ fabric.properties # -------- -# Patch/Diff Files -*.patch -*.diff +# Ignore files in the project's root: +/*.patch +/*.diff +/*.py +# but not this file: +!/setup.py + docs/_api !docs/_api/semver.__about__.rst diff --git a/MANIFEST.in b/MANIFEST.in index 7b2a7b61..e37851c9 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,4 +5,4 @@ include tests/test_*.py prune docs/_build recursive-exclude .github * -global-exclude *.py[cod] __pycache__ *.so *.dylib +global-exclude __pycache__ diff --git a/changelog.d/pr384.bugfix.rst b/changelog.d/pr384.bugfix.rst new file mode 100644 index 00000000..ca0b08d0 --- /dev/null +++ b/changelog.d/pr384.bugfix.rst @@ -0,0 +1,11 @@ +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/semverwithvprefix.py b/docs/advanced/semverwithvprefix.py index 4395a95e..f2a7fecd 100644 --- a/docs/advanced/semverwithvprefix.py +++ b/docs/advanced/semverwithvprefix.py @@ -17,10 +17,8 @@ def parse(cls, version: str) -> "SemVerWithVPrefix": """ if not version[0] in ("v", "V"): raise ValueError( - "{v!r}: not a valid semantic version tag. " - "Must start with 'v' or 'V'".format( - v=version - ) + f"{version!r}: not a valid semantic version tag. " + "Must start with 'v' or 'V'" ) return super().parse(version[1:]) diff --git a/pyproject.toml b/pyproject.toml index 0dd64853..d288e68e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,14 +21,7 @@ build-backend = "setuptools.build_meta" line-length = 88 target-version = ['py37', 'py38', 'py39', 'py310', 'py311'] # diff = true -extend-exclude = ''' -# A regex preceded with ^/ will apply only to files and directories -# in the root of the project. -^/*.py -''' -include = ''' -^/setup.py -''' + [tool.towncrier] package = "semver" diff --git a/setup.cfg b/setup.cfg index 0181697d..87ec3b8f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -55,10 +55,12 @@ semver = py.typed [tool:pytest] norecursedirs = .git build .env/ env/ .pyenv/ .tmp/ .eggs/ venv/ testpaths = tests docs +# pythonpath = src filterwarnings = ignore:Function 'semver.*:DeprecationWarning # ' <- This apostroph is just to fix syntax highlighting addopts = + # --import-mode=importlib --no-cov-on-fail --cov=semver --cov-report=term-missing @@ -69,18 +71,15 @@ addopts = [flake8] max-line-length = 88 ignore = F821,W503 -exclude = - src/semver/__init__.py - .env - venv +extend-exclude = .eggs - .tox - .git - __pycache__ + .env build - dist docs + venv conftest.py + src/semver/__init__.py + tasks.py [pycodestyle] count = False diff --git a/src/semver/version.py b/src/semver/version.py index 04e7faae..96281192 100644 --- a/src/semver/version.py +++ b/src/semver/version.py @@ -69,8 +69,7 @@ class Version: #: Regex for number in a prerelease _LAST_NUMBER = re.compile(r"(?:[^\d]*(\d+)[^\d]*)+") #: Regex template for a semver version - _REGEX_TEMPLATE = \ - r""" + _REGEX_TEMPLATE = r""" ^ (?P0|[1-9]\d*) (?: @@ -93,12 +92,12 @@ class Version: """ #: Regex for a semver version _REGEX = re.compile( - _REGEX_TEMPLATE.format(opt_patch='', opt_minor=''), + _REGEX_TEMPLATE.format(opt_patch="", opt_minor=""), re.VERBOSE, ) #: Regex for a semver version that might be shorter _REGEX_OPTIONAL_MINOR_AND_PATCH = re.compile( - _REGEX_TEMPLATE.format(opt_patch='?', opt_minor='?'), + _REGEX_TEMPLATE.format(opt_patch="?", opt_minor="?"), re.VERBOSE, ) @@ -571,9 +570,7 @@ def match(self, match_expr: str) -> bool: @classmethod def parse( - cls, - version: String, - optional_minor_and_patch: bool = False + cls, version: String, optional_minor_and_patch: bool = False ) -> "Version": """ Parse version string to a Version instance. @@ -611,10 +608,10 @@ def parse( raise ValueError(f"{version} is not valid SemVer string") matched_version_parts: Dict[str, Any] = match.groupdict() - if not matched_version_parts['minor']: - matched_version_parts['minor'] = 0 - if not matched_version_parts['patch']: - matched_version_parts['patch'] = 0 + if not matched_version_parts["minor"]: + matched_version_parts["minor"] = 0 + if not matched_version_parts["patch"]: + matched_version_parts["patch"] = 0 return cls(**matched_version_parts) diff --git a/tests/test_match.py b/tests/test_match.py index 2476eb01..e2685cae 100644 --- a/tests/test_match.py +++ b/tests/test_match.py @@ -60,9 +60,7 @@ def test_should_raise_value_error_for_unexpected_match_expression(left, right): match(left, right) -@pytest.mark.parametrize( - "left,right", [("1.0.0", ""), ("1.0.0", "!")] -) +@pytest.mark.parametrize("left,right", [("1.0.0", ""), ("1.0.0", "!")]) def test_should_raise_value_error_for_invalid_match_expression(left, right): with pytest.raises(ValueError): match(left, right) diff --git a/tox.ini b/tox.ini index f55becd4..8ca917b8 100644 --- a/tox.ini +++ b/tox.ini @@ -32,6 +32,7 @@ setenv = [testenv:black] description = Check for formatting changes basepython = python3 +skip_install = true deps = black commands = black --check {posargs:.} From 89d5423e3ecdb6605cc49f4af1b11d81d0e65005 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sun, 18 Dec 2022 20:46:24 +0100 Subject: [PATCH 22/22] Create 3.0.0-dev.4 Release * Update CHANGELOG * Fix small typos inside changelog.d/ --- CHANGELOG.rst | 88 +++++++++++++++++++++++++++++++++ CONTRIBUTORS | 9 ++-- changelog.d/335.doc.rst | 2 - changelog.d/340.doc.rst | 1 - changelog.d/343.doc.rst | 2 - changelog.d/350.doc.rst | 2 - changelog.d/351.doc.rst | 4 -- changelog.d/364.feature.rst | 3 -- changelog.d/372.deprecation.rst | 8 --- changelog.d/374.bugfix.rst | 3 -- changelog.d/378.trivial.rst | 1 - changelog.d/pr359.feature.rst | 2 - changelog.d/pr362.feature.rst | 2 - docs/usage/semver-version.rst | 2 +- src/semver/__about__.py | 2 +- 15 files changed, 96 insertions(+), 35 deletions(-) delete mode 100644 changelog.d/335.doc.rst delete mode 100644 changelog.d/340.doc.rst delete mode 100644 changelog.d/343.doc.rst delete mode 100644 changelog.d/350.doc.rst delete mode 100644 changelog.d/351.doc.rst delete mode 100644 changelog.d/364.feature.rst delete mode 100644 changelog.d/372.deprecation.rst delete mode 100644 changelog.d/374.bugfix.rst delete mode 100644 changelog.d/378.trivial.rst delete mode 100644 changelog.d/pr359.feature.rst delete mode 100644 changelog.d/pr362.feature.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3173507f..7f515aa1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,6 +16,94 @@ in our repository. .. towncrier release notes start +Version 3.0.0-dev.4 +=================== + +:Released: 2022-12-18 +:Maintainer: + + +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.]``. + + + +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. + + + +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" + + + +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 + + + +Trivial/Internal Changes +------------------------ + +* :gh:`378`: Fix some typos in Towncrier configuration + + + +---- + Version 3.0.0-dev.3 =================== diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 9e63f4d4..e5fef99b 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -2,7 +2,7 @@ Contributors ############ -Python SemVer library +Python Semver library ##################### This document records the primary maintainers and significant @@ -14,9 +14,13 @@ Thank you to everyone whose work has made this possible. Primary maintainers =================== -* Kostiantyn Rybnikov +* Tom Schraitle * Sébastien Celles +Old maintainer: + +* Kostiantyn Rybnikov + Significant contributors ======================== @@ -37,7 +41,6 @@ Significant contributors * robi-wan * sbrudenell * T. Jameson Little -* Tom Schraitle * Thomas Laferriere * Tuure Laurinolli * Tyler Cross diff --git a/changelog.d/335.doc.rst b/changelog.d/335.doc.rst deleted file mode 100644 index 1d29fb87..00000000 --- a/changelog.d/335.doc.rst +++ /dev/null @@ -1,2 +0,0 @@ -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/changelog.d/340.doc.rst b/changelog.d/340.doc.rst deleted file mode 100644 index 807e401c..00000000 --- a/changelog.d/340.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Describe how to get version from a file \ No newline at end of file diff --git a/changelog.d/343.doc.rst b/changelog.d/343.doc.rst deleted file mode 100644 index 630d7474..00000000 --- a/changelog.d/343.doc.rst +++ /dev/null @@ -1,2 +0,0 @@ -Describe combining Pydantic with semver in the "Advanced topic" -section. diff --git a/changelog.d/350.doc.rst b/changelog.d/350.doc.rst deleted file mode 100644 index 2fa92f0a..00000000 --- a/changelog.d/350.doc.rst +++ /dev/null @@ -1,2 +0,0 @@ -Restructure usage section. Create subdirectory "usage/" and splitted -all section into different files. diff --git a/changelog.d/351.doc.rst b/changelog.d/351.doc.rst deleted file mode 100644 index 0b5199fa..00000000 --- a/changelog.d/351.doc.rst +++ /dev/null @@ -1,4 +0,0 @@ -Introduce new topics for: - -* "Migration to semver3" -* "Advanced topics" diff --git a/changelog.d/364.feature.rst b/changelog.d/364.feature.rst deleted file mode 100644 index 885a6c85..00000000 --- a/changelog.d/364.feature.rst +++ /dev/null @@ -1,3 +0,0 @@ -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`. diff --git a/changelog.d/372.deprecation.rst b/changelog.d/372.deprecation.rst deleted file mode 100644 index c475f532..00000000 --- a/changelog.d/372.deprecation.rst +++ /dev/null @@ -1,8 +0,0 @@ -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. diff --git a/changelog.d/374.bugfix.rst b/changelog.d/374.bugfix.rst deleted file mode 100644 index fd4e7ea4..00000000 --- a/changelog.d/374.bugfix.rst +++ /dev/null @@ -1,3 +0,0 @@ -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.]``. diff --git a/changelog.d/378.trivial.rst b/changelog.d/378.trivial.rst deleted file mode 100644 index 2ecd2b73..00000000 --- a/changelog.d/378.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Fix some typos in Towncrier configuration diff --git a/changelog.d/pr359.feature.rst b/changelog.d/pr359.feature.rst deleted file mode 100644 index 5c18c9d2..00000000 --- a/changelog.d/pr359.feature.rst +++ /dev/null @@ -1,2 +0,0 @@ -Add optional parameter ``optional_minor_and_patch`` in :meth:`.Version.parse` to allow optional -minor and patch parts. diff --git a/changelog.d/pr362.feature.rst b/changelog.d/pr362.feature.rst deleted file mode 100644 index 1b7cc120..00000000 --- a/changelog.d/pr362.feature.rst +++ /dev/null @@ -1,2 +0,0 @@ -Make :meth:`.Version.match` accept a bare version string as match expression, defaulting to -equality testing. diff --git a/docs/usage/semver-version.rst b/docs/usage/semver-version.rst index b3d2c274..8eeab62f 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.3' + '3.0.0-dev.4' diff --git a/src/semver/__about__.py b/src/semver/__about__.py index d1dc8e3f..0f7150bf 100644 --- a/src/semver/__about__.py +++ b/src/semver/__about__.py @@ -16,7 +16,7 @@ """ #: Semver version -__version__ = "3.0.0-dev.3" +__version__ = "3.0.0-dev.4" #: Original semver author __author__ = "Kostiantyn Rybnikov"