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

Issue:
Deprecate min_ver/max_ver and use builtin max()/min() functions
## Situation

~The `min_ver` and `max_ver` functions have currently this signature:~

```python
def max_ver(ver1, ver2):
    # ...
def min_ver(ver1, ver2):
    # ...
```

~In other words, these functions allow to **compare exactly two versions**. However, there may be situations, where you have a *list of versions*. This use case could not be done with the current implementation and needs a cumbersome for-loop.~

As we have comparison operators, there is no need for this functions anymore. Of course, this works only for VersionInfo types, not strings. If someone still wants to compare strings, we could document a replacement.

Action Items:

* [ ] add deprecation warning for both functions
* [ ] document replacement; maybe something like: `max(map(semver.VersionInfo.parse, ['1.1.0', '1.2.0', '2.1.0', '0.5.10', '0.4.99']))`

-----

Old issue:

## Suggested Solution

To allow to compare more than two versions, it would be useful to change the above signature like this:

```python
def max_ver(ver1, ver2, *others):
    # ...
def min_ver(ver1, ver2, *others):
    # ...
```

This would resemble the builtin functions `min` and `max`. The above functions could be used like this:

```python
from semver import max_ver

m = max_ver("3.0.0", "2.0.0", "1.0.0", "4.0.0")
print(m)
# => "1.0.0"
```

Benefits:

* No incompatible changes to API; it's still possible to use two arguments
* Only minimal changes in the implementation

## Alternative Solution

The proposed solution may has some drawbacks:

* it doesn't allow to pass an iterable (like the builtin functions `min` and `max`).
* could hit a performance bottleneck

If you don't like the proposed solution, we could:

* leave the current implementation of `min_ver` and `max_ver` untouched, or,
* allow iterables in  `min_ver` and `max_ver` (would introduce incompatible API changes),
* introduce additional functions with the suffix `_iter` which allows to optimize for iterables. The signature could be:

```python
def max_ver_iter(iterable, *, default=None, key=None):
    """Returns the biggest version from an iterable

   :param iterable: an iterable with version strings
   :param default: specifies an object to return if the provided iterable is empty.
   :param key: specifies a one-argument ordering function like that used for list.sort()
   :raises: ValueError (If the iterable is empty and default is not provided)
    """
```


## Questions

* Does it make sense?
* Should we raise something if the `*others` tuple contains an invalid version?
* Should we ignore invalid versions? Or provide a flag (something like `raise_if_invalid`) which can be set?
* Anything else?

Pull request description:
Deprecate `max_ver` and `min_ver`
This could close #160 .

