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

Reply via email to