Package: dh-python Version: 3.20180927 Severity: wishlist Tags: patch Dear maintainer,
While packaging python-azure, I noticed a bunch of "Cannot find package that provides <module-name>_ [...]" messages. It turns out that the azure stuff makes extensive use of the PEP440 "Compatible release" ``module ~= version`` syntax in its install_requires. Attached is a patch adding support for these version restrictions for packages that are marked as compliant with PEP396. Even for modules that aren't marked as such, having ~= recognized as a valid version specifier operator, avoids pydist looking for bogus `<module-name>_` modules: the tilde stops being mapped to an underscore. Thanks for considering! Nicolas -- System Information: Debian Release: buster/sid APT prefers unstable-debug APT policy: (500, 'unstable-debug'), (500, 'stable-updates'), (500, 'stable-debug'), (500, 'buildd-unstable'), (500, 'unstable'), (500, 'testing'), (500, 'stable'), (1, 'experimental') Architecture: amd64 (x86_64) Foreign Architectures: i386 Kernel: Linux 4.19.0-rc7-amd64 (SMP w/4 CPU cores) Locale: LANG=fr_FR.UTF-8, LC_CTYPE=fr_FR.UTF-8 (charmap=UTF-8), LANGUAGE=fr_FR.UTF-8 (charmap=UTF-8) Shell: /bin/sh linked to /usr/bin/dash Init: systemd (via /run/systemd/system) LSM: AppArmor: enabled Versions of packages dh-python depends on: ii python3 3.6.7-1 ii python3-distutils 3.7.1-1 dh-python recommends no packages. Versions of packages dh-python suggests: ii dpkg-dev 1.19.2 ii libdpkg-perl 1.19.2 -- no debconf information
>From efa82c3385904a03d7fb5d1949d653e3de77ecfc Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont <ol...@debian.org> Date: Fri, 9 Nov 2018 17:29:18 +0100 Subject: [PATCH] Support "Compatible release" version specifiers in the requires parser This adds support for ~= version specifiers as proposed in https://www.python.org/dev/peps/pep-0440/#compatible-release --- dhpython/pydist.py | 38 ++++++++++++++++++++++++++++++++++++-- tests/test_depends.py | 22 ++++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/dhpython/pydist.py b/dhpython/pydist.py index 7372a50..42b1ec1 100644 --- a/dhpython/pydist.py +++ b/dhpython/pydist.py @@ -52,7 +52,7 @@ REQUIRES_RE = re.compile(r''' \s* \(? # optional parenthesis (?: # optional minimum/maximum version - (?P<operator><=?|>=?|==|!=) + (?P<operator><=?|>=?|==|!=|~=) \s* (?P<version>(\w|[-.])+) (?: # optional interval minimum/maximum version @@ -70,6 +70,7 @@ DEB_VERS_OPS = { '==': '=', '<': '<<', '>': '>>', + '~=': '>=', } @@ -139,7 +140,7 @@ def guess_dependency(impl, req, version=None, bdep=None, version = Version(version) # some upstreams have weird ideas for distribution name... - name, rest = re.compile('([^!><= \(\)\[]+)(.*)').match(req).groups() + name, rest = re.compile('([^!><=~ \(\)\[]+)(.*)').match(req).groups() # TODO: check stdlib and dist-packaged for name.py and name.so files req = safe_name(name) + rest @@ -173,6 +174,10 @@ def guess_dependency(impl, req, version=None, bdep=None, o2 = _translate_op(req_d['operator2']) v2 = _translate(req_d['version2'], item['rules'], item['standard']) d += ", %s (%s %s)" % (item['dependency'], o2, v2) + elif req_d['operator'] == '~=': + o2 = '<<' + v2 = _translate(_max_compatible(req_d['version']), item['rules'], item['standard']) + d += ", %s (%s %s)" % (item['dependency'], o2, v2) return d elif accept_upstream_versions and req_d['version'] and \ req_d['operator'] not in (None,'!='): @@ -181,6 +186,9 @@ def guess_dependency(impl, req, version=None, bdep=None, if req_d['version2'] and req_d['operator2'] not in (None,'!='): o2 = _translate_op(req_d['operator2']) d += ", %s (%s %s)" % (item['dependency'], o2, req_d['version2']) + elif req_d['operator'] == '~=': + o2 = '<<' + d += ", %s (%s %s)" % (item['dependency'], o2, _max_compatible(req_d['version'])) return d else: if item['dependency'] in bdep: @@ -305,6 +313,32 @@ def _pl2py(pattern): return GROUP_RE.sub(r'\\g<\1>', pattern) +def _max_compatible(version): + """Return the maximum version compatible with `version` in PEP440 terms, + used by ~= requires version specifiers. + + https://www.python.org/dev/peps/pep-0440/#compatible-release + + >>> _max_compatible('2.2') + '3' + >>> _max_compatible('1.4.5') + '1.5' + >>> _max_compatible('1.3.alpha4') + '2' + >>> _max_compatible('2.1.3.post5') + '2.2' + + """ + v = Version(version) + v.serial = None + v.releaselevel = None + if v.micro is not None: + v.micro = None + return str(v + 1) + v.minor = None + return str(v + 1) + + def _translate(version, rules, standard): """Translate Python version into Debian one. diff --git a/tests/test_depends.py b/tests/test_depends.py index cffb7bd..604d42d 100644 --- a/tests/test_depends.py +++ b/tests/test_depends.py @@ -130,3 +130,25 @@ class TestRequiresPyPy(DependenciesTestCase): def test_depends_on_baz(self): self.assertIn('pypy-baz (>= 1.0)', self.d.depends) + + +class TestRequiresCompatible(DependenciesTestCase): + options = FakeOptions(guess_deps=True) + pydist = { + 'bar': 'python3-bar', + 'baz': {'dependency': 'python3-baz', 'standard': 'PEP386'}, + 'quux': {'dependency': 'python3-quux', 'standard': 'PEP386'}, + } + requires = { + 'debian/foo/usr/lib/python3/dist-packages/foo.egg-info/requires.txt': ( + 'bar', + 'baz ~= 1.0', + 'quux', + ), + } + + def test_depends_on_bar(self): + self.assertIn('python3-bar', self.d.depends) + + def test_depends_on_baz(self): + self.assertIn('python3-baz (>= 1.0), python3-baz (<< 2)', self.d.depends) -- 2.19.1