As I said in this [comment](https://github.com/python-semver/python-semver/issues/160#issuecomment-642328641), this PR will deprecate functions that could be useful as a shortcut.
As you can see in the new examples in the documentation, it is quite a mouthful to find the maximum of two version strings.

As an alternative to this we could keep `max_ver` and `min_ver` as utility functions that automatically convert from whatever supported types we have, passes them to `max` or `min` then converts back into the input types to return them.

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 2671ef2..4cd18b6 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -29,10 +29,12 @@ Additions
 
 n/a
 
-Removals
+Deprecations
 --------
 
-n/a
+* :gh:`160` (:pr:`264`):
+    * :func:`semver.max_ver`
+    * :func:`semver.min_ver`
 
 
 Version 2.10.1
@@ -58,14 +60,6 @@ Bug Fixes
   to always return a ``VersionInfo`` instance.
 
 
-Additions
----------
-
-
-Removals
---------
-
-
 Version 2.10.0
 ==============
 
@@ -95,7 +89,7 @@ Additions
 * :pr:`228`: Added better doctest integration
 
 
-Removals
+Deprecations
 --------
 * :gh:`225` (:pr:`229`): Output a DeprecationWarning for the following functions:
 
@@ -139,12 +133,6 @@ Bug Fixes
 * :gh:`192` (:pr:`193`): Fixed "pysemver" and "pysemver bump" when called without arguments
 
 
-Removals
---------
-
-not available
-
-
 Version 2.9.0
 =============
 :Released: 2019-10-30
@@ -184,7 +172,7 @@ Bug Fixes
 Removals
 --------
 
-* :gh:`111` (:pr:`110`): Droped Python 3.3
+* :gh:`111` (:pr:`110`): Dropped Python 3.3
 * :gh:`148` (:pr:`149`): Removed and replaced ``python setup.py test``
 
 
diff --git a/docs/usage.rst b/docs/usage.rst
index b61e284..2f23e57 100644
--- a/docs/usage.rst
+++ b/docs/usage.rst
@@ -11,9 +11,9 @@ are met.
 Knowing the Implemented semver.org Version
 ------------------------------------------
 
-The semver.org page is the authorative specification of how semantical
-versioning is definied.
-To know which version of semver.org is implemented in the semver libary,
+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
@@ -445,7 +445,11 @@ To compare two versions depends on your type:
 
 Other types cannot be compared.
 
-If you need to convert some types into other, refer to :ref:`sec.convert.versions`.
+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`.
 
 
 
@@ -476,9 +480,47 @@ That gives you the following possibilities to express your condition:
     >>> 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:`semver.VersionInfo` implements :func:`__gt__()` and :func:`__lt__()`, it can be used with builtins requiring
+
+.. code-block:: python
+
+    >>> max([semver.VersionInfo(0, 1, 0), semver.VersionInfo(0, 2, 0), semver.VersionInfo(0, 1, 3)])
+    VersionInfo(major=0, minor=2, patch=0, prerelease=None, build=None)
+    >>> min([semver.VersionInfo(0, 1, 0), semver.VersionInfo(0, 2, 0), semver.VersionInfo(0, 1, 3)])
+    VersionInfo(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:`semver.VersionInfo`).
+
+For example, here are the maximum and minimum versions of a list of version strings:
+
+.. code-block:: python
+
+    >>> str(max(map(semver.VersionInfo.parse, ['1.1.0', '1.2.0', '2.1.0', '0.5.10', '0.4.99'])))
+    '2.1.0'
+    >>> str(min(map(semver.VersionInfo.parse, ['1.1.0', '1.2.0', '2.1.0', '0.5.10', '0.4.99'])))
+    '0.4.99'
+
+And the same can be done with tuples:
+
+.. code-block:: python
+
+    >>> max(map(lambda v: semver.VersionInfo(*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: semver.VersionInfo(*v), [(1, 1, 0), (1, 2, 0), (2, 1, 0), (0, 5, 10), (0, 4, 99)])).to_tuple()
+    (0, 4, 99, None, None)
 
-Getting Minimum and Maximum of two Versions
--------------------------------------------
+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
 
@@ -575,6 +617,28 @@ them with code which is compatible for future versions:
      >>> 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(semver.VersionInfo.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(semver.VersionInfo.parse, ("1.2.3", "1.2.4"))))
+     >>> s1 == s2
+     True
+
 * :func:`semver.parse`
 
   Replace it with :func:`semver.VersionInfo.parse` and
diff --git a/semver.py b/semver.py
index 0c98af9..2571684 100644
--- a/semver.py
+++ b/semver.py
@@ -812,6 +812,7 @@ def match(version, match_expr):
     return ver.match(match_expr)
 
 
+@deprecated(replace="max", version="2.10.2")
 def max_ver(ver1, ver2):
     """
     Returns the greater version of two versions strings.
@@ -835,6 +836,7 @@ def max_ver(ver1, ver2):
         return ver2
 
 
+@deprecated(replace="min", version="2.10.2")
 def min_ver(ver1, ver2):
     """
     Returns the smaller version of two versions strings.


Test patch:
diff --git a/test_semver.py b/test_semver.py
index 015cb7d..b23576d 100644
--- a/test_semver.py
+++ b/test_semver.py
@@ -1036,6 +1036,8 @@ def test_should_versioninfo_isvalid():
         (parse, ("1.2.3",), {}),
         (parse_version_info, ("1.2.3",), {}),
         (replace, ("1.2.3",), dict(major=2, patch=10)),
+        (max_ver, ("1.2.3", "1.2.4"), {}),
+        (min_ver, ("1.2.3", "1.2.4"), {}),
     ],
 )
 def test_should_raise_deprecation_warnings(func, args, kwargs):