Skip to content

htmforge

htmforge

htmforge — Typsichere, composable UI-Komponenten für Python.

Server-side rendered, framework-agnostisch, HTMX-first.

Quickstart::

from htmforge import Component
from htmforge.elements import div, p

class Card(Component):
    title: str
    body: str

    def render(self):
        return div(p(self.title), p(self.body), cls="card")

print(Card(title="Hello", body="World").to_html())

Component(**data)

Bases: BaseModel, ABC

Abstrakte Basisklasse für wiederverwendbare UI-Komponenten.

Subklassen deklarieren typisierte Props als Pydantic-Felder und implementieren die :meth:render-Methode, die ein :class:~htmforge.core.element.Element zurückgibt.

Die Klasse aktiviert Pydantic-Features
  • validate_assignment = True: Props werden auch nach der Initialisierung validiert.
  • arbitrary_types_allowed = True: Erlaubt Non-Pydantic-Typen wie DOM-Elemente als Felder.
  • frozen = False: Komponenten sind per Default mutable.
Example

from htmforge.elements import div, p

class Card(Component): ... title: str ... body: str ... ... def render(self) -> Element: ... return div(p(self.title), p(self.body), cls="card") ... Card(title="Hallo", body="Welt").to_html() '

Hallo

Welt

'

Initialisiert die Komponente und blockiert Klassen ohne render.

Source code in htmforge\core\component.py
def __init__(self, **data: Any) -> None:  # noqa: ANN401
    """Initialisiert die Komponente und blockiert Klassen ohne ``render``."""
    if getattr(type(self), "__htmforge_missing_render__", False):
        raise TypeError(
            f"Can't instantiate abstract class {type(self).__name__} "
            "without a concrete render() implementation"
        )
    super().__init__(**data)

__init_subclass__(**kwargs)

Validiert, dass Unterklassen eine konkrete render-Methode haben.

Source code in htmforge\core\component.py
def __init_subclass__(cls, **kwargs: Any) -> None:  # noqa: ANN401
    """Validiert, dass Unterklassen eine konkrete ``render``-Methode haben."""
    super().__init_subclass__(**kwargs)
    if cls is Component:
        return
    if cls.render is Component.render:
        cls.__htmforge_missing_render__ = True
    else:
        cls.__htmforge_missing_render__ = False

__repr__()

Gibt eine lesbare Debug-Darstellung der Komponente zurueck.

Example

Card(title="Hi", body="World") Card(title='Hi', body='World')

Source code in htmforge\core\component.py
def __repr__(self) -> str:
    """Gibt eine lesbare Debug-Darstellung der Komponente zurueck.

    Example:
        >>> Card(title="Hi", body="World")
        Card(title='Hi', body='World')
    """
    fields = self.model_fields.keys()
    props = ", ".join(
        f"{k}={getattr(self, k)!r}"
        for k in fields
        if getattr(self, k) != self.model_fields[k].default
    )
    return f"{type(self).__name__}({props})"

htmx_attrs()

Gibt alle gesetzten HTMX-Props als Attribut-Dict zurueck.

Returns:

Type Description
dict[str, object]

Ein Dict mit nur den HTMX-Attributen, die nicht None sind.

Example

button("Save", **self.htmx_attrs())

Source code in htmforge\core\component.py
def htmx_attrs(self) -> dict[str, object]:
    """Gibt alle gesetzten HTMX-Props als Attribut-Dict zurueck.

    Returns:
        Ein Dict mit nur den HTMX-Attributen, die nicht ``None`` sind.

    Example:
        ``button("Save", **self.htmx_attrs())``
    """
    attrs: dict[str, object] = {}
    for key in (
        "hx_get",
        "hx_post",
        "hx_put",
        "hx_patch",
        "hx_delete",
        "hx_trigger",
        "hx_target",
        "hx_swap",
        "hx_push_url",
        "hx_confirm",
        "hx_indicator",
        "hx_include",
        "hx_vals",
        "hx_headers",
        "hx_request",
        "hx_select",
        "hx_select_oob",
        "hx_params",
        "hx_encoding",
    ):
        value = getattr(self, key)
        if value is not None:
            attrs[key] = _normalize_htmx_value(value)
    return attrs

render() abstractmethod

Rendert die Komponente zu einem :class:~htmforge.core.element.Element.

Subklassen müssen diese Methode implementieren und das Root-Element der Komponente zurückgeben.

Returns:

Type Description
Element

Das Root-:class:~htmforge.core.element.Element der Komponente.

Source code in htmforge\core\component.py
@abstractmethod
def render(self) -> Element:
    """Rendert die Komponente zu einem :class:`~htmforge.core.element.Element`.

    Subklassen müssen diese Methode implementieren und das Root-Element
    der Komponente zurückgeben.

    Returns:
        Das Root-:class:`~htmforge.core.element.Element` der Komponente.
    """
    ...

to_django()

Gibt eine Django-kompatible HttpResponse zurück.

Note

Erfordert django als optionale Dependency.

Returns:

Type Description
Any

Eine django.http.HttpResponse mit dem gerenderten HTML.

Raises:

Type Description
ImportError

Wenn django nicht installiert ist.

