mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
handler name in classvar (#2675)
* handler name in classvar * fix for worker handler name * fix custom templates, event docs * doc tweak * doc tweak * restore signature
This commit is contained in:
@@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- The default Widget repr no longer displays classes and pseudo-classes (to reduce noise in logs). Add them to your `__rich_repr__` method if needed. https://github.com/Textualize/textual/pull/2623
|
||||
- Setting `Screen.AUTO_FOCUS` to `None` will inherit `AUTO_FOCUS` from the app instead of disabling it https://github.com/Textualize/textual/issues/2594
|
||||
- Setting `Screen.AUTO_FOCUS` to `""` will disable it on the screen https://github.com/Textualize/textual/issues/2594
|
||||
- Messages now have a `handler_name` class var which contains the name of the default handler method.
|
||||
|
||||
### Removed
|
||||
|
||||
|
||||
@@ -148,13 +148,40 @@ Most of the logic in a Textual app will be written in message handlers. Let's ex
|
||||
Textual uses the following scheme to map messages classes on to a Python method.
|
||||
|
||||
- Start with `"on_"`.
|
||||
- Add the messages' namespace (if any) converted from CamelCase to snake_case plus an underscore `"_"`
|
||||
- Add the message's namespace (if any) converted from CamelCase to snake_case plus an underscore `"_"`.
|
||||
- Add the name of the class converted from CamelCase to snake_case.
|
||||
|
||||
<div class="excalidraw">
|
||||
--8<-- "docs/images/events/naming.excalidraw.svg"
|
||||
</div>
|
||||
|
||||
Messages have a namespace if they are defined as a child class of a Widget.
|
||||
The namespace is the name of the parent class.
|
||||
For instance, the builtin `Input` class defines it's `Changed` message as follow:
|
||||
|
||||
```python
|
||||
class Input(Widget):
|
||||
...
|
||||
class Changed(Message):
|
||||
"""Posted when the value changes."""
|
||||
...
|
||||
```
|
||||
|
||||
Because `Changed` is a *child* class of `Input`, its namespace will be "input" (and the handler name will be `on_input_changed`).
|
||||
This allows you to have similarly named events, without clashing event handler names.
|
||||
|
||||
!!! tip
|
||||
|
||||
If you are ever in doubt about what the handler name should be for a given event, print the `handler_name` class variable for your event class.
|
||||
|
||||
Here's how you would check the handler name for the `Input.Changed` event:
|
||||
|
||||
```py
|
||||
>>> from textual.widgets import Input
|
||||
>>> Input.Changed.handler_name
|
||||
'on_input_changed'
|
||||
```
|
||||
|
||||
### On decorator
|
||||
|
||||
In addition to the naming convention, message handlers may be created with the [`on`][textual.on] decorator, which turns a method into a handler for the given message or event.
|
||||
|
||||
2497
poetry.lock
generated
2497
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -59,6 +59,7 @@ mypy = "^1.0.0"
|
||||
pytest-cov = "^2.12.1"
|
||||
mkdocs = "^1.3.0"
|
||||
mkdocstrings = {extras = ["python"], version = "^0.20.0"}
|
||||
mkdocstrings-python = "0.10.1"
|
||||
mkdocs-material = "^9.0.11"
|
||||
mkdocs-exclude = "^1.0.2"
|
||||
pre-commit = "^2.13.0"
|
||||
|
||||
@@ -29,7 +29,6 @@ class Message:
|
||||
"_forwarded",
|
||||
"_no_default_action",
|
||||
"_stop_propagation",
|
||||
"_handler_name",
|
||||
"_prevent",
|
||||
]
|
||||
|
||||
@@ -42,6 +41,8 @@ class Message:
|
||||
verbose: ClassVar[bool] = False # Message is verbose
|
||||
no_dispatch: ClassVar[bool] = False # Message may not be handled by client code
|
||||
namespace: ClassVar[str] = "" # Namespace to disambiguate messages
|
||||
handler_name: ClassVar[str]
|
||||
"""Name of the default message handler."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__post_init__()
|
||||
@@ -53,10 +54,6 @@ class Message:
|
||||
self._forwarded = False
|
||||
self._no_default_action = False
|
||||
self._stop_propagation = False
|
||||
name = camel_to_snake(self.__class__.__name__)
|
||||
self._handler_name = (
|
||||
f"on_{self.namespace}_{name}" if self.namespace else f"on_{name}"
|
||||
)
|
||||
self._prevent: set[type[Message]] = set()
|
||||
|
||||
def __rich_repr__(self) -> rich.repr.Result:
|
||||
@@ -77,6 +74,8 @@ class Message:
|
||||
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}"
|
||||
|
||||
@property
|
||||
def control(self) -> Widget | None:
|
||||
@@ -88,12 +87,6 @@ class Message:
|
||||
"""Has the message been forwarded?"""
|
||||
return self._forwarded
|
||||
|
||||
@property
|
||||
def handler_name(self) -> str:
|
||||
"""The name of the handler associated with this message."""
|
||||
# Property to make it read only
|
||||
return self._handler_name
|
||||
|
||||
def _set_forwarded(self) -> None:
|
||||
"""Mark this event as being forwarded."""
|
||||
self._forwarded = True
|
||||
|
||||
@@ -71,8 +71,13 @@ class _MessagePumpMeta(type):
|
||||
for message_type, selectors in getattr(value, "_textual_on"):
|
||||
handlers.setdefault(message_type, []).append((value, selectors))
|
||||
if isclass(value) and issubclass(value, Message):
|
||||
if "namespace" not in value.__dict__:
|
||||
value.namespace = namespace
|
||||
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__)}"
|
||||
)
|
||||
|
||||
class_obj = super().__new__(cls, name, bases, class_dict, **kwargs)
|
||||
return class_obj
|
||||
|
||||
@@ -616,7 +621,7 @@ class MessagePump(metaclass=_MessagePumpMeta):
|
||||
message: A Message object.
|
||||
"""
|
||||
_rich_traceback_guard = True
|
||||
handler_name = message._handler_name
|
||||
handler_name = message.handler_name
|
||||
|
||||
# Look through the MRO to find a handler
|
||||
dispatched = False
|
||||
|
||||
@@ -117,11 +117,9 @@ class Worker(Generic[ResultType]):
|
||||
"""A class to manage concurrent work (either a task or a thread)."""
|
||||
|
||||
@rich.repr.auto
|
||||
class StateChanged(Message, bubble=False):
|
||||
class StateChanged(Message, bubble=False, namespace="worker"):
|
||||
"""The worker state changed."""
|
||||
|
||||
namespace = "worker"
|
||||
|
||||
def __init__(self, worker: Worker, state: WorkerState) -> None:
|
||||
"""Initialize the StateChanged message.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user