diff --git a/pymaven/__init__.py b/pymaven/__init__.py index 4e64be6..bd33c57 100644 --- a/pymaven/__init__.py +++ b/pymaven/__init__.py @@ -19,5 +19,4 @@ from .versioning import Version from .versioning import VersionRange - __all__ = ["Artifact", "Version", "VersionRange"] diff --git a/pymaven/artifact.py b/pymaven/artifact.py index 914f617..4c10834 100644 --- a/pymaven/artifact.py +++ b/pymaven/artifact.py @@ -20,11 +20,17 @@ in a maven repository """ +import functools import re +import sys + +import six from .errors import ArtifactParseError from .versioning import VersionRange +if sys.version_info > (2,): + from .utils import cmp MAVEN_COORDINATE_RE = re.compile( r'(?P[^:]+)' @@ -34,6 +40,7 @@ ) +@functools.total_ordering class Artifact(object): """Represents an artifact within a maven repository.""" @@ -69,22 +76,18 @@ def __init__(self, coordinate): def __cmp__(self, other): if self is other: return 0 - if not isinstance(other, Artifact): - if isinstance(other, basestring): + if isinstance(other, six.string_types): try: return cmp(self, Artifact(other)) except ArtifactParseError: pass return 1 - result = cmp(self.group_id, other.group_id) if result == 0: result = cmp(self.artifact_id, other.artifact_id) - if result == 0: result = cmp(self.type, other.type) - if result == 0: if self.classifier is None: if other.classifier is not None: @@ -94,13 +97,20 @@ def __cmp__(self, other): result = -1 else: result = cmp(self.classifier, other.classifier) - if result == 0: result = cmp(self.version.version, other.version.version) - return result + def __eq__(self, other): + return self.__cmp__(other) == 0 + + def __lt__(self, other): + return self.__cmp__(other) < 0 + + def __ne__(self, other): + return self.__cmp__(other) != 0 + def __hash__(self): return hash((self.group_id, self.artifact_id, self.version, self.type, self.classifier)) diff --git a/pymaven/client.py b/pymaven/client.py index ab45454..49e2b26 100644 --- a/pymaven/client.py +++ b/pymaven/client.py @@ -15,12 +15,6 @@ # -try: - from xml.etree import cElementTree as ElementTree -except ImportError: - from xml.etree import ElementTree - -from urlparse import urlparse import getpass import hashlib import json @@ -28,7 +22,9 @@ import os import posixpath +from six.moves.urllib.parse import urlparse import requests +import six from . import utils from .artifact import Artifact @@ -37,6 +33,10 @@ from .pom import Pom from .versioning import VersionRange +try: + from xml.etree import cElementTree as ElementTree +except ImportError: + from xml.etree import ElementTree log = logging.getLogger(__name__) @@ -75,7 +75,7 @@ def __init__(self, cacheDir=None): if cacheDir is None: cacheDir = "/tmp/%s-maven-cache" % getpass.getuser() if not os.path.exists(cacheDir): - os.makedirs(cacheDir, mode=0700) + os.makedirs(cacheDir, mode=0o700) self.cacheDir = cacheDir def _gen_key(self, method, uri, query_params): @@ -155,7 +155,7 @@ class MavenClient(object): """ Client for talking to a maven repository """ def __init__(self, *urls): - if isinstance(urls, basestring): + if isinstance(urls, six.string_types): urls = [urls] self._repos = [] for url in urls: diff --git a/pymaven/pom.py b/pymaven/pom.py index b66a5fd..ee81da5 100644 --- a/pymaven/pom.py +++ b/pymaven/pom.py @@ -20,12 +20,12 @@ import re from lxml import etree +import six from .artifact import Artifact from .utils import memoize from .versioning import VersionRange - POM_PARSER = etree.XMLParser( recover=True, remove_comments=True, @@ -286,15 +286,15 @@ def dependencies(self): ((group, artifact, str(version)), True)) for key, value in itertools.chain( - self._find_import_deps().iteritems(), - self._find_deps().iteritems(), - self._find_relocations().iteritems()): + six.iteritems(self._find_import_deps()), + six.iteritems(self._find_deps()), + six.iteritems(self._find_relocations())): dependencies.setdefault(key, set()).update(value) for profile in self._find_profiles(): for key, value in itertools.chain( - self._find_deps(profile).iteritems(), - self._find_relocations(profile).iteritems()): + six.iteritems(self._find_deps(profile)), + six.iteritems(self._find_relocations(profile))): dependencies.setdefault(key, set()).update(value) return dependencies diff --git a/pymaven/utils.py b/pymaven/utils.py index 8086029..9c94075 100644 --- a/pymaven/utils.py +++ b/pymaven/utils.py @@ -16,9 +16,28 @@ from functools import wraps -from urlparse import urlsplit, urlunsplit import posixpath +from six.moves.urllib.parse import urlsplit +from six.moves.urllib.parse import urlunsplit + + +def cmp(x, y): + """ + Replacement for built-in funciton cmp that was removed in Python 3 + + Compare the two objects x and y and return an integer according to + the outcome. The return value is negative if x < y, zero if x == y + and strictly positive if x > y. + """ + if x is None and y is None: + return 0 + elif x is None: + return -1 + elif y is None: + return 1 + return (x > y) - (x < y) + def memoize(name): def wrap(func): diff --git a/pymaven/versioning.py b/pymaven/versioning.py index 63570ca..e89de6c 100644 --- a/pymaven/versioning.py +++ b/pymaven/versioning.py @@ -19,11 +19,17 @@ Versioning of artifacts """ -import itertools +import functools +import sys + +from six.moves import zip_longest +import six from .errors import RestrictionParseError from .errors import VersionRangeParseError +if sys.version_info > (2,): + from .utils import cmp EXCLUSIVE_CLOSE = ')' EXCLUSIVE_OPEN = '(' @@ -45,6 +51,7 @@ def list2tuple(l): return tuple(list2tuple(x) if isinstance(x, list) else x for x in l) +@functools.total_ordering class Restriction(object): """Describes a restriction in versioning """ @@ -127,9 +134,9 @@ def __cmp__(self, other): if self is other: return 0 - if not isinstance(other, Restriction): - if isinstance(other, basestring): - return cmp(self, Restriction(other)) + if not isinstance(other, self.__class__): + if isinstance(other, six.string_types): + return cmp(self, self.__class__(other)) return 1 result = cmp(self.lower_bound, other.lower_bound) @@ -143,10 +150,19 @@ def __cmp__(self, other): other.upper_bound_inclusive) return result + def __eq__(self, other): + return self.__cmp__(other) == 0 + def __hash__(self): return hash((self.lower_bound, self.lower_bound_inclusive, self.upper_bound, self.upper_bound_inclusive)) + def __lt__(self, other): + return self.__cmp__(other) < 0 + + def __ne__(self, other): + return self.__cmp__(other) != 0 + def __str__(self): s = "{open}{lower}{comma}{upper}{close}".format( open=(INCLUSIVE_OPEN if self.lower_bound_inclusive @@ -170,11 +186,12 @@ def __repr__(self): self.upper_bound_inclusive, ) - @staticmethod - def fromstring(self, spec): - return Restriction(spec) + @classmethod + def fromstring(cls, spec): + return cls(spec) +@functools.total_ordering class VersionRange(object): """Version range specification @@ -242,9 +259,9 @@ def __cmp__(self, other): if self is other: return 0 - if not isinstance(other, VersionRange): - if isinstance(other, basestring): - return cmp(self, VersionRange(other)) + if not isinstance(other, self.__class__): + if isinstance(other, six.string_types): + return cmp(self, self.__class__(other)) elif isinstance(other, Version): return cmp(other, self) return 1 @@ -258,9 +275,18 @@ def __cmp__(self, other): def __contains__(self, version): return any((version in r) for r in self.restrictions) + def __eq__(self, other): + return self.__cmp__(other) == 0 + def __hash__(self): return hash((self.version, self.restrictions)) + def __lt__(self, other): + return self.__cmp__(other) < 0 + + def __ne__(self, other): + return self.__cmp__(other) != 0 + def __str__(self): if self.version: return str(self.version) @@ -283,13 +309,13 @@ def _intersection(self, l1, l2): """ raise NotImplementedError - @staticmethod - def fromstring(spec): - return VersionRange(spec) + @classmethod + def fromstring(cls, spec): + return cls(spec) - @staticmethod - def from_version(version): - return VersionRange(str(version)) + @classmethod + def from_version(cls, version): + return cls(str(version)) def restrict(self, version_range): """Returns a new VersionRange that is a restriction of this @@ -313,6 +339,7 @@ def match_version(self, versions): return matched +@functools.total_ordering class Version(object): """Maven version objecjt """ @@ -386,18 +413,27 @@ def __cmp__(self, other): if self is other: return 0 - if not isinstance(other, Version): - if isinstance(other, basestring): - return self._compare(self._parsed, Version(other)._parsed) + if not isinstance(other, self.__class__): + if isinstance(other, six.string_types): + return self._compare(self._parsed, self.__class__(other)._parsed) elif isinstance(other, VersionRange) and other.version: return self._compare(self._parsed, other.version._parsed) return 1 return self._compare(self._parsed, other._parsed) + def __eq__(self, other): + return self.__cmp__(other) == 0 + def __hash__(self): return hash(self._unparsed) + def __lt__(self, other): + return self.__cmp__(other) < 0 + + def __ne__(self, other): + return self.__cmp__(other) != 0 + def __repr__(self): return "<%s.%s(%r)>" % (self.__module__, "Version", self._unparsed) @@ -407,7 +443,7 @@ def __str__(self): def _compare(self, this, other): if isinstance(this, int): return self._int_compare(this, other) - elif isinstance(this, basestring): + elif isinstance(this, six.string_types): return self._string_compare(this, other) elif isinstance(this, (list, tuple)): return self._list_compare(this, other) @@ -417,7 +453,7 @@ def _compare(self, this, other): def _int_compare(self, this, other): if isinstance(other, int): return this - other - elif isinstance(other, (basestring, list, tuple)): + elif isinstance(other, (six.string_types, list, tuple)): return 1 elif other is None: return this @@ -431,10 +467,10 @@ def _list_compare(self, l, other): return self._compare(l[0], other) if isinstance(other, int): return -1 - elif isinstance(other, basestring): + elif isinstance(other, six.string_types): return 1 elif isinstance(other, (list, tuple)): - for left, right in itertools.izip_longest(l, other): + for left, right in zip_longest(l, other): if left is None: if right is None: result = 0 @@ -482,7 +518,7 @@ def _string_compare(self, s, other): if isinstance(other, (int, list, tuple)): return -1 - elif isinstance(other, basestring): + elif isinstance(other, six.string_types): s_value = self._string_value(s) other_value = self._string_value(other) if s_value < other_value: @@ -530,6 +566,6 @@ def _string_value(self, s): return "%d-%s" % (len(QUALIFIERS), s) - @staticmethod - def fromstring(spec): - return Version(spec) + @classmethod + def fromstring(cls, spec): + return cls(spec) diff --git a/requirements.txt b/requirements.txt index 8cbed91..49d9dc2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ lxml>=3.4.4,<4.0.0 requests>=2.7.0,<3.0.0 +six>=1.10,<2.0 diff --git a/setup.cfg b/setup.cfg index 186c94c..3cea5ef 100644 --- a/setup.cfg +++ b/setup.cfg @@ -38,23 +38,18 @@ precision = 2 [tool:pytest] norecursedirs = + .eggs + .env .git .tox - .env - dist build - south_migrations + dist migrations + south_migrations python_files = test_*.py *_test.py tests.py -addopts = - -rxEfsw - --strict - --doctest-modules - --doctest-glob=\*.rst - --tb=short [isort] force_single_line = true diff --git a/setup.py b/setup.py index affee71..bd31750 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,6 @@ from setuptools import setup - setup( setup_requires=["pbr>=1.9", "setuptools>=17.1"], pbr=True, diff --git a/tests/test_client.py b/tests/test_client.py index 2c7b1c4..3fc5c9c 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -15,12 +15,11 @@ # -from StringIO import StringIO import os import tempfile import unittest -import mock +from six import StringIO import requests from pymaven import Artifact @@ -28,8 +27,13 @@ from pymaven.client import LocalRepository from pymaven.client import MavenClient from pymaven.client import Struct -from pymaven.errors import MissingPathError from pymaven.errors import MissingArtifactError +from pymaven.errors import MissingPathError + +try: + from unittest import mock +except ImportError: + import mock class TestMavenClient(unittest.TestCase): @@ -67,28 +71,44 @@ def test_find_artifacts(self, _LocalRepository): @mock.patch("pymaven.client.LocalRepository") def test_get_artifact(self, _LocalRepository): - _repo = mock.Mock(spec=LocalRepository) - _repo.exists.side_effect = [True, False] + _repo.exists.side_effect = [True] _repo.open.return_value = StringIO("some data") _LocalRepository.return_value = _repo client = MavenClient("/maven") - actual = client.get_artifact("foo:bar:2.0.0") assert "some data" == actual.contents.read() _repo.exists.assert_called_with("foo/bar/2.0.0/bar-2.0.0.jar") _repo.open.assert_called_with("foo/bar/2.0.0/bar-2.0.0.jar") - _repo.reset_mock() + @mock.patch("pymaven.client.LocalRepository") + def test_get_artifact_missing(self, _LocalRepository): + _repo = mock.Mock(spec=LocalRepository) + + _repo.exists.side_effect = [False] + _repo.open.return_value = StringIO("some data") + + _LocalRepository.return_value = _repo + + client = MavenClient("/maven") self.assertRaises(MissingArtifactError, client.get_artifact, "foo:bar:3.0") _repo.exists.assert_called_with("foo/bar/3.0/bar-3.0.jar") _repo.open.assert_not_called() - _repo.reset_mock() + @mock.patch("pymaven.client.LocalRepository") + def test_get_artifact_version_range(self, _LocalRepository): + _repo = mock.Mock(spec=LocalRepository) + + _repo.exists.side_effect = [True, False] + _repo.open.return_value = StringIO("some data") + + _LocalRepository.return_value = _repo + + client = MavenClient("/maven") self.assertRaises(AssertionError, client.get_artifact, "foo:bar:[1.0,2.0]") _repo.open.assert_not_called() @@ -183,7 +203,7 @@ def test_get_versions(self, _os): def test_open(self): with tempfile.NamedTemporaryFile() as tmp: repo = LocalRepository(os.path.dirname(tmp.name)) - tmp.write("the file\n") + tmp.write(b"the file\n") tmp.flush() with repo.open(tmp.name) as fh: assert "the file\n" == fh.read() diff --git a/tests/test_pom.py b/tests/test_pom.py index d094074..f9e7a8a 100644 --- a/tests/test_pom.py +++ b/tests/test_pom.py @@ -15,17 +15,21 @@ # -from StringIO import StringIO import unittest -import mock +from six import StringIO -from pymaven import Artifact from pymaven import VersionRange as VR +from pymaven import Artifact from pymaven.client import MavenClient from pymaven.client import Struct from pymaven.pom import Pom +try: + from unittest import mock +except ImportError: + import mock + class TestPom(unittest.TestCase): diff --git a/tox.ini b/tox.ini index 4f8504c..3e043cb 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = clean, check, py{27,34,35,36}, report, docs +envlist = clean, check, py{27,34,35,36}, report [testenv] setenv = @@ -8,6 +8,7 @@ setenv = passenv = * deps = + -rrequirements.txt -rtest-requirements.txt commands = {posargs:py.test --cov --cov-report=term-missing -vv tests/} @@ -38,14 +39,6 @@ commands = coverage xml --ignore-errors codecov [] -[testenv:docs] -deps = - -r{toxinidir}/docs/requirements.txt -commands = - sphinx-build {posargs:-E} -b doctest docs dist/docs - sphinx-build {posargs:-E} -b html docs dist/docs - sphinx-build -b linkcheck docs dist/docs - [testenv:report] deps = coverage skip_install = true