Textualize/textual · error · TypeError

expected a callable or string, got {screen_object!r}

Error message

expected a callable or string, got {screen_object!r}

What it means

TypeError raised in App.__init_subclass__ when a SCREENS/MODES entry is neither a string nor callable and also not a Screen instance (e.g. an int, None, or arbitrary object). Class-level validation runs as soon as the App subclass is defined.

Source

Thrown at src/textual/app.py:924

    @cached_property
    def _exception_event(self) -> asyncio.Event:
        """An event that will be set when the first exception is encountered."""
        return asyncio.Event()

    def __init_subclass__(cls, *args, **kwargs) -> None:
        for variable_name, screen_collection in (
            ("SCREENS", cls.SCREENS),
            ("MODES", cls.MODES),
        ):
            for screen_name, screen_object in screen_collection.items():
                if not (isinstance(screen_object, str) or callable(screen_object)):
                    if isinstance(screen_object, Screen):
                        raise ValueError(
                            f"{variable_name} should contain a Screen type or callable, not an instance"
                            f" (got instance of {type(screen_object).__name__} for {screen_name!r})"
                        )
                    raise TypeError(
                        f"expected a callable or string, got {screen_object!r}"
                    )

        return super().__init_subclass__(*args, **kwargs)

    def _thread_init(self):
        """Initialize threading primitives for the current thread.

        https://github.com/Textualize/textual/issues/5845

        """
        self._message_queue
        self._mounted_event
        self._exception_event
        self._thread_id = threading.get_ident()

    def _get_dom_base(self) -> DOMNode:
        """When querying from the app, we want to query the default screen."""

View on GitHub (pinned to 06dbeef4bb)

Solutions

  1. Replace the entry with a Screen subclass, a callable returning a Screen, or a screen name string
  2. Log/validate dict contents when building SCREENS dynamically before class creation

Example fix

# before
class MyApp(App):
    SCREENS = {"main": None}

# after
class MyApp(App):
    SCREENS = {"main": "main"}  # or MainScreen class
Defensive patterns

Strategy: type-guard

Validate before calling

assert all(isinstance(v, str) or callable(v) for v in [*SCREENS.values(), *MODES.values()])

Type guard

def valid_entry(v) -> bool:
    return isinstance(v, str) or callable(v)

Prevention

When it happens

Trigger: SCREENS = {"main": 42} or MODES = {"settings": None} in an App subclass definition.

Common situations: Typos, leftover placeholder values, or programmatically built dicts that inject non-screen values.

Related errors


AI-assisted analysis of Textualize/textual@06dbeef4bb (2026-08-27). Data as JSON: /api/errors/d0a5c78a45276692. Report an issue: GitHub.