From 59fb0d7c4d4c9f89ce1316d5bffd8fb2d74768e8 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Thu, 25 Jun 2026 13:39:02 -0400 Subject: [PATCH 1/2] atom: adapt to portage Atom attributes becoming read-only properties portage reworked Atom to expose operator, blocker, use, slot, cp, cpv, and repo as @property descriptors backed by private _* attributes. The pre-__init__ write "self.operator = self.blocker = self.use = self.slot = None" and the post-__init__ "self.operator = ''" normalization both fail because object.__setattr__() respects data descriptors and raises AttributeError when a property has no setter. Override operator as a read-only property normalizing None to "" to preserve the intersects() contract. Initialize CPV's private attrs directly since portage already manages cpv. Signed-off-by: Matt Turner --- pym/gentoolkit/atom.py | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/pym/gentoolkit/atom.py b/pym/gentoolkit/atom.py index 27cf77c8..f3c33d68 100644 --- a/pym/gentoolkit/atom.py +++ b/pym/gentoolkit/atom.py @@ -24,6 +24,8 @@ # Classes # ======= +_UNSET = object() + class Atom(portage.dep.Atom, CPV): """Portage's Atom class with improvements from pkgcore. @@ -43,20 +45,36 @@ class Atom(portage.dep.Atom, CPV): # Necessary for Portage versions < 2.1.7 _atoms = weakref.WeakValueDictionary() + @property + def operator(self): + # Old portage stored operator as a plain instance attribute in + # __dict__; new portage exposes it as a read-only @property backed + # by _operator. super().operator only searches class __dict__ + # entries, so check the instance dict first. + op = self.__dict__.get("operator", _UNSET) + if op is _UNSET: + op = super().operator + return "" if op is None else op + def __init__(self, atom): self.atom = atom - self.operator = self.blocker = self.use = self.slot = None try: portage.dep.Atom.__init__(self, atom) except portage.exception.InvalidAtom: raise errors.GentoolkitInvalidAtom(atom) - # Make operator compatible with intersects - if self.operator is None: - self.operator = "" - - CPV.__init__(self, self.cpv) + # cpv is already managed by portage.dep.Atom; initialize CPV's + # private attrs directly to avoid writing to portage's read-only + # cpv property (new portage) or redundantly overwriting __dict__ + # (old portage). + self._category = None + self._name = None + self._version = None + self._revision = None + self._cp = None + self._fullversion = None + self.validate = False # use_conditional is USE flag condition for this Atom to be required: # For: !build? ( >=sys-apps/sed-4.0.5 ), use_conditional = '!build' From 8e0b3104a0c46b014918e51937857fd3476982cc Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Thu, 25 Jun 2026 13:39:20 -0400 Subject: [PATCH 2/2] atom: remove no-op operator assignments in intersects() The tuple assignments ranged, ranged.operator = self, self.operator evaluate the RHS before binding, so .operator is always written back with its own value. With operator now a read-only property they raise AttributeError. Signed-off-by: Matt Turner --- pym/gentoolkit/atom.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pym/gentoolkit/atom.py b/pym/gentoolkit/atom.py index f3c33d68..b529c2f5 100644 --- a/pym/gentoolkit/atom.py +++ b/pym/gentoolkit/atom.py @@ -297,10 +297,10 @@ def intersects(self, other): # If we get here at least one of us is a <, <=, > or >=: if self.operator in ("<", "<=", ">", ">="): - ranged, ranged.operator = self, self.operator + ranged = self else: - ranged, ranged.operator = other, other.operator - other, other.operator = self, self.operator + ranged = other + other = self if "<" in other.operator or ">" in other.operator: # We are both ranged, and in the opposite "direction" (or