Repository: python-semver/python-semver (python)

Issue:
semver.VersionInfo allows negative numbers
# Situation

If you pass negative numbers into `semver.VersionInfo` it doesn't raise any errors:

```python
>>> import semver
>>> semver.VersionInfo(-2)                                                                                                                           
VersionInfo(major=-2, minor=0, patch=0, prerelease=None, build=None)
```

Tested with latest 2.11.0


# Proposal

I guess, this shouldn't happen. We should raise a ValueError exception.
Would integrate that into a 2.12.0 release and into the upcoming 3.0.0-alpha0.

@tlaferriere Thomas, can you conform? Do you agree?




Pull request description:
Fix #291: Disallow negative numbers in VersionInfo
This PR fixes #291 and contains the following change:

Disallow negative numbers of `major`, `minor`, and `patch` in `semver.VersionInfo.`
Reason: a version can only contain positive numbers

@tlaferriere This is the corresponding pull request. Maybe there are better ways to do it. :wink: 

I would release 2.12.0 if you think this is useful.

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 46a94ee..2fdfa8d 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -6,6 +6,36 @@ Change Log
 All notable changes to this code base will be documented in this file,
 in every released version.
 
+Version 2.12.0
+==============
+
+:Released:
+:Maintainer: Tom Schraitle
+
+Features
+--------
+
+n/a
+
+
+Bug Fixes
+---------
+
+* :gh:`291` (:pr:`292`): Disallow negative numbers of
+  major, minor, and patch for ``semver.VersionInfo``
+
+
+Additions
+---------
+
+n/a
+
+
+Deprecations
+------------
+
+n/a
+
 
 Version 2.11.0
 ==============
diff --git a/docs/usage.rst b/docs/usage.rst
index 010f1c0..05d4508 100644
--- a/docs/usage.rst
+++ b/docs/usage.rst
@@ -63,6 +63,14 @@ A :class:`semver.VersionInfo` instance can be created in different ways:
     >>> semver.VersionInfo(**d)
     VersionInfo(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4')
 
+  Keep in mind, the ``major``, ``minor``, ``patch`` parts has to
+  be positive.
+
+    >>> semver.VersionInfo(-1)
+    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.
diff --git a/semver.py b/semver.py
index 1e15a08..bd29882 100644
--- a/semver.py
+++ b/semver.py
@@ -243,9 +243,24 @@ class VersionInfo(object):
     )
 
     def __init__(self, major, minor=0, patch=0, prerelease=None, build=None):
-        self._major = int(major)
-        self._minor = int(minor)
-        self._patch = int(patch)
+        # Build a dictionary of the arguments except prerelease and build
+        version_parts = {
+            "major": major,
+            "minor": minor,
+            "patch": patch,
+        }
+
+        for name, value in version_parts.items():
+            value = int(value)
+            version_parts[name] = value
+            if value < 0:
+                raise ValueError(
+                    "{!r} is negative. A version can only be positive.".format(name)
+                )
+
+        self._major = version_parts["major"]
+        self._minor = version_parts["minor"]
+        self._patch = version_parts["patch"]
         self._prerelease = None if prerelease is None else str(prerelease)
         self._build = None if build is None else str(build)
 


Test patch:
diff --git a/test_semver.py b/test_semver.py
index 0aeedbd..3637982 100644
--- a/test_semver.py
+++ b/test_semver.py
@@ -73,6 +73,20 @@ def test_fordocstrings(func):
     assert func.__doc__, "Need a docstring for function %r" % func.__name
 
 
+@pytest.mark.parametrize(
+    "ver",
+    [
+        {"major": -1},
+        {"major": 1, "minor": -2},
+        {"major": 1, "minor": 2, "patch": -3},
+        {"major": 1, "minor": -2, "patch": 3},
+    ],
+)
+def test_should_not_allow_negative_numbers(ver):
+    with pytest.raises(ValueError, match=".* is negative. .*"):
+        VersionInfo(**ver)
+
+
 @pytest.mark.parametrize(
     "version,expected",
     [