diff options
-rw-r--r-- | README | 3 | ||||
-rw-r--r-- | doc/changelog.rst | 5 | ||||
-rw-r--r-- | doc/django.rst | 8 | ||||
-rw-r--r-- | doc/index.rst | 62 | ||||
-rw-r--r-- | doc/reference.rst | 221 | ||||
-rw-r--r-- | src/semantic_version/__init__.py | 2 | ||||
-rw-r--r-- | src/semantic_version/base.py | 17 | ||||
-rw-r--r-- | src/semantic_version/django_fields.py | 15 | ||||
-rw-r--r-- | tests/django_test_app/models.py | 7 | ||||
-rwxr-xr-x | tests/test_base.py | 3 | ||||
-rw-r--r-- | tests/test_django.py | 31 |
11 files changed, 152 insertions, 222 deletions
@@ -40,8 +40,7 @@ Define a simple specification:: Define complex specifications:: - >>> from semantic_version import SpecList - >>> s = SpecList('>=0.1.1,<0.2.0') + >>> s = Spec('>=0.1.1,<0.2.0') >>> Version('0.1.2') in s True >>> Version('0.3.0') in s diff --git a/doc/changelog.rst b/doc/changelog.rst index e4476ac..7b1cb8e 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -1,14 +1,15 @@ ChangeLog ========= -1.3.0 (Master) +2.0.0 (Master) -------------- *Backwards incompatible changes:* * Removed "loose" specification support * Cleanup :class:`~semantic_version.Spec` to be more intuitive. - * Rename Spec to SpecItem and SpecList to Spec. + * Merge Spec and SpecList into :class:`~semantic_version.Spec`. + * Remove :class:`~semantic_version.django_fields.SpecListField` 1.2.0 (18/05/2012) ------------------ diff --git a/doc/django.rst b/doc/django.rst index 2117c21..8cfdbca 100644 --- a/doc/django.rst +++ b/doc/django.rst @@ -3,10 +3,9 @@ Interaction with Django .. module:: semantic_version.django_fields -The ``python-semanticversion`` package provides three custom fields for Django: +The ``python-semanticversion`` package provides two custom fields for Django: - :class:`VersionField`: stores a :class:`semantic_version.Version` object -- :class:`SpecItemField`: stores a :class:`semantic_version.SpecItem` object - :class:`SpecField`: stores a :class:`semantic_version.Spec` object Those fields are :class:`django.db.models.CharField` subclasses, @@ -22,11 +21,6 @@ with their :attr:`~django.db.models.CharField.max_length` defaulting to 200. Boolean; whether :attr:`~semantic_version.Version.partial` versions are allowed. -.. class:: SpecItemField - - Stores a :class:`semantic_version.SpecItem` as its string representation. - - .. class:: SpecField Stores a :class:`semantic_version.Spec` as its comma-separated string representation. diff --git a/doc/index.rst b/doc/index.rst index cae45f5..952e30e 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -82,9 +82,9 @@ Obviously, :class:`Versions <Version>` can be compared:: Requirement specification ------------------------- -The :class:`SpecItem` object describes a range of accepted versions:: +The :class:`Spec` object describes a range of accepted versions:: - >>> s = SpecItem('>=0.1.1') # At least 0.1.1 + >>> s = Spec('>=0.1.1') # At least 0.1.1 >>> s.match(Version('0.1.1')) True >>> s.match(Version('0.1.1-alpha1')) # pre-release satisfy version spec @@ -94,61 +94,55 @@ The :class:`SpecItem` object describes a range of accepted versions:: Simpler test syntax is also available using the ``in`` keyword:: - >>> s = SpecItem('==0.1.1') + >>> s = Spec('==0.1.1') >>> Version('0.1.1-alpha1') in s True >>> Version('0.1.2') in s False +Combining specifications can be expressed in two ways: + +- Components separated by commas in a single string:: + + >>> Spec('>=0.1.1,<0.3.0') + +- Components given as different arguments:: + + >>> Spec('>=0.1.1', '<0.3.0') + +- A mix of both versions:: + + >>> Spec('>=0.1.1', '!=0.2.4-alpha,<0.3.0') + + Including pre-release identifiers in specifications """"""""""""""""""""""""""""""""""""""""""""""""""" -When testing a :class:`Version` against a :class:`SpecItem`, comparisons are only -performed for components defined in the :class:`SpecItem`; thus, a pre-release +When testing a :class:`Version` against a :class:`Spec`, comparisons are only +performed for components defined in the :class:`Spec`; thus, a pre-release version (``1.0.0-alpha``), while not strictly equal to the non pre-release -version (``1.0.0``), satisfies the ``==1.0.0`` :class:`SpecItem`. +version (``1.0.0``), satisfies the ``==1.0.0`` :class:`Spec`. -Pre-release identifiers will only be compared if included in the :class:`SpecItem` +Pre-release identifiers will only be compared if included in the :class:`Spec` definition or (for the empty pre-release number) if a single dash is appended (``1.0.0-``):: - >>> Version('0.1.0-alpha') in SpecItem('>=0.1.0') # No pre-release identifier + >>> Version('0.1.0-alpha') in Spec('>=0.1.0') # No pre-release identifier True - >>> Version('0.1.0-alpha') in SpecItem('>=0.1.0-') # Include pre-release in checks + >>> Version('0.1.0-alpha') in Spec('>=0.1.0-') # Include pre-release in checks False Including build identifiers in specifications """"""""""""""""""""""""""""""""""""""""""""" The same rule applies for the build identifier: comparisons will include it only -if it was included in the :class:`SpecItem` definition, or - for the unnumbered build +if it was included in the :class:`Spec` definition, or - for the unnumbered build version - if a single + is appended to the definition(``1.0.0+``, ``1.0.0-alpha+``):: - >>> Version('1.0.0+build2') in SpecItem('<=1.0.0') # Build identifier ignored + >>> Version('1.0.0+build2') in Spec('<=1.0.0') # Build identifier ignored True - >>> Version('1.0.0+build2') in SpecItem('<=1.0.0+') # Include build in checks - False - - -Combining requirements -====================== - -In order to express complex version specifications, use the :class:`Spec` class:: - - >>> # At least 0.1.1, not 0.2.0, avoid broken 0.1.5-alpha. - >>> sl = Spec('>=0.1.1,<0.2.0,!=0.1.5-alpha') - >>> sl.match(Version('0.1.1')) - True - >>> Version('0.1.1-rc1') in sl - True - >>> Version('0.1.2') in sl - True - >>> Version('0.2.0-alpha') in sl - False - >>> Version('0.1.5-alpha') in sl - False - >>> Version('0.1.5-alpha+build2') in sl + >>> Version('1.0.0+build2') in Spec('<=1.0.0+') # Include build in checks False @@ -156,7 +150,7 @@ Using with Django ================= The :mod:`semantic_version.django_fields` module provides django fields to -store :class:`Version`, :class:`SpecItem` or :class:`Spec` objects. +store :class:`Version` or :class:`Spec` objects. More documentation is available in the :doc:`django` section. diff --git a/doc/reference.rst b/doc/reference.rst index e785aa9..a173cb9 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -189,7 +189,7 @@ Representing a version (the Version class) :rtype: (major, minor, patch, prerelease, build) -Version specifications (the SpecItem class) +Version specifications (the Spec class) ------------------------------------------- @@ -215,13 +215,13 @@ In order to have version specification behave naturally, the rules are the follo This means that:: - >>> Version('1.1.1-rc1') in SpecItem('<1.1.1') + >>> Version('1.1.1-rc1') in Spec('<1.1.1') False - >>> Version('1.1.1-rc1') in SpecItem('<1.1.1-rc4') + >>> Version('1.1.1-rc1') in Spec('<1.1.1-rc4') True - >>> Version('1.1.1-rc1+build4') in SpecItem('<=1.1.1-rc1') + >>> Version('1.1.1-rc1+build4') in Spec('<=1.1.1-rc1') True - >>> Version('1.1.1-rc1+build4') in SpecItem('<=1.1.1-rc1+build2') + >>> Version('1.1.1-rc1+build4') in Spec('<=1.1.1-rc1+build2') False In order to force matches to *strictly* compare version numbers, these additional @@ -230,21 +230,114 @@ rules apply: * Setting a pre-release separator without a pre-release identifier (``<=1.1.1-``) forces match to take into account pre-release version:: - >>> Version('1.1.1-rc1') in SpecItem('<1.1.1') + >>> Version('1.1.1-rc1') in Spec('<1.1.1') False - >>> Version('1.1.1-rc1') in SpecItem('<1.1.1-') + >>> Version('1.1.1-rc1') in Spec('<1.1.1-') True * Setting a build separator without a build identifier (``>1.1.1+``) forces satisfaction tests to include both prerelease and build identifiers:: - >>> Version('1.1.1+build2') in SpecItem('>1.1.1') + >>> Version('1.1.1+build2') in Spec('>1.1.1') False - >>> Version('1.1.1+build2') in SpecItem('>1.1.1+') + >>> Version('1.1.1+build2') in Spec('>1.1.1+') True +.. class:: Spec(spec_string[, spec_string[, ...]]) + + Stores a list of :class:`SpecItem` and matches any :class:`Version` against all + contained :class:`specs <SpecItem>`. + + It is build from a comma-separated list of version specifications:: + + >>> 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>> + )> + + 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>> + )> + + + .. rubric:: Attributes + + + .. attribute:: specs + + Tuple of :class:`SpecItem`, the included specifications. + + + .. rubric:: Methods + + + .. method:: match(self, version) + + Test whether a given :class:`Version` matches all included :class:`SpecItem`:: + + >>> Spec('>=1.1.0,<1.1.2').match(Version('1.1.1')) + True + + :param version: The version to test against the specs + :type version: :class:`Version` + :rtype: ``bool`` + + .. method:: __contains__(self, version) + + Alias of the :func:`match` method; + allows the use of the ``version in speclist`` syntax:: + + >>> Version('1.1.1-alpha') in Spec('>=1.1.0,<1.1.1') + True + + + .. method:: __str__(self) + + Converting a :class:`Spec` returns the initial description string:: + + >>> str(Spec('>=0.1.1,!=0.1.2')) + '>=0.1.1,!=0.1.2' + + .. method:: __iter__(self) + + Returns an iterator over the contained specs:: + + >>> for spec in Spec('>=0.1.1,!=0.1.2'): + ... print spec + >=0.1.1 + !=0.1.2 + + .. method:: __hash__(self) + + Provides a hash based solely on the hash of contained specs. + + Allows using a :class:`Spec` as a dictionary key. + + + .. rubric:: Class methods + + + .. classmethod:: parse(self, specs_string) + + Retrieve a ``(*specs)`` tuple from a string. + + :param str requirement_string: The textual description of the specifications + :raises: :exc:`ValueError`: if the ``requirement_string`` is invalid. + :rtype: ``(*spec)`` tuple + + .. class:: SpecItem(spec_string) + .. note:: This class belong to the private python-semanticversion API. + Stores a version specification, defined from a string:: >>> SpecItem('>=0.1.1') @@ -310,15 +403,6 @@ rules apply: :rtype: ``bool`` - .. method:: __contains__(self, version) - - Alias of the :func:`match` method; - allows the use of the ``version in spec`` syntax:: - - >>> Version('1.1.1') in SpecItem('<=1.1.2') - True - - .. method:: __str__(self) Converting a :class:`SpecItem` to a string returns the initial description string:: @@ -383,105 +467,4 @@ rules apply: - -Combining version specifications (the Spec class) -------------------------------------------------- - -It may be useful to define a rule such as -"Accept any version between the first 1.0.0 (incl. pre-release) and strictly before 1.2.0; ecluding 1.1.4 which was broken.". - -This is possible with the :class:`Spec` class. - - -.. class:: Spec(spec_string[, spec_string[, ...]]) - - Stores a list of :class:`SpecItem` and matches any :class:`Version` against all - contained :class:`specs <SpecItem>`. - - It is build from a comma-separated list of version specifications:: - - >>> 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>> - )> - - 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>> - )> - - - .. rubric:: Attributes - - - .. attribute:: specs - - Tuple of :class:`SpecItem`, the included specifications. - - - .. rubric:: Methods - - - .. method:: match(self, version) - - Test whether a given :class:`Version` matches all included :class:`SpecItem`:: - - >>> Spec('>=1.1.0,<1.1.2').match(Version('1.1.1')) - True - - :param version: The version to test against the specs - :type version: :class:`Version` - :rtype: ``bool`` - - .. method:: __contains__(self, version) - - Alias of the :func:`match` method; - allows the use of the ``version in speclist`` syntax:: - - >>> Version('1.1.1-alpha') in Spec('>=1.1.0,<1.1.1') - True - - - .. method:: __str__(self) - - Converting a :class:`Spec` returns the initial description string:: - - >>> str(Spec('>=0.1.1,!=0.1.2')) - '>=0.1.1,!=0.1.2' - - .. method:: __iter__(self) - - Returns an iterator over the contained specs:: - - >>> for spec in Spec('>=0.1.1,!=0.1.2'): - ... print spec - >=0.1.1 - !=0.1.2 - - .. method:: __hash__(self) - - Provides a hash based solely on the hash of contained specs. - - Allows using a :class:`Spec` as a dictionary key. - - - .. rubric:: Class methods - - - .. classmethod:: parse(self, specs_string) - - Retrieve a ``(*specs)`` tuple from a string. - - :param str requirement_string: The textual description of the specifications - :raises: :exc:`ValueError`: if the ``requirement_string`` is invalid. - :rtype: ``(*spec)`` tuple - - .. _SemVer: http://semver.org/ diff --git a/src/semantic_version/__init__.py b/src/semantic_version/__init__.py index 9075b70..99a00dc 100644 --- a/src/semantic_version/__init__.py +++ b/src/semantic_version/__init__.py @@ -5,4 +5,4 @@ __version__ = '1.3.0-alpha' -from .base import compare, match, Version, Spec, SpecItem +from .base import compare, match, Version, Spec diff --git a/src/semantic_version/base.py b/src/semantic_version/base.py index 4f177b2..861063b 100644 --- a/src/semantic_version/base.py +++ b/src/semantic_version/base.py @@ -88,16 +88,8 @@ class Version(object): major, minor, patch, prerelease, build = match.groups() major = int(major) - - if minor is None: - return (major, None, None, None, None) - else: - minor = int(minor) - - if patch is None: - return (major, minor, None, None, None) - else: - patch = int(patch) + minor = int(minor) + patch = int(patch) if prerelease is None: if partial and (build is None): @@ -290,11 +282,6 @@ class SpecItem(object): else: # pragma: no cover raise ValueError('Unexpected match kind: %r' % self.kind) - def __contains__(self, version): - if isinstance(version, Version): - return self.match(version) - return False - def __str__(self): return '%s%s' % (self.kind, self.spec) diff --git a/src/semantic_version/django_fields.py b/src/semantic_version/django_fields.py index 7cc5d44..c6ef688 100644 --- a/src/semantic_version/django_fields.py +++ b/src/semantic_version/django_fields.py @@ -46,21 +46,6 @@ class VersionField(BaseSemVerField): return base.Version(value, partial=self.partial) -class SpecItemField(BaseSemVerField): - default_error_messages = { - 'invalid': _(u"Enter a valid version number spec in ==X.Y.Z format."), - } - description = _(u"Version specification") - - def to_python(self, value): - """Converts any value to a base.SpecItem field.""" - if value is None or value == '': - return value - if isinstance(value, base.SpecItem): - return value - return base.SpecItem(value) - - class SpecField(BaseSemVerField): default_error_messages = { 'invalid': _(u"Enter a valid version number spec list in ==X.Y.Z,>=A.B.C format."), diff --git a/tests/django_test_app/models.py b/tests/django_test_app/models.py index 8e1f2ce..53ed874 100644 --- a/tests/django_test_app/models.py +++ b/tests/django_test_app/models.py @@ -7,13 +7,10 @@ from semantic_version import django_fields as semver_fields class VersionModel(models.Model): version = semver_fields.VersionField(verbose_name='my version') - spec = semver_fields.SpecItemField(verbose_name='my spec') - speclist = semver_fields.SpecField(verbose_name='my spec list') + spec = semver_fields.SpecField(verbose_name='my spec') class PartialVersionModel(models.Model): partial = semver_fields.VersionField(partial=True, verbose_name='partial version') optional = semver_fields.VersionField(verbose_name='optional version', blank=True, null=True) - optional_spec = semver_fields.SpecItemField(verbose_name='optional spec', blank=True, null=True) - optional_speclist = semver_fields.SpecField(verbose_name='optional spec list', - blank=True, null=True) + optional_spec = semver_fields.SpecField(verbose_name='optional spec', blank=True, null=True) diff --git a/tests/test_base.py b/tests/test_base.py index 41ec6cf..22690b6 100755 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -249,13 +249,10 @@ class SpecItemTestCase(unittest.TestCase): for version_text in matching: version = base.Version(version_text) - self.assertTrue(version in spec, "%r should be in %r" % (version, spec)) self.assertTrue(spec.match(version), "%r should match %r" % (version, spec)) for version_text in failing: version = base.Version(version_text) - self.assertFalse(version in spec, - "%r should not be in %r" % (version, spec)) self.assertFalse(spec.match(version), "%r should not match %r" % (version, spec)) diff --git a/tests/test_django.py b/tests/test_django.py index 5768c47..b4a3c29 100644 --- a/tests/test_django.py +++ b/tests/test_django.py @@ -35,28 +35,25 @@ if django_loaded: # pragma: no cover @unittest.skipIf(not django_loaded, "Django not installed") class DjangoFieldTestCase(unittest.TestCase): def test_version(self): - obj = models.VersionModel(version='0.1.1', spec='>0.1.0', speclist='==0.1.1,!=0.1.1-alpha') + obj = models.VersionModel(version='0.1.1', spec='==0.1.1,!=0.1.1-alpha') self.assertEqual(semantic_version.Version('0.1.1'), obj.version) - self.assertEqual(semantic_version.SpecItem('>0.1.0'), obj.spec) - self.assertEqual(semantic_version.Spec('==0.1.1,!=0.1.1-alpha'), obj.speclist) + self.assertEqual(semantic_version.Spec('==0.1.1,!=0.1.1-alpha'), obj.spec) - alt_obj = models.VersionModel(version=obj.version, spec=obj.spec, speclist=obj.speclist) + alt_obj = models.VersionModel(version=obj.version, spec=obj.spec) self.assertEqual(semantic_version.Version('0.1.1'), alt_obj.version) - self.assertEqual(semantic_version.SpecItem('>0.1.0'), alt_obj.spec) - self.assertEqual(semantic_version.Spec('==0.1.1,!=0.1.1-alpha'), alt_obj.speclist) + self.assertEqual(semantic_version.Spec('==0.1.1,!=0.1.1-alpha'), alt_obj.spec) self.assertEqual(obj.spec, alt_obj.spec) self.assertEqual(obj.version, alt_obj.version) - self.assertEqual(obj.speclist, alt_obj.speclist) def test_invalid_input(self): self.assertRaises(ValueError, models.VersionModel, - version='0.1.1', spec='blah', speclist='==0.1.1,!=0.1.1-alpha') + version='0.1.1', spec='blah') self.assertRaises(ValueError, models.VersionModel, - version='0.1', spec='>0.1.1', speclist='==0.1.1,!=0.1.1-alpha') + version='0.1', spec='==0.1.1,!=0.1.1-alpha') self.assertRaises(ValueError, models.VersionModel, - version='0.1.1', spec='>0.1.1', speclist='==0,!=0.2') + version='0.1.1', spec='==0,!=0.2') def test_partial(self): obj = models.PartialVersionModel(partial='0.1.0') @@ -64,27 +61,23 @@ class DjangoFieldTestCase(unittest.TestCase): self.assertEqual(semantic_version.Version('0.1.0', partial=True), obj.partial) self.assertIsNone(obj.optional) self.assertIsNone(obj.optional_spec) - self.assertIsNone(obj.optional_speclist) # Copy values to another model alt_obj = models.PartialVersionModel( partial=obj.partial, optional=obj.optional, optional_spec=obj.optional_spec, - optional_speclist=obj.optional_speclist, ) self.assertEqual(semantic_version.Version('0.1.0', partial=True), alt_obj.partial) self.assertEqual(obj.partial, alt_obj.partial) self.assertIsNone(obj.optional) self.assertIsNone(obj.optional_spec) - self.assertIsNone(obj.optional_speclist) def test_serialization(self): - o1 = models.VersionModel(version='0.1.1', spec='<0.2.4-rc42', - speclist='==0.1.1,!=0.1.1-alpha') - o2 = models.VersionModel(version='0.4.3-rc3+build3', spec='==0.4.3', - speclist='<=0.1.1-rc2,!=0.1.1-rc1') + o1 = models.VersionModel(version='0.1.1', spec='==0.1.1,!=0.1.1-alpha') + o2 = models.VersionModel(version='0.4.3-rc3+build3', + spec='<=0.1.1-rc2,!=0.1.1-rc1') data = serializers.serialize('json', [o1, o2]) @@ -94,9 +87,9 @@ class DjangoFieldTestCase(unittest.TestCase): def test_serialization_partial(self): o1 = models.PartialVersionModel(partial='0.1.1', optional='0.2.4-rc42', - optional_spec=None, optional_speclist=None) + optional_spec=None) o2 = models.PartialVersionModel(partial='0.4.3-rc3+build3', optional='', - optional_spec='==1.1.0', optional_speclist='==0.1.1,!=0.1.1-alpha') + optional_spec='==0.1.1,!=0.1.1-alpha') data = serializers.serialize('json', [o1, o2]) |