--- Begin Message ---
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 <[email protected]>
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
--- End Message ---