Architecture¶
The class hierarchy is small on purpose. Five classes carry the entire model.
classDiagram
class Tag {
+name: str
+children: list
+attributes: dict
+classes: set
+append(child)
+to_string(pretty)
+iter_string()
+iter_lines()
+iter_chunk()
}
class TagInstance {
+__tag_name__
+__void__
+__escaped__
}
class Fragment {
rendered without wrapper
}
class Raw {
rendered verbatim, no escape
}
class Page {
+PREAMBLE: doctype
+head
+body
+html
+to_html5(pretty)
}
Tag <|-- TagInstance
Tag <|-- Fragment
Fragment <|-- Raw
Page o-- Tag : composes
The five classes¶
Tag¶
The base class. Stores a tag name, children, attributes, classes,
and two flags: _void (no closing tag, no children allowed) and
_escaped (HTML-escape string children/attribute values).
You can construct a Tag directly, but in practice you don’t —
everything routes through the :data:html factory.
TagInstance¶
A Tag subclass with element-specific defaults baked into class
attributes (__tag_name__, __void__, __escaped__,
optionally __default_children__ and __default_attributes__).
Each named tag — div, br, script, anything you ask for — is a
dynamically created TagInstance subclass cached by lru_cache so
that html.div always returns the same class.
Fragment¶
Fragment is a Tag with an empty name and overridden
_format_tag_open / _format_tag_close that emit nothing. It
renders its children inline with no surrounding markup. Equivalent
to React’s fragment.
Raw¶
Raw is a Fragment that disables escaping for its single
content string. Use it when you have pre-rendered HTML you want to
splice in unmodified.
Page¶
A small composition helper. Holds a head <head> tag, a body
<body> tag, and an <html> root that contains both. The
PREAMBLE class attribute is the DOCTYPE — override it for non-
HTML5 documents.
How html.div(...) actually works¶
from tagz import html, TagInstance, Tag
# `html.div` is a CLASS, not a function.
assert isinstance(html.div, type)
assert issubclass(html.div, TagInstance)
assert issubclass(html.div, Tag)
# Calling it produces an instance whose tag name comes from the
# class attribute __tag_name__.
inst = html.div("hi")
assert inst.__tag_name__ == "div"
assert inst.name == "div"
# Two attribute lookups return the same cached class.
assert html.div is html.div
Why a dataclass with slots=True?¶
Tag uses @dataclass(slots=True) mostly for memory efficiency
in trees with many small nodes (think CSV → 10k rows × N cells).
Slots also stop accidental attribute typos from silently succeeding.
What’s not here¶
No DOM-style parent links. Children know nothing about who contains them. This keeps trees cheap to copy and impossible to cycle.
No event system.
tagzis rendering-only.No async surface in
tagzitself. The sync core stays dependency- free ofasyncio. The async mirror lives in a separate module — see Async and tagz.