Add Python3 support.
This commit is contained in:
@@ -2,6 +2,8 @@ language: python
|
||||
python:
|
||||
- "2.6"
|
||||
- "2.7"
|
||||
- "3.2"
|
||||
- "3.3"
|
||||
script: "python setup.py test"
|
||||
install: "if [[ $TRAVIS_PYTHON_VERSION = 2.6 ]]; then pip install unittest2 --use-mirrors; fi"
|
||||
notifications:
|
||||
|
||||
3
README
3
README
@@ -12,6 +12,9 @@ Handles the full 2.0.0-rc1 version of the SemVer scheme, and provides tools to d
|
||||
The full doc is available on http://python-semanticversion.readthedocs.org/; simple usage is described below.
|
||||
|
||||
|
||||
The semantic_version library supports Python 2.6, 2.7, 3.2, 3.3.
|
||||
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
|
||||
22
setup.py
22
setup.py
@@ -54,15 +54,20 @@ class test(cmd.Command):
|
||||
ex_path = sys.path
|
||||
sys.path.insert(0, os.path.join(root_dir, 'src'))
|
||||
loader = unittest.defaultTestLoader
|
||||
suite = unittest.TestSuite()
|
||||
|
||||
if self.test_suite != self.DEFAULT_TEST_SUITE:
|
||||
suite = loader.loadTestsFromName(self.test_suite)
|
||||
if self.test_suite == self.DEFAULT_TEST_SUITE:
|
||||
for test_module in loader.discover('.'):
|
||||
suite.addTest(test_module)
|
||||
else:
|
||||
suite = loader.discover(self.test_suite)
|
||||
suite.addTest(loader.loadTestsFromName(self.test_suite))
|
||||
|
||||
unittest.TextTestRunner(verbosity=verbosity).run(suite)
|
||||
result = unittest.TextTestRunner(verbosity=verbosity).run(suite)
|
||||
sys.path = ex_path
|
||||
|
||||
if not result.wasSuccessful():
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
setup(
|
||||
name="semantic_version",
|
||||
@@ -77,12 +82,19 @@ setup(
|
||||
package_dir={'': 'src'},
|
||||
packages=['semantic_version'],
|
||||
classifiers=[
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: BSD License",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.2',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules'
|
||||
],
|
||||
cmdclass={'test': test},
|
||||
)
|
||||
|
||||
@@ -2,10 +2,14 @@
|
||||
# Copyright (c) 2012-2013 Raphaël Barrois
|
||||
# This code is distributed under the two-clause BSD License.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import functools
|
||||
import re
|
||||
|
||||
|
||||
from .compat import base_cmp
|
||||
|
||||
def _to_int(value):
|
||||
try:
|
||||
return int(value), True
|
||||
@@ -21,7 +25,7 @@ def identifier_cmp(a, b):
|
||||
|
||||
if a_is_int and b_is_int:
|
||||
# Numeric identifiers are compared as integers
|
||||
return cmp(a_cmp, b_cmp)
|
||||
return base_cmp(a_cmp, b_cmp)
|
||||
elif a_is_int:
|
||||
# Numeric identifiers have lower precedence
|
||||
return -1
|
||||
@@ -29,7 +33,7 @@ def identifier_cmp(a, b):
|
||||
return 1
|
||||
else:
|
||||
# Non-numeric identifers are compared lexicographically
|
||||
return cmp(a_cmp, b_cmp)
|
||||
return base_cmp(a_cmp, b_cmp)
|
||||
|
||||
|
||||
def identifier_list_cmp(a, b):
|
||||
@@ -53,7 +57,7 @@ def identifier_list_cmp(a, b):
|
||||
if cmp_res != 0:
|
||||
return cmp_res
|
||||
# alpha1.3 < alpha1.3.1
|
||||
return cmp(len(a), len(b))
|
||||
return base_cmp(len(a), len(b))
|
||||
|
||||
|
||||
class Version(object):
|
||||
@@ -221,9 +225,6 @@ class Version(object):
|
||||
', partial=True' if self.partial else '',
|
||||
)
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.major, self.minor, self.patch, self.prerelease, self.build))
|
||||
|
||||
@classmethod
|
||||
def _comparison_functions(cls, partial=False):
|
||||
"""Retrieve comparison methods to apply on version components.
|
||||
@@ -281,17 +282,17 @@ class Version(object):
|
||||
|
||||
if partial:
|
||||
return [
|
||||
cmp, # Major is still mandatory
|
||||
make_optional(cmp),
|
||||
make_optional(cmp),
|
||||
base_cmp, # Major is still mandatory
|
||||
make_optional(base_cmp),
|
||||
make_optional(base_cmp),
|
||||
make_optional(prerelease_cmp),
|
||||
make_optional(build_cmp),
|
||||
]
|
||||
else:
|
||||
return [
|
||||
cmp,
|
||||
cmp,
|
||||
cmp,
|
||||
base_cmp,
|
||||
base_cmp,
|
||||
base_cmp,
|
||||
prerelease_cmp,
|
||||
build_cmp,
|
||||
]
|
||||
@@ -302,15 +303,54 @@ class Version(object):
|
||||
|
||||
field_pairs = zip(self, other)
|
||||
comparison_functions = self._comparison_functions(partial=self.partial or other.partial)
|
||||
comparisons = zip(comparison_functions, self, other)
|
||||
|
||||
for cmp_fun, field_pair in zip(comparison_functions, field_pairs):
|
||||
self_field, other_field = field_pair
|
||||
for cmp_fun, self_field, other_field in comparisons:
|
||||
cmp_res = cmp_fun(self_field, other_field)
|
||||
if cmp_res != 0:
|
||||
return cmp_res
|
||||
|
||||
return 0
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, self.__class__):
|
||||
return NotImplemented
|
||||
|
||||
return self.__cmp__(other) == 0
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.major, self.minor, self.patch, self.prerelease, self.build))
|
||||
|
||||
def __ne__(self, other):
|
||||
if not isinstance(other, self.__class__):
|
||||
return NotImplemented
|
||||
|
||||
return self.__cmp__(other) != 0
|
||||
|
||||
def __lt__(self, other):
|
||||
if not isinstance(other, self.__class__):
|
||||
return NotImplemented
|
||||
|
||||
return self.__cmp__(other) < 0
|
||||
|
||||
def __le__(self, other):
|
||||
if not isinstance(other, self.__class__):
|
||||
return NotImplemented
|
||||
|
||||
return self.__cmp__(other) <= 0
|
||||
|
||||
def __gt__(self, other):
|
||||
if not isinstance(other, self.__class__):
|
||||
return NotImplemented
|
||||
|
||||
return self.__cmp__(other) > 0
|
||||
|
||||
def __ge__(self, other):
|
||||
if not isinstance(other, self.__class__):
|
||||
return NotImplemented
|
||||
|
||||
return self.__cmp__(other) >= 0
|
||||
|
||||
|
||||
class SpecItem(object):
|
||||
"""A requirement specification."""
|
||||
@@ -434,7 +474,7 @@ class Spec(object):
|
||||
|
||||
|
||||
def compare(v1, v2):
|
||||
return cmp(Version(v1), Version(v2))
|
||||
return base_cmp(Version(v1), Version(v2))
|
||||
|
||||
|
||||
def match(spec, version):
|
||||
|
||||
18
src/semantic_version/compat.py
Normal file
18
src/semantic_version/compat.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2012-2013 Raphaël Barrois
|
||||
# This code is distributed under the two-clause BSD License.
|
||||
|
||||
import sys
|
||||
|
||||
is_python2 = (sys.version_info[0] == 2)
|
||||
|
||||
if is_python2: # pragma: no cover
|
||||
base_cmp = cmp
|
||||
else: # pragma: no cover
|
||||
def base_cmp(x, y):
|
||||
if x < y:
|
||||
return -1
|
||||
elif x > y:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
@@ -2,6 +2,8 @@
|
||||
# Copyright (c) 2012-2013 Raphaël Barrois
|
||||
# This code is distributed under the two-clause BSD License.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
@@ -31,11 +33,16 @@ class BaseSemVerField(models.CharField):
|
||||
return super(BaseSemVerField, self).run_validators(str(value))
|
||||
|
||||
|
||||
class VersionField(BaseSemVerField):
|
||||
# Py2 and Py3-compatible metaclass
|
||||
SemVerField = models.SubfieldBase(
|
||||
str('SemVerField'), (BaseSemVerField, models.CharField), {})
|
||||
|
||||
|
||||
class VersionField(SemVerField):
|
||||
default_error_messages = {
|
||||
'invalid': _(u"Enter a valid version number in X.Y.Z format."),
|
||||
'invalid': _("Enter a valid version number in X.Y.Z format."),
|
||||
}
|
||||
description = _(u"Version")
|
||||
description = _("Version")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.partial = kwargs.pop('partial', False)
|
||||
@@ -54,11 +61,11 @@ class VersionField(BaseSemVerField):
|
||||
return base.Version(value, partial=self.partial)
|
||||
|
||||
|
||||
class SpecField(BaseSemVerField):
|
||||
class SpecField(SemVerField):
|
||||
default_error_messages = {
|
||||
'invalid': _(u"Enter a valid version number spec list in ==X.Y.Z,>=A.B.C format."),
|
||||
'invalid': _("Enter a valid version number spec list in ==X.Y.Z,>=A.B.C format."),
|
||||
}
|
||||
description = _(u"Version specification list")
|
||||
description = _("Version specification list")
|
||||
|
||||
def to_python(self, value):
|
||||
"""Converts any value to a base.Spec field."""
|
||||
|
||||
14
tests/compat.py
Normal file
14
tests/compat.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2012-2013 Raphaël Barrois
|
||||
# This code is distributed under the two-clause BSD License.
|
||||
|
||||
import sys
|
||||
|
||||
is_python2 = (sys.version_info[0] == 2)
|
||||
|
||||
|
||||
try: # pragma: no cover
|
||||
import unittest2 as unittest
|
||||
except ImportError: # pragma: no cover
|
||||
import unittest
|
||||
|
||||
@@ -5,11 +5,7 @@
|
||||
|
||||
"""Test the various functions from 'base'."""
|
||||
|
||||
try: # pragma: no cover
|
||||
import unittest2 as unittest
|
||||
except ImportError: # pragma: no cover
|
||||
import unittest
|
||||
|
||||
from .compat import unittest, is_python2
|
||||
|
||||
from semantic_version import base
|
||||
|
||||
@@ -171,6 +167,8 @@ class VersionTestCase(unittest.TestCase):
|
||||
self.assertNotEqual(text, base.Version(text))
|
||||
|
||||
partial_versions = {
|
||||
'1.1': (1, 1, None, None, None),
|
||||
'2': (2, None, None, None, None),
|
||||
'1.0.0-alpha': (1, 0, 0, ('alpha',), None),
|
||||
'1.0.0-alpha.1': (1, 0, 0, ('alpha', '1'), None),
|
||||
'1.0.0-beta.2': (1, 0, 0, ('beta', '2'), None),
|
||||
@@ -229,6 +227,21 @@ class VersionTestCase(unittest.TestCase):
|
||||
]))
|
||||
)
|
||||
|
||||
@unittest.skipIf(is_python2, "Comparisons to other objects are broken in Py2.")
|
||||
def test_invalid_comparisons(self):
|
||||
v = base.Version('0.1.0')
|
||||
with self.assertRaises(TypeError):
|
||||
v < '0.1.0'
|
||||
with self.assertRaises(TypeError):
|
||||
v <= '0.1.0'
|
||||
with self.assertRaises(TypeError):
|
||||
v > '0.1.0'
|
||||
with self.assertRaises(TypeError):
|
||||
v >= '0.1.0'
|
||||
|
||||
self.assertTrue(v != '0.1.0')
|
||||
self.assertFalse(v == '0.1.0')
|
||||
|
||||
|
||||
class SpecItemTestCase(unittest.TestCase):
|
||||
components = {
|
||||
@@ -345,6 +358,7 @@ class SpecItemTestCase(unittest.TestCase):
|
||||
class CoerceTestCase(unittest.TestCase):
|
||||
examples = {
|
||||
# Dict of target: [list of equivalents]
|
||||
'0.0.0': ('0', '0.0', '0.0.0', '0.0.0+', '0-', '00000000.00'),
|
||||
'0.1.0': ('0.1', '0.1+', '0.1-', '0.1.0', '0.000001.000000000000'),
|
||||
'0.1.0+2': ('0.1.0+2', '0.1.0.2'),
|
||||
'0.1.0+2.3.4': ('0.1.0+2.3.4', '0.1.0+2+3+4', '0.1.0.2+3+4'),
|
||||
@@ -360,6 +374,9 @@ class CoerceTestCase(unittest.TestCase):
|
||||
v_sample = base.Version.coerce(sample)
|
||||
self.assertEqual(target, v_sample)
|
||||
|
||||
def test_invalid(self):
|
||||
self.assertRaises(ValueError, base.Version.coerce, 'v1')
|
||||
|
||||
|
||||
class SpecTestCase(unittest.TestCase):
|
||||
examples = {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
# Copyright (c) 2012-2013 Raphaël Barrois
|
||||
# This code is distributed under the two-clause BSD License.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
try: # pragma: no cover
|
||||
import unittest2 as unittest
|
||||
except ImportError: # pragma: no cover
|
||||
@@ -28,7 +30,7 @@ if django_loaded: # pragma: no cover
|
||||
'tests.django_test_app',
|
||||
]
|
||||
)
|
||||
from django_test_app import models
|
||||
from .django_test_app import models
|
||||
from django.core import serializers
|
||||
|
||||
try: # pragma: no cover
|
||||
|
||||
@@ -63,7 +63,9 @@ class ComparisonTestCase(unittest.TestCase):
|
||||
self.assertTrue(first_ver == second_ver, '%r != %r' % (first_ver, second_ver))
|
||||
else:
|
||||
self.assertTrue(first_ver > second_ver, '%r !> %r' % (first_ver, second_ver))
|
||||
self.assertEqual(cmp(i, j), semantic_version.compare(first, second))
|
||||
|
||||
cmp_res = -1 if i < j else (1 if i > j else 0)
|
||||
self.assertEqual(cmp_res, semantic_version.compare(first, second))
|
||||
|
||||
|
||||
if __name__ == '__main__': # pragma: no cover
|
||||
|
||||
Reference in New Issue
Block a user