Source: python-debian
Version: 1.0.1
Severity: wishlist
Tags: patch

Hello.

The next concern after deb822.PkgRelation.parse_relations() is
probably to check that the relation holds in a given context.  The
last commit enables callers to do that without depending on the
internal representation of arch and profiles restrictions.

The 6 first commits are suggestions inspired by 'debian/rules qa' and
lintian.
>From 4e7ba5499d0944df734f4f52dea4adeb8025be96 Mon Sep 17 00:00:00 2001
From: Nicolas Boulenguez <[email protected]>
Date: Thu, 6 Nov 2025 21:30:03 +0100
Subject: [PATCH 1/7] style: Remove trailing whitespaces in test_deb822.py

---
 tests/test_deb822.py | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/tests/test_deb822.py b/tests/test_deb822.py
index 6a6e584..97623dd 100755
--- a/tests/test_deb822.py
+++ b/tests/test_deb822.py
@@ -143,7 +143,7 @@ Description: text-based mailreader supporting MIME, GPG, PGP and threading
 Tag: interface::text-mode, made-of::lang:c, mail::imap, mail::pop, mail::user-agent, protocol::imap, protocol::ipv6, protocol::pop, protocol::ssl, role::sw:client, uitoolkit::ncurses, use::editing, works-with::mail
 Task: mail-server
 '''
-        
+
 
 PARSED_PACKAGE = deb822.Deb822Dict([
     ('Package', 'mutt'),
@@ -841,7 +841,7 @@ with open("test_deb822.pickle", "wb") as fh:
                               cls: Type[deb822.Deb822],
                               **kwargs: Any) -> None:
         """Ensure iter_paragraphs consistency"""
-        
+
         with open(filename, 'rb') as fh:
             packages_content = fh.read()
 
@@ -968,7 +968,7 @@ with open("test_deb822.pickle", "wb") as fh:
 
         for k, v in deb822_.items():
             assert dict_[k] == v
-    
+
     def test_case_insensitive(self) -> None:
         # PARSED_PACKAGE is a deb822.Deb822Dict object, so we can test
         # it directly
@@ -998,7 +998,7 @@ with open("test_deb822.pickle", "wb") as fh:
         with a newline (e.g. the control file Description field), then there
         should be a space after the colon, as with non-multiline fields.
         """
-        
+
         # bad_re: match a line that starts with a "Field:", and ends in
         # whitespace
         bad_re = re.compile(r"^\S+:\s+$")
@@ -1010,7 +1010,7 @@ with open("test_deb822.pickle", "wb") as fh:
                                 "after the colon in a multiline field " \
                                 "starting with a newline"
 
-        
+
         control_paragraph = """Package: python-debian
 Architecture: all
 Depends: ${python:Depends}
@@ -1047,12 +1047,12 @@ Description: python modules to work with Debian-related data formats
         d['Foo'] = 'bar'
         d['Baz'] = ''
         d['Another-Key'] = 'another value'
-        
+
         # Previous versions would raise an exception here -- this makes the
         # test fail and gives useful information, so I won't try to wrap around
         # it.
         dumped = d.dump()
-        
+
         # May as well make sure the resulting string is what we want
         expected = "Foo: bar\nBaz:\nAnother-Key: another value\n"
         assert dumped == expected
-- 
2.47.3

>From 0d81af5066bac24d3fca2e67c428e7e998d9a06f Mon Sep 17 00:00:00 2001
From: Nicolas Boulenguez <[email protected]>
Date: Thu, 6 Nov 2025 22:14:09 +0100
Subject: [PATCH 2/7] style: clean src/python_debian.egg-info/

---
 debian/clean | 1 +
 1 file changed, 1 insertion(+)

diff --git a/debian/clean b/debian/clean
index 3651b9c..714eafc 100644
--- a/debian/clean
+++ b/debian/clean
@@ -7,3 +7,4 @@ build/sphinx/
 .mypy_cache/
 src/debian/.mypy_cache/
 .coverage
+src/python_debian.egg-info/
-- 
2.47.3

>From 1de870f52150693cbd51eab0ace24bb81ddf2de3 Mon Sep 17 00:00:00 2001
From: Nicolas Boulenguez <[email protected]>
Date: Thu, 6 Nov 2025 22:27:41 +0100
Subject: [PATCH 3/7] style: replace physical address to the FSF with an URL

---
 debian/copyright | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/debian/copyright b/debian/copyright
index d1187b0..032123a 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -117,8 +117,7 @@ License: GPL-2+
  GNU General Public License for more details.
  .
  You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
  .
  On Debian systems, the complete text of the GNU General Public License
  version 2 can be found in `/usr/share/common-licenses/GPL-2'.
-- 
2.47.3

>From 495b78728d344bb13897c81a138101887ef4dec4 Mon Sep 17 00:00:00 2001
From: Nicolas Boulenguez <[email protected]>
Date: Thu, 6 Nov 2025 22:31:54 +0100
Subject: [PATCH 4/7] style: drop obsolete Rules-Requires-Root: no from
 debian/control

---
 debian/control | 1 -
 1 file changed, 1 deletion(-)

diff --git a/debian/control b/debian/control
index 32be789..9d101b2 100644
--- a/debian/control
+++ b/debian/control
@@ -29,7 +29,6 @@ Standards-Version: 4.7.0
 Vcs-Browser: https://salsa.debian.org/python-debian-team/python-debian
 Vcs-Git: https://salsa.debian.org/python-debian-team/python-debian.git
 Homepage: https://salsa.debian.org/python-debian-team/python-debian
-Rules-Requires-Root: no
 
 Package: python3-debian
 Architecture: all
-- 
2.47.3

>From ead04b68884de73f7ae5fcf77ed87dee5612b9ee Mon Sep 17 00:00:00 2001
From: Nicolas Boulenguez <[email protected]>
Date: Thu, 6 Nov 2025 22:33:25 +0100
Subject: [PATCH 5/7] style: Update Standards-Version to 4.7.2

---
 debian/control | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/debian/control b/debian/control
index 9d101b2..3986786 100644
--- a/debian/control
+++ b/debian/control
@@ -25,7 +25,7 @@ Build-Depends:
  zstd <!nocheck>,
 Build-Conflicts:
   gpgv-from-sq,
-Standards-Version: 4.7.0
+Standards-Version: 4.7.2
 Vcs-Browser: https://salsa.debian.org/python-debian-team/python-debian
 Vcs-Git: https://salsa.debian.org/python-debian-team/python-debian.git
 Homepage: https://salsa.debian.org/python-debian-team/python-debian
-- 
2.47.3

>From 058da4f4835ad594bf54842926e0f7a8ca36e488 Mon Sep 17 00:00:00 2001
From: Nicolas Boulenguez <[email protected]>
Date: Thu, 6 Nov 2025 21:32:47 +0100
Subject: [PATCH 6/7] style: replace collections.namedtuple with
 typing.NamedTuple in deb822.py

---
 src/debian/deb822.py | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/debian/deb822.py b/src/debian/deb822.py
index 1592ec9..4929583 100644
--- a/src/debian/deb822.py
+++ b/src/debian/deb822.py
@@ -257,6 +257,7 @@ from typing import (
     List,
     Mapping,
     MutableMapping,
+    NamedTuple,
     Optional,
     overload,
     Text,
@@ -1393,10 +1394,13 @@ class PkgRelation:
         r'(?P<enabled>\!)?'
         r'(?P<profile>[^\s]+)')
 
-    ArchRestriction = collections.namedtuple('ArchRestriction',
-                                             ['enabled', 'arch'])
-    BuildRestriction = collections.namedtuple('BuildRestriction',
-                                              ['enabled', 'profile'])
+    class ArchRestriction(NamedTuple):
+        enabled: bool
+        arch: str
+
+    class BuildRestriction(NamedTuple):
+        enabled: bool
+        profile: str
 
     if TYPE_CHECKING:
         class ParsedRelation(TypedDict):
-- 
2.47.3

>From a63e7044b93d56cc3d029912e7befcb657726fb1 Mon Sep 17 00:00:00 2001
From: Nicolas Boulenguez <[email protected]>
Date: Thu, 6 Nov 2025 21:28:36 +0100
Subject: [PATCH 7/7] Add deb822.PkgRelation.{holds_on_arch,
 holds_with_profiles}

The next concern after parse_relations is probably to check that the
relation holds in a given context.  Enable callers to do that without
depending on the internal representation of arch and profiles
restrictions.
---
 src/debian/deb822.py | 27 +++++++++++++++++++++++++++
 tests/test_deb822.py | 40 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 67 insertions(+)

diff --git a/src/debian/deb822.py b/src/debian/deb822.py
index 4929583..b230e30 100644
--- a/src/debian/deb822.py
+++ b/src/debian/deb822.py
@@ -1487,6 +1487,33 @@ class PkgRelation:
         cnf = map(cls.__pipe_sep_RE.split, tl_deps)
         return [[parse_rel(or_dep) for or_dep in or_deps] for or_deps in cnf]
 
+    @staticmethod
+    def holds_on_arch(relation: ParsedRelation, arch: str) -> bool:
+        """Is relation active on the given architecture?
+
+        Check arch against a disjunction like [amd64 armel]
+        or a conjonction of exclusions like [!amd64 !armel].
+
+        Per policy, the list is non empty and ! affects all names or none."""
+        archs = relation["arch"]
+        return (archs is None
+                or archs[0].enabled == any(arch == a.arch for a in archs))
+
+    @staticmethod
+    def holds_with_profiles(
+        relation: ParsedRelation,
+        profiles: collections.abc.Container[str],
+    ) -> bool:
+        """Is relation active under the given profiles?
+
+        In the relation, '<a !b> <c>' requires that profiles
+        either contains a but not b, or contains c."""
+        restrictions = relation["restrictions"]
+        return (restrictions is None
+                or any(all(term.enabled == (term.profile in profiles)
+                           for term in restriction_list)
+                       for restriction_list in restrictions))
+
     @staticmethod
     def str(rels: List[List[PkgRelation.ParsedRelation]]) -> builtins.str:
         """Format to string structured inter-package relationships
diff --git a/tests/test_deb822.py b/tests/test_deb822.py
index 97623dd..a632e4f 100755
--- a/tests/test_deb822.py
+++ b/tests/test_deb822.py
@@ -1701,6 +1701,46 @@ class TestPkgRelations:
         assert term == "native"
         assert deb822.PkgRelation.str(rel) == r
 
+    def test_holds_on_arch(self) -> None:
+        A = "a"                 # the current architecture
+        for one_relation, expected in (
+            # no restriction
+            ("foo",                     True),
+            # architecture membership
+            ("foo [ a1  a   a2]",       True),
+            ("foo [ a1      a2]",       False),
+            # architecture exclusions
+            ("foo [!a1    !a2]",        True),
+            ("foo [!a1 !a !a2]",        False),
+        ):
+            rel = deb822.PkgRelation.parse_relations(one_relation)[0][0]
+            got = deb822.PkgRelation.holds_on_arch(rel, A)
+            assert got == expected, one_relation
+
+    def test_holds_with_profiles(self) -> None:
+        P = ("p1", "p2")        # the current profiles
+        for one_relation, expected in (
+            # no restriction
+            ("foo",                     True),
+            # profile membership
+            ("foo <p1>",                True),
+            ("foo <p>",                 False),
+            # profile negation
+            ("foo <!p1>",               False),
+            ("foo <!p>",                True),
+            # profile conjunction
+            ("foo <p p1>",              False),
+            ("foo <p1 p2>",             True),
+            ("foo <p1 p2 p>",           False),
+            # profile disjunction
+            ("foo <p> <p1>",            True),
+            ("foo <p> <q>",             False),
+            ("foo <p1> <p2>",           True),
+        ):
+            rel = deb822.PkgRelation.parse_relations(one_relation)[0][0]
+            got = deb822.PkgRelation.holds_with_profiles(rel, P)
+            assert got == expected, one_relation
+
 
 class TestVersionAccessor:
 
-- 
2.47.3

Reply via email to