Skip to content

Commit

Permalink
calculate message namespace from __qualname__ when not specified (#3940)
Browse files Browse the repository at this point in the history
* use __qualname__ for the default message namespace

* improve tests

* update changelog

* better, more backwards compatible splitting

* Fix syntax

* Fix CHANGELOG

---------

Co-authored-by: Darren Burns <[email protected]>
Co-authored-by: Darren Burns <[email protected]>
  • Loading branch information
3 people authored Jul 17, 2024
1 parent 433d78f commit c9bb137
Show file tree
Hide file tree
Showing 4 changed files with 16 additions and 13 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Fixed `Tree` and `DirectoryTree` horizontal scrolling off-by-2 https://github.com/Textualize/textual/pull/4744
- Fixed text-opacity in component styles https://github.com/Textualize/textual/pull/4747
- Ensure `Tree.select_node` sends `NodeSelected` message https://github.com/Textualize/textual/pull/4753
- Fixed message handlers not working when message types are assigned as the value of class vars https://github.com/Textualize/textual/pull/3940
- Fixed `CommandPalette` not focusing the input when opened when `App.AUTO_FOCUS` doesn't match the input https://github.com/Textualize/textual/pull/4763
- `SelectionList.SelectionToggled` will now be sent for each option when a bulk toggle is performed (e.g. `toggle_all`). Previously no messages were sent at all. https://github.com/Textualize/textual/pull/4759

Expand Down
12 changes: 10 additions & 2 deletions src/textual/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,16 @@ def __init_subclass__(
cls.no_dispatch = no_dispatch
if namespace is not None:
cls.namespace = namespace
name = camel_to_snake(cls.__name__)
cls.handler_name = f"on_{namespace}_{name}" if namespace else f"on_{name}"
name = f"{namespace}_{camel_to_snake(cls.__name__)}"
else:
# a class defined inside of a function will have a qualified name like func.<locals>.Class,
# so make sure we only use the actual class name(s)
qualname = cls.__qualname__.rsplit("<locals>.", 1)[-1]
# only keep the last two parts of the qualified name of deeply nested classes
# for backwards compatibility, e.g. A.B.C.D becomes C.D
namespace = qualname.rsplit(".", 2)[-2:]
name = "_".join(camel_to_snake(part) for part in namespace)
cls.handler_name = f"on_{name}"

@property
def control(self) -> DOMNode | None:
Expand Down
11 changes: 0 additions & 11 deletions src/textual/message_pump.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from __future__ import annotations

import asyncio
import inspect
import threading
from asyncio import CancelledError, Queue, QueueEmpty, Task, create_task
from contextlib import contextmanager
Expand All @@ -36,7 +35,6 @@
from ._context import prevent_message_types_stack
from ._on import OnNoWidget
from ._time import time
from .case import camel_to_snake
from .css.match import match
from .errors import DuplicateKeyHandlers
from .events import Event
Expand Down Expand Up @@ -78,8 +76,6 @@ def __new__(
class_dict: dict[str, Any],
**kwargs: Any,
) -> _MessagePumpMetaSub:
namespace = camel_to_snake(name)
isclass = inspect.isclass
handlers: dict[
type[Message], list[tuple[Callable, dict[str, tuple[SelectorSet, ...]]]]
] = class_dict.get("_decorated_handlers", {})
Expand All @@ -93,13 +89,6 @@ def __new__(
] = getattr(value, "_textual_on")
for message_type, selectors in textual_on:
handlers.setdefault(message_type, []).append((value, selectors))
if isclass(value) and issubclass(value, Message):
if "namespace" in value.__dict__:
value.handler_name = f"on_{value.__dict__['namespace']}_{camel_to_snake(value.__name__)}"
else:
value.handler_name = (
f"on_{namespace}_{camel_to_snake(value.__name__)}"
)

# Look for reactives with public AND private compute methods.
prefix = "compute_"
Expand Down
5 changes: 5 additions & 0 deletions tests/test_message_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ class Right(BaseWidget):
class Fired(BaseWidget.Fired):
pass

class DummyWidget(Widget):
# ensure that referencing a message type in other class scopes
# doesn't break the namespace
_event = Left.Fired

handlers_called = []

class MessageInheritanceApp(App[None]):
Expand Down

0 comments on commit c9bb137

Please sign in to comment.