Skip to content

Commit

Permalink
Use __init_subclass__ instead of metaclass.
Browse files Browse the repository at this point in the history
Relevant review comment: #4252 (comment)
  • Loading branch information
rodrigogiraoserrao committed Mar 5, 2024
1 parent e7846fe commit 2a7e762
Show file tree
Hide file tree
Showing 3 changed files with 21 additions and 33 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Changed

- Clicking a non focusable widget focus ancestors https://github.com/Textualize/textual/pull/4236
- BREAKING: Querying and TCSS expect widget class names to start with a capital letter or an underscore `_` https://github.com/Textualize/textual/pull/4252
- BREAKING: widget class names must start with a capital letter or an underscore `_` https://github.com/Textualize/textual/pull/4252

## [0.52.1] - 2024-02-20

Expand Down
41 changes: 10 additions & 31 deletions src/textual/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

from __future__ import annotations

import warnings
from asyncio import Lock, create_task, wait
from collections import Counter
from contextlib import asynccontextmanager
Expand All @@ -13,7 +12,6 @@
from types import TracebackType
from typing import (
TYPE_CHECKING,
Any,
AsyncGenerator,
Awaitable,
ClassVar,
Expand All @@ -22,7 +20,6 @@
Iterable,
NamedTuple,
Sequence,
Type,
TypeVar,
cast,
overload,
Expand Down Expand Up @@ -75,7 +72,6 @@
)
from .layouts.vertical import VerticalLayout
from .message import Message
from .message_pump import _MessagePumpMeta
from .messages import CallbackType
from .notifications import Notification, SeverityLevel
from .reactive import Reactive
Expand Down Expand Up @@ -247,35 +243,12 @@ def __get__(self, obj: Widget, objtype: type[Widget] | None = None) -> str | Non
return title.markup


_WidgetMetaSub = TypeVar("_WidgetMetaSub", bound="_WidgetMeta")


class _WidgetMeta(_MessagePumpMeta):
"""Metaclass for widgets.
Used to issue a warning if a widget subclass is created with naming that's
incompatible with TCSS/querying.
"""

def __new__(
mcs: Type[_WidgetMetaSub],
name: str,
*args: Any,
**kwargs: Any,
) -> _WidgetMetaSub:
"""Hook into widget subclass creation to check the subclass name."""
if not name[0].isupper() and not name.startswith("_"):
warnings.warn(
SyntaxWarning(
f"Widget subclass {name!r} should be capitalised or start with '_'."
),
stacklevel=2,
)
return super().__new__(mcs, name, *args, **kwargs)
class BadWidgetName(Exception):
"""Raised when widget class names do not satisfy the required restrictions."""


@rich.repr.auto
class Widget(DOMNode, metaclass=_WidgetMeta):
class Widget(DOMNode):
"""
A Widget is the base class for Textual widgets.
Expand Down Expand Up @@ -2919,11 +2892,17 @@ def __init_subclass__(
inherit_css: bool = True,
inherit_bindings: bool = True,
) -> None:
base = cls.__mro__[0]
name = cls.__name__
if not name[0].isupper() and not name.startswith("_"):
raise BadWidgetName(
f"Widget subclass {name!r} should be capitalised or start with '_'."
)

super().__init_subclass__(
inherit_css=inherit_css,
inherit_bindings=inherit_bindings,
)
base = cls.__mro__[0]
if issubclass(base, Widget):
cls.can_focus = base.can_focus if can_focus is None else can_focus
cls.can_focus_children = (
Expand Down
11 changes: 10 additions & 1 deletion tests/test_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from textual.css.query import NoMatches
from textual.geometry import Offset, Size
from textual.message import Message
from textual.widget import MountError, PseudoClasses, Widget
from textual.widget import BadWidgetName, MountError, PseudoClasses, Widget
from textual.widgets import Label, LoadingIndicator


Expand Down Expand Up @@ -513,3 +513,12 @@ def compose(self) -> ComposeResult:
"l1",
"l3",
]


def test_bad_widget_name_raised() -> None:
"""Ensure error is raised when bad class names are used for widgets."""

with pytest.raises(BadWidgetName):

class lowercaseWidget(Widget):
pass

0 comments on commit 2a7e762

Please sign in to comment.