Repository: Textualize/textual
File: src/textual/app.py (Python)

Code before:

        self.design = DEFAULT_COLORS

        self._css_has_errors = False
        self.stylesheet = Stylesheet(variables=self.get_css_variables())

        css_path = css_path or self.CSS_PATH
        css_paths = [
            _make_path_object_relative(css_path, self)
            for css_path in (
                _css_path_type_as_list(css_path) if css_path is not None else []
            )
        ]
        self.css_path = css_paths

        self._registry: WeakSet[DOMNode] = WeakSet()

        # Sensitivity on X is double the sensitivity on Y to account for
        # cells being twice as tall as wide
        self.scroll_sensitivity_x: float = 4.0
        """Number of columns to scroll in the X direction with wheel or trackpad."""
        self.scroll_sensitivity_y: float = 2.0
        """Number of lines to scroll in the Y direction with wheel or trackpad."""

        self._installed_screens: dict[str, Screen | Callable[[], Screen]] = {}
        for var_name, dct in (("SCREENS", self.SCREENS), ("MODES", self.MODES)):
            for screen_name, v in dct.items():
                if (
                    isinstance(v, Screen)
                    or (not callable(v))
                    and (not isinstance(v, str))
                ):
                    raise TypeError(
                        f"{var_name} should contain a Screen type or callable, not an instance"
                        f" (got instance of {type(v).__name__} for {screen_name!r})"
                    )
        self._installed_screens.update(**self.SCREENS)
        self.modes: dict[str, str | Callable[[], Screen]] = self.MODES.copy()
        """Contains the working-copy of the `MODES` for each instance."""

        self._compose_stacks: list[list[Widget]] = []
        self._composed: list[list[Widget]] = []
        self._recompose_required = False

        self.devtools: DevtoolsClient | None = None
        self._devtools_redirector: StdoutRedirector | None = None
        if "devtools" in self.features:
            try:
                from textual_dev.client import DevtoolsClient
                from textual_dev.redirect_output import StdoutRedirector
            except ImportError:

Review comment (suggestion):
We have "no abbreviation" rule in Textual. So `var_name` and `dct` would have to be something descriptive.

The constructor may not be the best place for this. As you are checking classvars, it seems redundant to do this work every time the App is instantiated.

Suggest `__init_subclass__` is the right place for this check. That way it would run just once, at import time...

Code after:
        self._css_has_errors = False
        self.stylesheet = Stylesheet(variables=self.get_css_variables())

        css_path = css_path or self.CSS_PATH
        css_paths = [
            _make_path_object_relative(css_path, self)
            for css_path in (
                _css_path_type_as_list(css_path) if css_path is not None else []
            )
        ]
        self.css_path = css_paths

        self._registry: WeakSet[DOMNode] = WeakSet()

        # Sensitivity on X is double the sensitivity on Y to account for
        # cells being twice as tall as wide
        self.scroll_sensitivity_x: float = 4.0
        """Number of columns to scroll in the X direction with wheel or trackpad."""
        self.scroll_sensitivity_y: float = 2.0
        """Number of lines to scroll in the Y direction with wheel or trackpad."""

        self._installed_screens: dict[str, Screen | Callable[[], Screen]] = {}
        self._installed_screens.update(**self.SCREENS)
        self._modes: dict[str, str | Callable[[], Screen]] = self.MODES.copy()
        """Contains the working-copy of the `MODES` for each instance."""

        self._compose_stacks: list[list[Widget]] = []
        self._composed: list[list[Widget]] = []
        self._recompose_required = False

        self.devtools: DevtoolsClient | None = None
        self._devtools_redirector: StdoutRedirector | None = None
        if "devtools" in self.features:
            try:
                from textual_dev.client import DevtoolsClient
                from textual_dev.redirect_output import StdoutRedirector
            except ImportError:
                # Dev dependencies not installed
                pass
            else:
                self.devtools = DevtoolsClient(constants.DEVTOOLS_HOST)
                self._devtools_redirector = StdoutRedirector(self.devtools)

        self._loop: asyncio.AbstractEventLoop | None = None
        self._return_value: ReturnType | None = None
        """Internal attribute used to set the return value for the app."""
        self._return_code: int | None = None
        """Internal attribute used to set the return code for the app."""
        self._exit = False
        self._disable_tooltips = False
        self._disable_notifications = False