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" pFad - Phonifier reborn

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

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


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy