Basics¶
Installation¶
Install the minihtml package from PyPI:
$ pip install minihtml
or:
$ uv add minihtml
Prototypes¶
All minihtml HTML elements are created from a Prototype. A prototype is an immutable element factory that produces Elements.
You can import the prototypes for all HTML5 elements from minihtml.tags:
>>> from minihtml.tags import html, head, title
>>> html
<PrototypeNonEmpty html>
Converting an element to a string returns its HTML representation. There are several ways to produce an element from a prototype.
Calling the prototype produces a new element:
>>> elem = html()
>>> elem
<ElementNonEmpty html>
>>> print(elem)
<html></html>
Positional arguments add content to the element. Content can be other elements or strings:
>>> elem = html(head(title("hello, world!")))
>>> print(elem)
<html>
<head>
<title>hello, world!</title>
</head>
</html>
Keyword arguments are converted to HTML attributes:
>>> elem = html(lang="en")
>>> print(elem)
<html lang="en"></html>
Indexing a prototype with a string is a convenient way to set the new element’s
class
and/or id
attributes:
>>> from minihtml.tags import div
>>> elem = div["#my-id class-a class-b"]
>>> print(elem)
<div id="my-id" class="class-a class-b"></div>
Using a prototype as a context manager creates an element context: New elements created within the context are added as children to the parent element (the element returned by the context manager):
>>> with html as elem: # creates an element context with parent element `elem` (html)
... with head: # creates a nested element context
... title("hello, world!")
<...>
>>> print(elem)
<html>
<head>
<title>hello, world!</title>
</head>
</html>
As you can see in the example above, it does not matter how the elements are
created, from a context manager (with head:
) or by calling (title()
),
they are always added to the parent element of the containing context. There is
one important exception: Elements passed as positional arguments to another
element or prototype are exempt from this, so this works as expected:
>>> from minihtml.tags import p, em
>>> with div as elem:
... p(em("this em element is a child of p, not div"))
<...>
>>> print(elem)
<div>
<p><em>this em element is a child of p, not div</em></p>
</div>
Finally, you can create new prototypes with the make_prototype()
factory
function:
>>> from minihtml import make_prototype
>>> custom_element = make_prototype("custom-element")
>>> custom_element
<PrototypeNonEmpty custom-element>
>>> elem = custom_element()
>>> print(elem)
<custom-element></custom-element>
Elements¶
Elements provide the same APIs as Prototypes, but they are mutable. Calling, indexing or using an element as a context manager returns the element itself, so all operations can be chained to modify the element further:
>>> from minihtml.tags import a
>>> elem = a["repo"](href="https://github.com/trendels/minihtml")("minihtml")
>>> print(elem)
<a class="repo" href="https://github.com/trendels/minihtml">minihtml</a>
>>> with html(lang="en") as elem:
... with div["#main"]:
... p("content")
<...>
>>> print(elem)
<html lang="en">
<div id="main">
<p>content</p>
</div>
</html>
Attributes¶
When setting attributes via keyword arguments, the names of keyword arguments are modified according to these rules:
If the name is
_
(a single underscore), it is not changed.Any trailing underscores are stripped from the name.
Any remaining underscores are converted to hyphens (
-
).
Examples:
>>> print(div(_="something"))
<div _="something"></div>
>>> print(div(foo_bar="x"))
<div foo-bar="x"></div>
>>> from minihtml.tags import label
>>> print(label(for_="target")("Label"))
<label for="target">Label</label>
>>> print(div(__foo="bar"))
<div --foo="bar"></div>
Attribute values are quoted (HTML-escaped) automatically:
>>> print(div(attr='a value with "quotes" and a <tag>'))
<div attr="a value with "quotes" and a <tag>"></div>
Boolean attributes¶
A boolean HTML attribute is one that does not have a value. For example, to
mark an input field as required, you can write <input required>
. There is
also an alternative syntax, which minihtml uses: <input
required="required">
.
To set a boolean attribute, pass True
as the value. False
is also a
valid value, and causes the attribute to be omitted (this can be useful if you
set the attribute from a variable):
>>> from minihtml.tags import input
>>> print(input(required=True, disabled=False))
<input required="required">
Content¶
We have already seen that elements and strings can be passed to elements as content.
String content is also HTML-escaped automatically. To include strings as HTML,
use the safe()
helper function. Only use this if the content comes from a
trusted source, to prevent Cross Site Scripting (XSS) vulnerabilities!
>>> print(div("content with a <tag>"))
<div>content with a <tag></div>
>>> from minihtml import safe
>>> print(div(safe("<b>inline html</b>")))
<div><b>inline html</b></div>
To add text content to the parent element inside an element context, you can use the
text()
helper function (safe()
can be used in the same way):
>>> from minihtml import text
>>> with div as elem:
... text("text")
... text(" and more text")
<...>
>>> print(elem)
<div>text and more text</div>
Sometimes you might need to pass around a group of elements that do not share a
common parent element. This is called a Fragment. Fragments are created using
the fragment()
function:
>>> from minihtml import fragment
>>> f = fragment(p("one"), p("two"), "three")
>>> print(f)
<p>one</p>
<p>two</p>
three
fragment()
can also be used in an element context:
>>> with div as elem:
... fragment(p("one"), p("two"), "three")
<...>
>>> print(elem)
<div>
<p>one</p>
<p>two</p>
three
</div>