""" The root Textual module. Exposes some commonly used symbols. """ from __future__ import annotations import inspect import weakref from typing import TYPE_CHECKING, Callable import rich.repr from textual import constants from textual._context import active_app from textual._log import LogGroup, LogVerbosity from textual._on import on from textual._work_decorator import work if TYPE_CHECKING: from typing_extensions import TypeAlias __all__ = [ "__version__", # type: ignore "log", "on", "work", ] LogCallable: TypeAlias = "Callable" if TYPE_CHECKING: from importlib.metadata import version from textual.app import App as _App __version__ = version("textual") """The version of Textual.""" else: def __getattr__(name: str) -> str: """Lazily get the version.""" if name == "__version__": from importlib.metadata import version return version("textual") raise AttributeError(f"module {__name__!r} has no attribute {name!r}") class LoggerError(Exception): """Raised when the logger failed.""" @rich.repr.auto class Logger: """A [logger class](/guide/devtools/#logging-handler) that logs to the Textual [console](/guide/devtools#console).""" def __init__( self, log_callable: LogCallable | None, group: LogGroup = LogGroup.INFO, verbosity: LogVerbosity = LogVerbosity.NORMAL, app: _App | None = None, ) -> None: self._log = log_callable self._group = group self._verbosity = verbosity self._app = None if app is None else weakref.ref(app) @property def app(self) -> _App | None: """The associated application, or `None` if there isn't one.""" return None if self._app is None else self._app() def __rich_repr__(self) -> rich.repr.Result: yield self._group, LogGroup.INFO yield self._verbosity, LogVerbosity.NORMAL def __call__(self, *args: object, **kwargs) -> None: if constants.LOG_FILE: output = " ".join(str(arg) for arg in args) if kwargs: key_values = " ".join( f"{key}={value!r}" for key, value in kwargs.items() ) output = f"{output} {key_values}" if output else key_values with open(constants.LOG_FILE, "a", encoding="utf-8") as log_file: print(output, file=log_file) app = self.app if app is None: try: app = active_app.get() except LookupError: if constants.DEBUG: print_args = ( *args, *[f"{key}={value!r}" for key, value in kwargs.items()], ) print(*print_args) return if not app._is_devtools_connected: return current_frame = inspect.currentframe() assert current_frame is not None previous_frame = current_frame.f_back assert previous_frame is not None caller = inspect.getframeinfo(previous_frame) _log = self._log or app._log try: _log( self._group, self._verbosity, caller, *args, **kwargs, ) except LoggerError: # If there is not active app, try printing if constants.DEBUG: print_args = ( *args, *[f"{key}={value!r}" for key, value in kwargs.items()], ) print(*print_args) def verbosity(self, verbose: bool) -> Logger: """Get a new logger with selective verbosity. Args: verbose: True to use HIGH verbosity, otherwise NORMAL. Returns: New logger. """ verbosity = LogVerbosity.HIGH if verbose else LogVerbosity.NORMAL return Logger(self._log, self._group, verbosity, app=self.app) @property def verbose(self) -> Logger: """A verbose logger.""" return Logger(self._log, self._group, LogVerbosity.HIGH, app=self.app) @property def event(self) -> Logger: """Logs events.""" return Logger(self._log, LogGroup.EVENT, app=self.app) @property def debug(self) -> Logger: """Logs debug messages.""" return Logger(self._log, LogGroup.DEBUG, app=self.app) @property def info(self) -> Logger: """Logs information.""" return Logger(self._log, LogGroup.INFO, app=self.app) @property def warning(self) -> Logger: """Logs warnings.""" return Logger(self._log, LogGroup.WARNING, app=self.app) @property def error(self) -> Logger: """Logs errors.""" return Logger(self._log, LogGroup.ERROR, app=self.app) @property def system(self) -> Logger: """Logs system information.""" return Logger(self._log, LogGroup.SYSTEM, app=self.app) @property def logging(self) -> Logger: """Logs from stdlib logging module.""" return Logger(self._log, LogGroup.LOGGING, app=self.app) @property def worker(self) -> Logger: """Logs worker information.""" return Logger(self._log, LogGroup.WORKER, app=self.app) log = Logger(None) """Global logger that logs to the currently active app. Example: ```python from textual import log log(locals()) ``` !!! note This logger will only work if there is an active app in the current thread. Use `app.log` to write logs from a thread without an active app. """