Source code in htmforge\core\component.py
def to_django(self) -> Any:  # noqa: ANN401
    """Gibt eine Django-kompatible ``HttpResponse`` zurück.

    Note:
        Erfordert ``django`` als optionale Dependency.

    Returns:
        Eine ``django.http.HttpResponse`` mit dem gerenderten HTML.

    Raises:
        ImportError: Wenn ``django`` nicht installiert ist.
    """
    try:
        from django.http import HttpResponse
    except ImportError as exc:
        raise ImportError(
            "django ist nicht installiert. Installiere es mit: pip install django"
        ) from exc
    return HttpResponse(content=self.to_html())

to_fastapi()

Gibt eine FastAPI-kompatible HTMLResponse zurück.

Note

Erfordert fastapi als optionale Dependency.

Returns:

Type Description
Any

Eine fastapi.responses.HTMLResponse mit dem gerenderten HTML.

Raises:

Type Description
ImportError

Wenn fastapi nicht installiert ist.

Source code in htmforge\core\component.py
def to_fastapi(self) -> Any:  # noqa: ANN401
    """Gibt eine FastAPI-kompatible ``HTMLResponse`` zurück.

    Note:
        Erfordert ``fastapi`` als optionale Dependency.

    Returns:
        Eine ``fastapi.responses.HTMLResponse`` mit dem gerenderten HTML.

    Raises:
        ImportError: Wenn ``fastapi`` nicht installiert ist.
    """
    try:
        from fastapi.responses import HTMLResponse
    except ImportError as exc:
        raise ImportError(
            "fastapi ist nicht installiert. Installiere es mit: pip install fastapi"
        ) from exc
    return HTMLResponse(content=self.to_html())

to_flask()

Gibt eine Flask-kompatible Response zurück.

Note

Erfordert flask als optionale Dependency.

Returns:

Type Description
Any

Eine flask.Response mit dem gerenderten HTML.

Raises:

Type Description
ImportError

Wenn flask nicht installiert ist.

Source code in htmforge\core\component.py
def to_flask(self) -> Any:  # noqa: ANN401
    """Gibt eine Flask-kompatible Response zurück.

    Note:
        Erfordert ``flask`` als optionale Dependency.

    Returns:
        Eine ``flask.Response`` mit dem gerenderten HTML.

    Raises:
        ImportError: Wenn ``flask`` nicht installiert ist.
    """
    try:
        from flask import Response
    except ImportError as exc:
        raise ImportError(
            "flask ist nicht installiert. Installiere es mit: pip install flask"
        ) from exc
    return Response(response=self.to_html(), mimetype="text/html")

to_html()

Delegiert das HTML-Rendering an :meth:render.

Returns:

Type Description
str

Den vollständigen HTML-String der Komponente.

Source code in htmforge\core\component.py
def to_html(self) -> str:
    """Delegiert das HTML-Rendering an :meth:`render`.

    Returns:
        Den vollständigen HTML-String der Komponente.
    """
    return self.render().to_html()

Element(tag, *children, **attrs)

Repräsentiert einen einzelnen HTML-Tag mit Kindern und Attributen.

Parameters:

Name Type Description Default
tag str

Der HTML-Tag-Name (z.B. "div", "span").

required
*children Child

Beliebig viele Kind-Elemente (Element, str oder None). None-Werte werden stillschweigend ignoriert.

()
**attrs object

HTML-Attribute als Keyword-Argumente.

{}
Attribute-Konventionen
  • cls wird zu class im HTML.
  • for_ wird zu for im HTML.
  • Unterstriche innerhalb von Attribut-Namen werden zu Bindestrichen konvertiert (hx_gethx-get, data_iddata-id).
  • Boolesche Attribute (True) werden als eigenständige Flags gerendert (disabled, checked ...).
  • Attribute mit Wert False oder None werden weggelassen.
Example

el = Element("div", Element("span", "Hallo"), cls="container") el.to_html() '

Hallo
'

Initialisiert ein Element mit Tag-Name, Kindern und Attributen.

Source code in htmforge\core\element.py
def __init__(self, tag: str, *children: Child, **attrs: object) -> None:
    """Initialisiert ein Element mit Tag-Name, Kindern und Attributen."""
    self._tag: str = tag.lower()
    self._children: tuple[Child, ...] = children
    self._attrs: dict[str, object] = attrs

__repr__()

Gibt eine kurze Debug-Darstellung zurück.

Source code in htmforge\core\element.py
def __repr__(self) -> str:
    """Gibt eine kurze Debug-Darstellung zurück."""
    return f"Element(tag={self._tag!r}, attrs={self._attrs!r})"

__str__()

Delegiert zu :meth:to_html.

Source code in htmforge\core\element.py
def __str__(self) -> str:
    """Delegiert zu :meth:`to_html`."""
    return self.to_html()

to_html()

Rendert das Element rekursiv zu einem sicheren HTML-String.

Alle Text-Inhalte werden durch markupsafe.escape gesichert, damit kein unbeabsichtigtes HTML injiziert werden kann.

Returns:

Type Description
str

Ein vollständiger, wohlgeformter HTML-String.

Source code in htmforge\core\element.py
def to_html(self) -> str:
    """Rendert das Element rekursiv zu einem sicheren HTML-String.

    Alle Text-Inhalte werden durch ``markupsafe.escape`` gesichert, damit
    kein unbeabsichtigtes HTML injiziert werden kann.

    Returns:
        Ein vollständiger, wohlgeformter HTML-String.
    """
    attrs_str = self._render_attrs()
    tag = self._tag

    if tag in _VOID_ELEMENTS:
        return f"<{tag}{attrs_str}>"

    inner = self._render_children()
    return f"<{tag}{attrs_str}>{inner}</{tag}>"