HTML in Python
The ht factory builds HTML Element trees in Python. Use it for fragments, email bodies, or HTML you want to return without a template file. The package root exports ht, Element, and Document.
from starlette_templates import ht, Element, Document
Building elements
Call ht.<tag>(...) with children as positional arguments and attributes as keyword arguments:
card = ht.div(
ht.h1("Hello World"),
ht.p("Built in Python."),
id="card",
classes=["container", "content"],
style={"color": "red"},
)
- A positional argument becomes a child. It can be a string, a number, another element, or a callable that returns one.
classes=accepts a string or a list. It renders into theclassattribute.style=accepts a dict. It renders into an inlinestylestring.- Every other keyword argument becomes an attribute. An underscore maps to a hyphen where needed, so
http_equivbecomeshttp-equiv.
Rendering
An Element renders to a string in four equivalent ways:
card.render() # returns Markup
card.to_string() # returns str
str(card) # returns str
ht.render_element(card) # returns str; also accepts strings, numbers, None, and callables
Element implements _repr_html_, so an element also displays as rendered HTML in a Jupyter notebook.
Documents and responses
Document is an Element subclass. It renders a full HTML document, and it builds the <html>, <head>, <title>, and <body> elements for you. The <head> includes charset and viewport meta tags.
doc = Document(
ht.h1("Welcome"),
ht.p("This is a full page."),
page_title="Home",
)
A Document is also an ASGI application, so you can return it directly from a Starlette route. It renders itself as an HTMLResponse with the status_code and headers you pass:
from starlette.applications import Starlette
from starlette.routing import Route
from starlette_templates import ht, Document
async def homepage(request):
return Document(
ht.h1("Welcome"),
page_title="Home",
status_code=200,
)
app = Starlette(routes=[Route("/", homepage)])
Composing with pipe
Element.pipe() applies a function to an element and returns the result, so you can build reusable transforms:
def add_prefix_classes(element: Element, prefix: str) -> Element:
element.add_classes(f"{prefix}-box")
return element
ht.div("Hello world").pipe(add_prefix_classes, "card")