HTML in Python f-strings
htmf is a collection of utilities for writing HTML in Python f-strings. It works great for typed UI components.
Also it is the suite of tools to boost the developer experience:
- Pylint plugin checking if expressions are HTML-safe and markup is not soup
- VS Code syntax highlighting
- HTML formatter
Installation
pip install htmf
Working example
from dataclasses import dataclass
from flask import Flask, url_for, request
import htmf as ht
from htmf import Safe
@dataclass
class Soup:
name: str
countries: list[str]
is_chunky: bool
def SoupCard(id: int, soup: Soup) -> Safe:
return ht.m(
f"""
<div class="card mb-2">
<div class="card-body">
<h5 class="{ ht.c("card-title", soup.is_chunky and 'text-danger') }" >
{ ht.t(soup.name) }
</h5>
<p class="card-text">
Countries:
{ ht.t(', '.join(soup.countries) if soup.countries else '?') }
</p>
<a href="{ ht.t(url_for('soup', id=id)) }" class="card-link">Go</a>
</div>
</div>
"""
)
def SoupsCollection(soups: dict[int, Soup], limit: int) -> Safe:
selected = list(soups.items())[:limit]
return ht.m(
f"""
<div class="container">
<h4>Showing first { ht.t(limit) } soups:</h4>
{ ht.t(SoupCard(*soup) for soup in selected) }
</div>
"""
)
def Layout(body: Safe, title: str):
bootstrap = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
return ht.document(
f"""
<!doctype html>
<html>
<head>
<title>{ ht.t(title) }</title>
<link href="{ bootstrap }" rel="stylesheet">
</head>
<body>
{ body }
</body>
</html>
"""
)
app = Flask(__name__)
# https://en.wikipedia.org/wiki/List_of_soups
SOUPS = {
1: Soup("Aguadito", ["Peru"], True),
2: Soup("Ant egg soup", ["Laos", "Northeastern Thailand"], True),
3: Soup("Beef noodle soup", ["East Asia"], False),
4: Soup("Chicken soup", [], False),
5: Soup("Tom Yum", ["Thailand"], True),
}
@app.route("/")
def index():
limit = request.args.get("limit", type=int, default=len(SOUPS))
return Layout(SoupsCollection(SOUPS, limit=limit), title="Soups list")
@app.route("/<int:id>")
def soup(id: int):
found = SOUPS.get(id)
if not found:
return Layout(ht.t("Unknown soup id"), title="Soup 404")
return Layout(SoupCard(id, found), title=found.name)
You can run the tag-soup-inspired example with flask --app=soup.py run
Few things to note:
- It's just the normal Python code without any magic
- Syntax highlighting makes the HTML look like, well, HTML
- UI is composed from components
- Components functions names are not PEP-8. It's intentional to make it look more JSX-y.
The main utilities are:
ht.m
- wraps the markup and triggers syntax highlighing, linting and formatting.ht.t
- composes text and components, HTML-escapes and concatenates themht.c
- composes classes in a way resembling the popular classnames library.Safe
- noop subclass ofstr
. Used in runtime to tell HTML-escaped from non-escaped strings. Also used in linting to prove the f-expressions are safe.
Logo by Alexander Kuliev