Introduce Spec.filter and Spec.select.

Signed-off-by: Raphaël Barrois <raphael.barrois@polytechnique.org>
This commit is contained in:
Raphaël Barrois
2012-05-22 02:00:47 +02:00
parent aeeecf6327
commit 4691a44b37
7 changed files with 159 additions and 18 deletions

13
README
View File

@@ -25,9 +25,9 @@ Compare it to other versions::
>>> v < Version('0.1.2')
True
>>> sorted([Version('0.1.1'), Version('0.11.1'), Version('0.1.1-alpha')])
[<SemVer(0, 1, 1, ('alpha',), ())>,
<SemVer(0, 1, 1, (), ())>,
<SemVer(0, 11, 1, (), ())>]
[<Version(0, 1, 1, ('alpha',), ())>,
<Version(0, 1, 1, (), ())>,
<Version(0, 11, 1, (), ())>]
Define a simple specification::
@@ -49,6 +49,13 @@ Define complex specifications::
False
Select the best compatible version from a list::
>>> s = Spec('>=0.1.1,<0.2.0')
>>> s.select([Version('0.1.1'), Version('0.1.9-alpha'), Version('0.1.9-alpha+1'))
<Version(0, 1, 9, ('alpha',), (1,))>
Framework integration
=====================

View File

@@ -1,9 +1,19 @@
ChangeLog
=========
2.0.0 (Master)
2.1.0 (Master)
--------------
*New:*
* Add :func:`semantic_version.Spec.filter` (filter a list of :class:`~semantic_version.Version`)
* Add :func:`semantic_version.Spec.select` (select the highest
:class:`~semantic_version.Version` from a list)
* Update :func:`semantic_version.Version.__repr__`
2.0.0 (22/05/2012)
------------------
*Backwards incompatible changes:*
* Removed "loose" specification support

View File

@@ -9,7 +9,7 @@ python-semanticversion
This small python library provides a few tools to handle `SemVer`_ in Python.
The first release (1.0.0) should handle the 2.0.0-rc1 version of the SemVer scheme.
It follows strictly the 2.0.0-rc1 version of the SemVer scheme.
Getting started
@@ -116,6 +116,26 @@ Combining specifications can be expressed in two ways:
>>> Spec('>=0.1.1', '!=0.2.4-alpha,<0.3.0')
Using a specification
"""""""""""""""""""""
The :func:`Spec.filter` method filters an iterable of :class:`Version`::
>>> s = Spec('>=0.1.0,<0.4.0')
>>> versions = (Version('0.%d.0' % i) for i in range(6))
>>> for v in s.filter(versions):
... print v
0.1.0
0.2.0
0.3.0
It is also possible to select the 'best' version from such iterables::
>>> s = Spec('>=0.1.0,<0.4.0')
>>> versions = (Version('0.%d.0' % i) for i in range(6))
>>> s.select(versions)
<Version(0, 3, 0, (), ())>
Including pre-release identifiers in specifications
"""""""""""""""""""""""""""""""""""""""""""""""""""

View File

@@ -53,7 +53,7 @@ Representing a version (the Version class)
Constructed from a textual version string::
>>> Version('1.1.1')
<SemVer(1, 1, 1, [], [])>
<Version(1, 1, 1, [], [])>
>>> str(Version('1.1.1'))
'1.1.1'
@@ -156,7 +156,7 @@ Representing a version (the Version class)
>>> v = Version('0.1.1-rc2+build4.4')
>>> v
<SemVer(0, 1, 1, ['rc2'], ['build4', '4'])>
<Version(0, 1, 1, ['rc2'], ['build4', '4'])>
>>> str(v)
'0.1.1-rc2+build4.4'
@@ -252,19 +252,19 @@ rules apply:
>>> Spec('>=1.0.0,<1.2.0,!=1.1.4')
<Spec: (
<SpecItem: >= <~SemVer: 1 0 0 None None>>,
<SpecItem: < <~SemVer: 1 2 0 None None>>,
<SpecItem: != <~SemVer: 1 1 4 None None>>
<SpecItem: >= <~Version(1 0 0 None None)>>,
<SpecItem: < <~Version(1 2 0 None None)>>,
<SpecItem: != <~Version(1 1 4 None None)>>
)>
Version specifications may also be passed in separated arguments::
>>> Spec('>=1.0.0', '<1.2.0', '!=1.1.4,!=1.1.13')
<Spec: (
<SpecItem: >= <~SemVer: 1 0 0 None None>>,
<SpecItem: < <SemVer: 1 2 0 None None>>,
<SpecItem: != <~SemVer: 1 1 4 None None>>
<SpecItem: != <~SemVer: 1 1 13 None None>>
<SpecItem: >= <~Version(1 0 0 None None)>>,
<SpecItem: < <Version(1 2 0 None None)>>,
<SpecItem: != <~Version(1 1 4 None None)>>
<SpecItem: != <~Version(1 1 13 None None)>>
)>
@@ -290,6 +290,36 @@ rules apply:
:type version: :class:`Version`
:rtype: ``bool``
.. method:: filter(self, versions)
Extract all compatible :class:`versions <Version>` from an iterable of
:class:`Version` objects.
:param versions: The versions to filter
:type versions: iterable of :class:`Version`
:yield: :class:`Version`
.. method:: select(self, versions)
Select the highest compatible version from an iterable of :class:`Version`
objects.
.. sourcecode:: pycon
>>> s = Spec('>=0.1.0')
>>> s.select([])
None
>>> s.select([Version('0.1.0'), Version('0.1.3'), Version('0.1.1')])
<Version(0, 1, 3, (), ())>
:param versions: The versions to filter
:type versions: iterable of :class:`Version`
:rtype: The highest compatible :class:`Version` if at least one of the
given versions is compatible; :class:`None` otherwise.
.. method:: __contains__(self, version)
Alias of the :func:`match` method;
@@ -341,7 +371,7 @@ rules apply:
Stores a version specification, defined from a string::
>>> SpecItem('>=0.1.1')
<SpecItem: >= <SemVer(0, 1, 1, [], [])>>
<SpecItem: >= <Version(0, 1, 1, [], [])>>
This allows to test :class:`Version` objects against the :class:`SpecItem`::

View File

@@ -2,7 +2,7 @@
# Copyright (c) 2012 Raphaël Barrois
__version__ = '2.0.0'
__version__ = '2.1.0-alpha'
from .base import compare, match, Spec, SpecItem, Version

View File

@@ -127,8 +127,8 @@ class Version(object):
return version
def __repr__(self):
return '<%sSemVer(%s, %s, %s, %r, %r)>' % (
'~' if self.partial else '',
return '<%sVersion(%s, %s, %s, %r, %r)>' % (
', partial=True' if self.partial else '',
self.major,
self.minor,
self.patch,
@@ -311,6 +311,19 @@ class Spec(object):
"""Check whether a Version satisfies the Spec."""
return all(spec.match(version) for spec in self.specs)
def filter(self, versions):
"""Filter an iterable of versions satisfying the Spec."""
for version in versions:
if self.match(version):
yield version
def select(self, versions):
"""Select the best compatible version among an iterable of options."""
options = list(self.filter(versions))
if options:
return max(options)
return None
def __contains__(self, version):
if isinstance(version, Version):
return self.match(version)

View File

@@ -343,6 +343,67 @@ class SpecTestCase(unittest.TestCase):
self.assertEqual(slist1, slist2)
self.assertFalse(slist1 == spec_list_text)
def test_filter_empty(self):
s = base.Spec('>=0.1.1')
res = tuple(s.filter(()))
self.assertEqual((), res)
def test_filter_incompatible(self):
s = base.Spec('>=0.1.1,!=0.1.4')
res = tuple(s.filter([
base.Version('0.1.0'),
base.Version('0.1.4'),
base.Version('0.1.4-alpha'),
]))
self.assertEqual((), res)
def test_filter_compatible(self):
s = base.Spec('>=0.1.1,!=0.1.4,<0.2.0')
res = tuple(s.filter([
base.Version('0.1.0'),
base.Version('0.1.1'),
base.Version('0.1.5'),
base.Version('0.1.4-alpha'),
base.Version('0.1.2'),
base.Version('0.2.0-rc1'),
base.Version('3.14.15'),
]))
expected = (
base.Version('0.1.1'),
base.Version('0.1.5'),
base.Version('0.1.2'),
)
self.assertEqual(expected, res)
def test_select_empty(self):
s = base.Spec('>=0.1.1')
self.assertIsNone(s.select(()))
def test_select_incompatible(self):
s = base.Spec('>=0.1.1,!=0.1.4')
res = s.select([
base.Version('0.1.0'),
base.Version('0.1.4'),
base.Version('0.1.4-alpha'),
])
self.assertIsNone(res)
def test_select_compatible(self):
s = base.Spec('>=0.1.1,!=0.1.4,<0.2.0')
res = s.select([
base.Version('0.1.0'),
base.Version('0.1.1'),
base.Version('0.1.5'),
base.Version('0.1.4-alpha'),
base.Version('0.1.2'),
base.Version('0.2.0-rc1'),
base.Version('3.14.15'),
])
self.assertEqual(base.Version('0.1.5'), res)
def test_contains(self):
self.assertFalse('ii' in base.Spec('>=0.1.1'))