summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRaphaël Barrois <raphael.barrois@polytechnique.org>2019-08-18 17:41:33 +0200
committerRaphaël Barrois <raphael.barrois@polytechnique.org>2019-08-24 15:13:09 +0200
commita5cc0fb509b2c515ae73c85f9a4d426a3f9100e3 (patch)
tree2beb1ad109f28f995b293a548da4ef76818b7bc3
parentfdef1e9cdae901d095d8e8c9cd6fa6adcfe02074 (diff)
downloadsemantic-version-a5cc0fb509b2c515ae73c85f9a4d426a3f9100e3.tar.gz
Allow Version(major=1, ...).
Eases the creation of version objects from existing versions. We still validate the type and structure of each component.
-rw-r--r--ChangeLog8
-rw-r--r--README.rst16
-rw-r--r--docs/reference.rst10
-rw-r--r--semantic_version/base.py45
-rwxr-xr-xtests/test_parsing.py21
5 files changed, 98 insertions, 2 deletions
diff --git a/ChangeLog b/ChangeLog
index 8804796..ca4f3af 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,14 @@
ChangeLog
=========
+2.7.0 (unreleased)
+------------------
+
+*New:*
+
+ * Allow creation of a ``Version`` directly from parsed components, as keyword arguments
+ (``Version(major=1, minor=2, patch=3)``)
+
2.6.0 (2016-09-25)
------------------
diff --git a/README.rst b/README.rst
index 682a8c7..ac42f72 100644
--- a/README.rst
+++ b/README.rst
@@ -149,6 +149,22 @@ It is also possible to check whether a given string is a proper semantic version
False
+Finally, one may create a :class:`Version` with named components instead:
+
+.. code-block:: pycon
+
+ >>> semantic_version.Version(major=0, minor=1, patch=2)
+ Version('0.1.2')
+
+In that case, ``major``, ``minor`` and ``patch`` are mandatory, and must be integers.
+``prerelease`` and ``patch``, if provided, must be tuples of strings:
+
+.. code-block:: pycon
+
+ >>> semantic_version.Version(major=0, minor=1, patch=2, prerelease=('alpha', '2'))
+ Version('0.1.2-alpha.2')
+
+
Requirement specification
-------------------------
diff --git a/docs/reference.rst b/docs/reference.rst
index 7c91200..58aa320 100644
--- a/docs/reference.rst
+++ b/docs/reference.rst
@@ -76,6 +76,16 @@ Representing a version (the Version class)
>>> str(Version('1.1.1'))
'1.1.1'
+.. class:: Version(major: int, minor: int, patch: int, prereleases: tuple, build: tuple[, partial=False])
+
+ Constructed from named components:
+
+ .. code-block:: pycon
+
+ >>> Version(major=1, minor=2, patch=3)
+ Version('1.2.3')
+
+
.. rubric:: Attributes
diff --git a/semantic_version/base.py b/semantic_version/base.py
index 4ddaf05..49c3fb5 100644
--- a/semantic_version/base.py
+++ b/semantic_version/base.py
@@ -73,8 +73,30 @@ class Version(object):
version_re = re.compile(r'^(\d+)\.(\d+)\.(\d+)(?:-([0-9a-zA-Z.-]+))?(?:\+([0-9a-zA-Z.-]+))?$')
partial_version_re = re.compile(r'^(\d+)(?:\.(\d+)(?:\.(\d+))?)?(?:-([0-9a-zA-Z.-]*))?(?:\+([0-9a-zA-Z.-]*))?$')
- def __init__(self, version_string, partial=False):
- major, minor, patch, prerelease, build = self.parse(version_string, partial)
+ def __init__(
+ self,
+ version_string=None,
+ *,
+ major=None,
+ minor=None,
+ patch=None,
+ prerelease=None,
+ build=None,
+ partial=False,
+ ):
+ has_text = version_string is not None
+ has_parts = not (major is minor is patch is prerelease is build is None)
+ if not has_text ^ has_parts:
+ raise ValueError("Call either Version('1.2.3') or Version(major=1, ...).")
+
+ if has_text:
+ major, minor, patch, prerelease, build = self.parse(version_string, partial)
+ else:
+ # Convenience: allow to omit prerelease/build.
+ if not partial:
+ prerelease = prerelease or ()
+ build = build or ()
+ self._validate_kwargs(major, minor, patch, prerelease, build, partial)
self.major = major
self.minor = minor
@@ -254,6 +276,25 @@ class Version(object):
if item[0] == '0' and item.isdigit() and item != '0' and not allow_leading_zeroes:
raise ValueError("Invalid leading zero in identifier %r" % item)
+ @classmethod
+ def _validate_kwargs(cls, major, minor, patch, prerelease, build, partial):
+ if (
+ major != int(major)
+ or minor != cls._coerce(minor, partial)
+ or patch != cls._coerce(patch, partial)
+ or prerelease is None and not partial
+ or build is None and not partial
+ ):
+ raise ValueError(
+ "Invalid kwargs to Version(major=%r, minor=%r, patch=%r, "
+ "prerelease=%r, build=%r, partial=%r" % (
+ major, minor, patch, prerelease, build, partial
+ ))
+ if prerelease is not None:
+ cls._validate_identifiers(prerelease, allow_leading_zeroes=False)
+ if build is not None:
+ cls._validate_identifiers(build, allow_leading_zeroes=True)
+
def __iter__(self):
return iter((self.major, self.minor, self.patch, self.prerelease, self.build))
diff --git a/tests/test_parsing.py b/tests/test_parsing.py
index 8fd22da..af99d05 100755
--- a/tests/test_parsing.py
+++ b/tests/test_parsing.py
@@ -28,6 +28,15 @@ class ParsingTestCase(unittest.TestCase):
'0.1.2-rc1.3-14.15+build.2012-01-01.11h34',
]
+ valid_fields = [
+ ('0.1.1', [0, 1, 1, (), ()]),
+ ('0.1.1', [0, 1, 1, None, None]),
+ ('0.1.2-rc1', [0, 1, 2, ('rc1',), ()]),
+ ('0.1.2-rc1.3.4', [0, 1, 2, ('rc1', '3', '4'), ()]),
+ ('0.1.2+build42-12.2012-01-01.12h23', [0, 1, 2, (), ('build42-12', '2012-01-01', '12h23')]),
+ ('0.1.2-rc1.3-14.15+build.2012-01-01.11h34', [0, 1, 2, ('rc1', '3-14', '15'), ('build', '2012-01-01', '11h34')]),
+ ]
+
def test_invalid(self):
for invalid in self.invalids:
self.assertRaises(ValueError, semantic_version.Version, invalid)
@@ -37,6 +46,18 @@ class ParsingTestCase(unittest.TestCase):
version = semantic_version.Version(valid)
self.assertEqual(valid, str(version))
+ def test_kwargs(self):
+ for text, fields in self.valid_fields:
+ major, minor, patch, prerelease, build = fields
+ version = semantic_version.Version(
+ major=major,
+ minor=minor,
+ patch=patch,
+ prerelease=prerelease,
+ build=build,
+ )
+ self.assertEqual(text, str(version))
+
class ComparisonTestCase(unittest.TestCase):
order = [