Coverage for src / lexigram / ui / decorators.py: 100%
16 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-10 04:11 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-10 04:11 +0800
1"""Decorators for UI components — metadata tagging for registry and render pipeline."""
3from __future__ import annotations
5from collections.abc import Callable
6import functools
7from typing import Any, TypeVar
9F = TypeVar("F", bound=Callable[..., Any])
11__all__ = [
12 "component",
13]
16def component(
17 name: str | None = None,
18 *,
19 cacheable: bool = False,
20) -> Callable[[F], F]:
21 """Mark a callable as a named UI component and attach component metadata.
23 Tags the decorated function with ``__component_name__`` and
24 ``__component_cacheable__`` attributes so that component registries,
25 debug tooling, and render pipelines can discover and identify components
26 by name without relying on ``__qualname__``.
28 This decorator does **not** register the component in any global registry
29 by itself — registration is handled by the DI container via the
30 :class:`~lexigram.ui.di.provider.UIProvider`. The decorator provides
31 the metadata that the provider reads during component discovery.
33 Args:
34 name: Logical component name used for registration and cache keys.
35 Defaults to the decorated function's ``__name__``.
36 cacheable: When ``True``, signals that the component's rendered output
37 may be cached by the render pipeline. Defaults to ``False``.
39 Returns:
40 Decorator that attaches component metadata to the target callable.
42 Example::
44 @component("user_card", cacheable=True)
45 def user_card(user: User) -> str:
46 return render_to_string(
47 el("div", {"class": "card"}, user.display_name)
48 )
50 @component()
51 def avatar(src: str, alt: str = "") -> str:
52 return render_to_string(el("img", {"src": src, "alt": alt}))
53 """
55 def decorator(fn: F) -> F:
56 component_name = name or fn.__name__
58 @functools.wraps(fn)
59 def wrapper(*args: Any, **kwargs: Any) -> Any:
60 return fn(*args, **kwargs)
62 wrapper.__component_name__ = component_name # type: ignore[attr-defined]
63 wrapper.__component_cacheable__ = cacheable # type: ignore[attr-defined]
64 return wrapper # type: ignore[return-value]
66 return decorator