The browser DOM, in Python.
Generate HTML. Parse real pages. Query with CSS or XPath. Manipulate a browser-style DOM.
Use and learn real HTML, DOM and JavaScript-style APIs using Python code!
domonic is a pure-Python implementation of the browser platform: a real DOM, HTML/SVG/XML generation, multiple HTML parsers, CSS selectors, XPath, and a large slice of the JavaScript and Web API surface — all as ordinary Python objects.
from domonic.html import *
page = html(
body(
h1("Hello, World!"),
p("HTML as Python objects."),
a("GitHub", _href="https://github.com"),
)
)
print(page)
# <html><body><h1>Hello, World!</h1><p>HTML as Python objects.</p><a href="https://github.com">GitHub</a></body></html>That tree is a real DOM — query and mutate it like you would in a browser:
page.querySelector("h1").textContent = "Hello, DOM!" # mutate by selector
print([a.href for a in page.querySelectorAll("a")]) # ['https://github.com']The same kind of tree comes back from parsed HTML:
from domonic import domonic
document = domonic.parseString("<h1>Hello</h1><a href='/docs'>Documentation</a>")
print(document.querySelector("h1").textContent) # HelloNew here? Start with the examples gallery.
Scrape a page — fetch and query in one call, real DOM underneath:
from domonic import scrape
page = scrape("https://example.com")
print(page.querySelector("h1").textContent)
print([a.href for a in page.querySelectorAll("a[href]")])
# Load external CSS into document.styleSheets and attach a Window when needed.
page = scrape("https://example.com", css=True, attach=True)
style = page.defaultView.getComputedStyle(page.querySelector("body"))scrape() also takes a CSS selector=, a list of URLs, css=True to fetch and parse linked stylesheets,
attach=True to wire document.defaultView, or to="text" | "json" | "pyml" —
see the scraping guide.
Porting Beautiful Soup code? from domonic.bs4 import BeautifulSlop wraps the same nodes with
find_all, select and get_text.
Build a server-side component — functions that return DOM trees:
from domonic.html import a, article, h2, p
def card(title, body, href):
return article(h2(title), p(body), a("Open", _href=href), _class="card")
print(card("Python DOM", "Generate HTML with Python objects.", "/docs"))Stream a large response — render lazily instead of building one huge string:
from fastapi.responses import StreamingResponse
from domonic.html import body, html, table, td, tr
def rows():
for i in range(50_000):
yield tr(td(f"Row {i}"), td(f"Data {i}"))
page = html(body(table(rows())))
return StreamingResponse(page.stream(), media_type="text/html") # chunks, not one blobMore task guides: scraping · server-side HTML · live DOM updates · parser performance · compiled SSR
| 🏗️ Markup generation | HTML5, SVG, XML, MathML, RSS, Atom, ODF, A-Frame, X3D and custom elements |
| 🌳 DOM | Document, Element, Node, NodeList, fragments, ranges, events, traversal, observers, shadow DOM and more |
| 🔎 Querying | CSS selectors and XPath, index-backed for repeated queries |
| 📥 Parsing | Multiple interchangeable parser backends |
| 🌐 Web APIs | URL, URLPattern, storage, messaging, workers, crypto, performance, permissions and more |
| 🟨 JavaScript-like APIs | Array, Date, Math, String, Number, Promise, timers, typed arrays and JSON helpers |
| ⚡ CLI | Query URLs, files or piped HTML with CSS and XPath |
| 🧪 Experiments | dQuery, d3-inspired utilities, diffdom, BeautifulSlop and other browser-inspired ideas |
Not every API is browser-complete — the goal is to keep moving closer to the real
standards. Python 3.10+.
python3 -m pip install domonic # or: pip install --upgrade domonicfrom domonic.html import *
print(h1("hello world"))Want only the command line tool? Install it isolated with pipx — see the
CLI guide.
Tag names are the HTML names. Attributes are the HTML names with a leading underscore, so examples read like HTML with Python syntax:
from domonic.html import *
card = div(
h2("domonic"),
p("The browser DOM, in Python."),
a("Documentation", _href="https://domonic.readthedocs.io"),
_class="card", # _class -> class (avoids the keyword)
)
print(card)
label("Email", _for="email") # _for -> for
div("hello", **{"_data-user-id": "42"}) # attributes that aren't valid identifiersdomonic elements are nodes in a document tree, not formatted strings.
from domonic.html import *
from domonic.dom import document
page = html(body(main(
h1("Projects"),
ul(li("domonic"), li("Blueberry"), li("ezcron")),
)))
page.querySelector("h1").textContent = "Open source projects" # mutate
new_item = document.createElement("li") # create
new_item.textContent = "something new"
page.querySelector("ul").appendChild(new_item) # attachdomonic follows the WHATWG DOM and HTML standards where practical. Full API: DOM documentation.
Browser-style selectors, straight against the tree:
page.querySelector("#content")
page.querySelectorAll("a[rel=nofollow]")
page.querySelectorAll("a[href$='.pdf']")
page.querySelectorAll("article > p:first-child")
for link in page.querySelectorAll("a"):
print(link.href)XPath works too — from Python (domonic.webapi.xpath, see the
scraping guide)
or straight from the terminal:
domonic -x https://example.com '//a/@href'
curl -s https://example.com | domonic -q 'a.cta' --attr hrefRepeated queries over the same page are significantly faster. 🚀 Parser performance.
from domonic import domonic
page = domonic.parseString("<!doctype html><article><h1>Hello from HTML</h1></article>")
print(page.querySelector("h1"))To fetch and parse a live URL in one step, use
scrape().
Parsing is fast. 🚀 Pick a backend for zero dependencies, malformed-HTML repair, or raw speed:
| Backend | Notes |
|---|---|
html.parser |
Python standard library, no extra dependency |
html5lib |
Pure Python, bundled with domonic, spec-accurate tree building |
turbohtml |
Pure-Python WHATWG parser, direct DOM adaptation |
selectolax · lxml_html · html5_parser · markupever |
Native / compiled parsers via a shared adapter |
tl · reliq |
Opt-in native parsers, outside auto |
expat |
Built in, for XML-like input |
domonic.parseString(markup, parser="selectolax") # pin one
domonic.set_default_parser("html.parser") # or set a default
domonic.get_active_parser() # what "auto" choseThe default is parser="auto", which tries the fastest installed backend that
can handle the input. Install notes, the full comparison, whitespace-fidelity
details and benchmarks are in the
parser performance guide.
from domonic.html import div, h1, p, render
page = div(h1("Hello"), p("Rendered from a Python DOM."))
markup = str(page) # or: "".join(page.stream()) for chunks
render(f"{page}", "index.html") # write to diskRendering is configurable through DOMConfig (GLOBAL_AUTOESCAPE,
RENDER_OPTIONAL_CLOSING_TAGS, ATTRIBUTE_QUOTES, an opt-in render cache, …).
See the DOM documentation.
Each of these is a package with its own docs
JavaScript-style APIs — Math, Array, Date, URL, Promise, timers…
from domonic.javascript import Math, Array, URL, setTimeout
Math.random()
Array(1, 2, 3).splice(1) # [2, 3]
URL("https://example.com:8000/blog#hello").port # 8000
setTimeout(lambda: print("later"), 1000)Web APIs — fetch/XHR, storage, workers, crypto, sanitizer, streams, canvas…
from domonic.webapi.sanitizer import Sanitizer
Sanitizer().sanitizeToString('<p onclick="bad()">Hi <script>bad()</script></p>')
# <p>Hi </p>Dozens of APIs (URL, URLPattern, Web Storage, History, File API, Web Crypto,
Web Workers, WebSocket, SSE, Permissions, Performance, Scheduler, Compression
Streams, Canvas/WebGL, custom elements, Shadow DOM, MutationObserver…).
Browse the Web APIs
SVG, XML, MathML and more
from domonic.svg import svg, circle
print(svg(circle(_cx="50", _cy="50", _r="40"), _width="100", _height="100"))Also XML, MathML, RSS, Atom, ODF, sitemaps and A-Frame / X3D.
Style — DOM-style property access
box = div("hello", _id="message")
box.style.backgroundColor = "black"
box.style.fontSize = "12px"
# <div id="message" style="background-color: black; font-size: 12px;">hello</div>BeautifulSlop — a BS4-style API over domonic parsing
Familiar find, find_all, select, get_text and mutation methods, but the
objects you get back are real domonic nodes — no wrapper Tag, no second tree.
BeautifulSlop documentation
diffdom — minimal DOM patches for live updates
from domonic.diffdom import DiffDOM
from domonic.html import div, p
DiffDOM().diff(div(p("Version one")), div(p("Version two"))) # patch listdQuery — a jQuery-inspired API
from domonic.dQuery import º
º("#test").append(º('<div class="child"></div>'))It also serves as a demanding consumer of the DOM implementation. dQuery documentation
d3-inspired utilities
from domonic.d3 import *A Python interpretation of useful parts of the d3 ecosystem, built on the JavaScript and DOM layers. d3 documentation
JSON utilities — data ⇄ HTML tables ⇄ CSV
import domonic.JSON as JSON
JSON.tablify([{"id": "01", "name": "some item"}]) # -> an HTML table
JSON.csvify(data, "data.csv")
JSON.csv2json("data.csv")Animation / tweening
from domonic.lerpy.tween import Tween
from domonic.lerpy.easing import Linear
Tween({"x": 0}, {"x": 10}, 6, Linear.easeIn).start()Terminal APIs — Python wrappers for Unix commands
from domonic.terminal import ls, git
print(ls())
print(git("status"))Windows users can use domonic.cmd.
terminal documentation
domonic -q https://example.com 'a.cta' --attr href --first # CSS query a URL
domonic --xpath-file ./page.html '//title' # XPath a local file
curl -s https://example.com | domonic -q 'h1' --text # pipe HTML in
domonic -e 'html(body(h1("hello")))' # evaluate pyml
domonic -p myproject --server fastapi # scaffold a projectFull flag reference: CLI guide.
domonic elements are Python objects that render to markup, so they drop into FastAPI, Flask, Django, Sanic and others — see the servers documentation.
For views that only return HTML, @compiled makes rendering significantly
faster — every request, including the very first: 🚀
from domonic import compiled
from domonic.html import div, h1, p
@compiled
def home(name="World"):
return div(h1("Hello"), p(name))
print(home("Alice & Bob")) # <div><h1>Hello</h1><p>Alice & Bob</p></div>Supported syntax, caching, route integration and how the compiler works are in the compiled-rendering guide.
Working examples throughout the repo: github.com/byteface/domonic/tree/master/examples
Built with domonic:
- domonic-libs — extends domonic further
- myjs — a JavaScript interpreter in pure Python
- Blueberry — a browser-based file OS / component example
- ezcron — a cron viewer
- bombdisposer — a small game
- htmlx — a lightweight DOM-focused relative of domonic
📚 domonic.readthedocs.io — API coverage, package guides and less common functionality · Release notes
Contributions are welcome — fork, branch, add or update tests, open a PR.
python3 -m pip install -r requirements-dev.txt
make test # or: pytest testsThe tests double as executable examples of the API. See CONTRIBUTING.md for more.
⭐ If you find it useful, consider starring the project.
Documentation · PyPI · Examples · Releases