Skip to content

component

component #

Component — one node of a page; a subclass's annotated fields are its props.

  1. Declaring a subclass freezes it as a dataclass over its annotations; building an instance checks every prop against its annotation (props).
  2. A page renders by asking each component for its HTML (html), handing it the page's render context to stage files and resolve links through.
  3. A component given a Missing for any prop is absent: its props go unchecked and it renders as nothing (absent). A Missing item inside a list prop is left out instead (check_prop).
  4. Under a PageBuilder, a freshly built component attaches itself to the innermost open stack (Build.attach), so the body of a page function reads as the page, top to bottom.

Component #

Base of every page component; subclass it, annotate the props, implement html.

A prop is a JSON scalar, a list or dict of them, a cg value, or another component; each is checked against its annotation when the component is built, and a path given for a file prop becomes that file scalar. A component given Missing for any prop is absent and renders as nothing, so cg.ui.Stat("Wind", session.weather.wind_speed_mps) simply leaves the page when no weather was recorded; a Missing item in a list prop is left out.

class Badge(cg.ui.Component):
    text: str

    def html(self, render: cg.ui.Render) -> str:
        return cg.ui.element("span", {"class": "badge"}, cg.ui.text(self.text))
Source code in capturegraph-lib/capturegraph/ui/component.py
@dataclass_transform(frozen_default=True)
class Component:
    """Base of every page component; subclass it, annotate the props, implement ``html``.

    A prop is a JSON scalar, a list or dict of them, a ``cg`` value, or another
    component; each is checked against its annotation when the component is
    built, and a path given for a file prop becomes that file scalar. A
    component given ``Missing`` for any prop is absent and renders as nothing,
    so ``cg.ui.Stat("Wind", session.weather.wind_speed_mps)`` simply leaves
    the page when no weather was recorded; a ``Missing`` item in a list prop
    is left out.

    ```python
    class Badge(cg.ui.Component):
        text: str

        def html(self, render: cg.ui.Render) -> str:
            return cg.ui.element("span", {"class": "badge"}, cg.ui.text(self.text))
    ```
    """

    _props: ClassVar[dict[str, object]] = {}

    def __init_subclass__(cls, **kwargs: object) -> None:
        """Freeze ``cls`` over its props and make its ``html`` render nothing when absent."""
        super().__init_subclass__(**kwargs)
        dataclass(frozen=True)(cls)
        cls._props = {
            name: hint
            for name, hint in get_type_hints(cls).items()
            if get_origin(hint) is not ClassVar
        }
        if "html" in cls.__dict__:
            cls.html = _nothing_when_absent(cls.__dict__["html"])

    def __post_init__(self) -> None:
        """Check every prop against its annotation, coercing where a scalar admits it.

        Raises:
            TypeError: If a prop does not fit its annotation; the message names the prop.
        """
        if not self.absent:
            for name, annotation in type(self)._props.items():
                checked = check_prop(
                    getattr(self, name), annotation, f"{type(self).__name__}.{name}"
                )
                object.__setattr__(self, name, checked)
        Build.attach(self)

    @property
    def absent(self) -> bool:
        """Whether any prop is ``Missing``, in which case the component renders as nothing."""
        return any(isinstance(getattr(self, name), _Missing) for name in type(self)._props)

    def html(self, render: Render) -> str:
        """This component as HTML, its files staged and its links resolved through ``render``.

        Raises:
            NotImplementedError: Always, on the base class.
        """
        raise NotImplementedError(f"{type(self).__name__} does not implement html()")

absent property #

Whether any prop is Missing, in which case the component renders as nothing.

__init_subclass__(**kwargs) #

Freeze cls over its props and make its html render nothing when absent.

Source code in capturegraph-lib/capturegraph/ui/component.py
def __init_subclass__(cls, **kwargs: object) -> None:
    """Freeze ``cls`` over its props and make its ``html`` render nothing when absent."""
    super().__init_subclass__(**kwargs)
    dataclass(frozen=True)(cls)
    cls._props = {
        name: hint
        for name, hint in get_type_hints(cls).items()
        if get_origin(hint) is not ClassVar
    }
    if "html" in cls.__dict__:
        cls.html = _nothing_when_absent(cls.__dict__["html"])

__post_init__() #

Check every prop against its annotation, coercing where a scalar admits it.

Raises:

Type Description
TypeError

If a prop does not fit its annotation; the message names the prop.

Source code in capturegraph-lib/capturegraph/ui/component.py
def __post_init__(self) -> None:
    """Check every prop against its annotation, coercing where a scalar admits it.

    Raises:
        TypeError: If a prop does not fit its annotation; the message names the prop.
    """
    if not self.absent:
        for name, annotation in type(self)._props.items():
            checked = check_prop(
                getattr(self, name), annotation, f"{type(self).__name__}.{name}"
            )
            object.__setattr__(self, name, checked)
    Build.attach(self)

html(render) #

This component as HTML, its files staged and its links resolved through render.

Raises:

Type Description
NotImplementedError

Always, on the base class.

Source code in capturegraph-lib/capturegraph/ui/component.py
def html(self, render: Render) -> str:
    """This component as HTML, its files staged and its links resolved through ``render``.

    Raises:
        NotImplementedError: Always, on the base class.
    """
    raise NotImplementedError(f"{type(self).__name__} does not implement html()")