From 1432b49457873ca4e535af778d5e402e759ccc02 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Nov 2026 00:08:33 -0500 Subject: [PATCH 1/2] Each class owns its declared method keys, settled once at creation method_bindings and class_bindings each read base.__dict__.get("_declared_key_bindings", ()), a class-body probe with a default for when the name is absent. MethodBinding.__set_name__ runs before __init_subclass__, so DOMNode.__init_subclass__ now settles cls._declared_key_bindings beside _own_bindings, and both readers read the attribute. method_bindings walks only DOMNode bases: object and plain mixins never pass __init_subclass__, and none of them declares method keys (689 owners loaded with Toad, all DOMNode). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01XbFMSrQKHwQJSkZZaLyQha --- src/textual/dom.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/textual/dom.py b/src/textual/dom.py index a085922f33..9cfff6b8bb 100644 --- a/src/textual/dom.py +++ b/src/textual/dom.py @@ -574,8 +574,8 @@ def declared_bindings(cls, mode: type[EditingMode]) -> tuple[Binding, ...]: @classmethod def method_bindings(cls, mode: type[EditingMode]) -> tuple[Binding, ...]: """Keys each class in the MRO declares on its own methods.""" - return tuple(projected for base in cls.__mro__ - for binding in base.__dict__.get("_declared_key_bindings", ()) + return tuple(projected for base in cls.__mro__ if issubclass(base, DOMNode) + for binding in base._declared_key_bindings for projected in binding.for_mode(mode)) @classmethod @@ -589,7 +589,7 @@ def class_bindings(cls, mode: type[EditingMode]) -> dict[str, list[Binding]]: keys: dict[str, list[Binding]] = {} for base in cls._binding_bases(): keys.update(base._own_bindings.project( - mode, base.__dict__.get("_declared_key_bindings", ())).key_to_bindings) + mode, base._declared_key_bindings).key_to_bindings) return keys @classmethod @@ -1025,6 +1025,8 @@ def __init_subclass__( for base in bases: css_type_names.add(base.__name__) cls._own_bindings = BindingsMap(cls.__dict__.get("BINDINGS", [])) + # MethodBinding.__set_name__ has already run for this body; a class without one owns none. + cls._declared_key_bindings = cls.__dict__.get("_declared_key_bindings", ()) cls._merged_bindings = cls._merge_bindings() cls._css_type_names = frozenset(css_type_names) compute_methods = frozenset( From 2f21ec927776ee8940fcf9e1bfa7eaf14b53ff9d Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 6 Nov 2026 00:10:11 -0500 Subject: [PATCH 2/2] One walk owns the MRO's DOMNode classes method_bindings filtered cls.__mro__ with issubclass(base, DOMNode) and _binding_bases ran the same filter over the reversed MRO: one fact, "which classes of this MRO are DOMNodes", written at two sites. _dom_bases() states it once and both read it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd --- src/textual/dom.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/textual/dom.py b/src/textual/dom.py index 9cfff6b8bb..68e2b8dd13 100644 --- a/src/textual/dom.py +++ b/src/textual/dom.py @@ -574,7 +574,7 @@ def declared_bindings(cls, mode: type[EditingMode]) -> tuple[Binding, ...]: @classmethod def method_bindings(cls, mode: type[EditingMode]) -> tuple[Binding, ...]: """Keys each class in the MRO declares on its own methods.""" - return tuple(projected for base in cls.__mro__ if issubclass(base, DOMNode) + return tuple(projected for base in cls._dom_bases() for binding in base._declared_key_bindings for projected in binding.for_mode(mode)) @@ -592,16 +592,21 @@ def class_bindings(cls, mode: type[EditingMode]) -> dict[str, list[Binding]]: mode, base._declared_key_bindings).key_to_bindings) return keys + @classmethod + def _dom_bases(cls) -> tuple[type[DOMNode], ...]: + """The MRO's DOMNode classes, most derived first: object and plain mixins never + pass DOMNode.__init_subclass__, so they own no bindings.""" + return tuple(base for base in cls.__mro__ if issubclass(base, DOMNode)) + @classmethod def _binding_bases(cls) -> list[type[DOMNode]]: """The DOMNode bases whose keys this class inherits, oldest first: the walk restarts at any base that does not inherit its ancestors' bindings.""" bases: list[type[DOMNode]] = [] - for base in reversed(cls.__mro__): - if issubclass(base, DOMNode): - if not base._inherit_bindings: - bases.clear() - bases.append(base) + for base in reversed(cls._dom_bases()): + if not base._inherit_bindings: + bases.clear() + bases.append(base) return bases @classmethod