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

