208 lines
5.6 KiB
Python
208 lines
5.6 KiB
Python
"""
|
|
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.
|
|
|
|
|
|
"""
|