𖤐 domonic 𖤐
The browser DOM, in Python.
Generate HTML. Parse real pages. Query with CSS or XPath. Manipulate a browser-style DOM.
Learn real HTML, DOM and JavaScript-style APIs from Python code that uses the same names and ideas.
Then render it, serve it, scrape it, transform it, or do something strange with it.
domonic is a pure-Python DOM toolkit inspired by the browser platform.
It gives you one document model for creating, parsing, querying, traversing, manipulating and rendering markup.
Whether you are coming from Python and learning the web platform or coming from
JavaScript and working in Python, domonic keeps the browser's names and
patterns — querySelectorAll(), appendChild(), textContent, Array.map(),
Promise, fetch — so the concepts carry across in both directions.
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>
But generating HTML is only the beginning.
heading = page.querySelector("h1")
heading.textContent = "Hello, DOM!"
for link in page.querySelectorAll("a"):
print(link.href)
The same kind of DOM can also come from parsed HTML.
from domonic import domonic
document = domonic.parseString("""
<html>
<body>
<h1>Hello</h1>
<a href="/docs">Documentation</a>
</body>
</html>
""")
print(document.querySelector("h1").textContent)
Create it. Parse it. Query it. Change it. Render it.
That is the central idea: you can learn browser HTML, DOM traversal, CSS selectors and JavaScript-like programming without leaving Python.
Or, coming from JavaScript, you can use Python without giving up the DOM-shaped tools you already know.
Copy-paste recipes
Scrape links like Beautiful Soup, keep a real DOM
from domonic.bs4 import BeautifulSlop
soup = BeautifulSlop("<main><a href='/docs'>Docs</a></main>", "html.parser")
for link in soup.find_all("a", href=True):
print(link.text, link["href"])
# Same object, still domonic:
print(soup.querySelector("a").getAttribute("href"))
Build a server-side component
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 large HTML responses
from fastapi.responses import StreamingResponse
from domonic.html import body, html, table, td, tr
def rows():
for index in range(50000):
yield tr(td(f"Row {index}"), td(f"Data {index}"))
page = html(body(table(rows())))
return StreamingResponse(page.stream(), media_type="text/html")
Sanitize user HTML
from domonic.webapi.sanitizer import Sanitizer
clean = Sanitizer().sanitizeToString(
'<p onclick="bad()">Hello <script>bad()</script></p>'
)
print(clean)
Send a minimal DOM patch
from domonic.diffdom import DiffDOM
from domonic.html import div, p
old = div(p("Version one"))
new = div(p("Version two"))
changes = DiffDOM().diff(old, new)
print(changes)
More focused guides:
Why domonic?
Python already has HTML generators, parsers and XML libraries.
domonic is interested in something broader:
What if Python had a practical, browser-flavoured document platform?
That makes it useful both as a production toolkit and as a learning bridge. A
Python developer can build pages with div(), inspect them with
querySelector(), move nodes with appendChild(), parse URLs with URL(),
and use familiar JavaScript collection methods without switching
mental models every five minutes.
For JavaScript developers, domonic makes Python feel less alien: selectors, nodes, events, URL parsing, JSON helpers, timers, promises, fetch-style APIs and DOM mutation all live behind names that already mean something.
So the project brings together:
| 🏗️ 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 |
| 📥 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 |
Python 3.10+.
Install
python3 -m pip install domonic
Upgrade:
python3 -m pip install --upgrade domonic
For the domonic command line tool, pipx keeps the executable isolated
and on your shell path:
brew install pipx
pipx ensurepath
pipx install domonic
domonic -x https://example.com '//title'
Then:
from domonic.html import *
print(h1("hello world"))
HTML that is actually Python
HTML elements are ordinary Python objects.
The tag names are the HTML names. The attribute names are the HTML names with a Python-friendly leading underscore where needed. That means examples often 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"
)
print(card)
Attributes are prefixed with _ to avoid collisions with Python keywords:
label("Email", _for="email", _class="label")
<label for="email" class="label">Email</label>
For attributes that cannot be expressed as Python identifiers:
div(
"hello",
**{"_data-user-id": "42"}
)
A real DOM
domonic elements are more than formatted strings.
They're nodes in a document tree.
from domonic.html import *
page = html(
body(
main(
h1("Projects"),
ul(
li("domonic"),
li("Blueberry"),
li("ezcron")
)
)
)
)
print(page.querySelector("h1"))
print(page.querySelectorAll("li"))
Manipulate the tree using familiar DOM concepts:
title = page.querySelector("h1")
title.textContent = "Open source projects"
new_item = document.createElement("li")
new_item.textContent = "something new"
page.querySelector("ul").appendChild(new_item)
The project aims to follow the real platform where practical:
See the DOM documentation for the implemented API.
CSS selectors
Use browser-style selectors directly against the tree.
page.querySelector("button")
page.querySelector("#content")
page.querySelector(".active")
page.querySelectorAll("a")
page.querySelectorAll("a[rel=nofollow]")
page.querySelectorAll("a[href='#services']")
page.querySelectorAll("a[href$='technology']")
page.querySelectorAll("a[href*='github']")
for link in page.querySelectorAll("a"):
print(link.href)
XPath
XPath is available too.
From Python:
from domonic import domonic
page = domonic.parseString("<main><h1>Hello</h1></main>")
# use XPath against your document tree
Or straight from your terminal:
domonic -x https://example.com '//a'
Against a local file:
domonic --xpath-file ./page.html '//title'
Or pipe HTML directly into it:
curl -s https://example.com | domonic -x '//a' --count
Parse HTML
from domonic import domonic
page = domonic.parseString("""
<!doctype html>
<html>
<body>
<article>
<h1>Hello from HTML</h1>
</article>
</body>
</html>
""")
print(page.querySelector("h1"))
You can also load a page through the window API:
from domonic.window import window
window.location = "https://example.com"
print(window.document.title)
Pick your parser
One parser does not fit every job.
domonic lets you choose between zero dependencies, pure Python compatibility, malformed-HTML repair and high-performance native parsers.
from domonic import domonic
page = domonic.parseString("<p>Hello</p>", parser="selectolax")
page = domonic.parseString("<p>Hello</p>", parser="turbohtml")
page = domonic.parseString("<p>Hello</p>", parser="lxml_html")
page = domonic.parseString("<p>Hello</p>", parser="markupever")
page = domonic.parseString("<p>Hello</p>", parser="html5_parser")
page = domonic.parseString("<p>Hello</p>", parser="html.parser")
page = domonic.parseString("<p>Hello</p>", parser="html5lib")
page = domonic.parseString("<p>Hello</p>", parser="expat")
page = domonic.parseString("<p>Hello</p>", parser="justhtml")
The default is parser="auto", which picks the fastest installed backend that
can parse the input (trying selectolax, turbohtml, lxml_html,
html5_parser, markupever, html.parser, justhtml, then html5lib). Call
domonic.get_active_parser() afterwards to see which one ran.
Set one for your application:
from domonic import domonic
domonic.set_default_parser("html.parser")
page = domonic.parseString("<p>Hello</p>")
Parser choices
Fastest on the bundled large-page benchmark first:
| Parser | Why use it? |
|---|---|
selectolax |
Fast native HTML parsing with direct domonic DOM adaptation |
turbohtml |
Fast native WHATWG parsing with direct domonic DOM adaptation |
lxml_html |
Very fast lxml-backed parsing and direct lxml DOM adaptation |
html5_parser |
Fast HTML5 parsing through the shared lxml DOM adapter |
markupever |
Fast Rust-powered HTML repair; uses the shared lxml DOM adapter |
html.parser |
Python standard library; no extra dependency |
justhtml |
Pure-Python alternative with a direct domonic DOM adapter |
html5lib |
Pure Python and bundled with domonic |
expat |
Built into Python; useful for XML-like input |
Optional parsers require their respective packages.
Install the native parser stack like this:
python -m pip install selectolax
python -m pip install turbohtml
python -m pip install lxml
python -m pip install markupever lxml
python -m pip install html5-parser lxml
For parser details and installation notes, see the documentation.
Render it back to markup
Every element can be rendered with str():
from domonic.html import *
page = div(
h1("Hello"),
p("Rendered from a Python DOM.")
)
markup = str(page)
print(markup)
Write documents to disk with render:
render(f"{page}", "index.html")
Rendering behaviour can be configured through DOMConfig.
from domonic.dom import DOMConfig
print(DOMConfig.GLOBAL_AUTOESCAPE)
print(DOMConfig.RENDER_OPTIONAL_CLOSING_TAGS)
See the docs for all rendering options.
Browser-flavoured Python
domonic includes a large practical slice of JavaScript's familiar APIs.
from domonic.javascript import Math, Array, Date
print(Math.random())
numbers = Array(1, 2, 3)
print(numbers.splice(1))
from domonic.javascript import URL
url = URL("https://example.com:8000/blog/article#hello")
print(url.protocol)
print(url.host)
print(url.port)
print(url.pathname)
print(url.hash)
Timers are there too:
from domonic.javascript import setTimeout
def hello():
print("hello")
setTimeout(hello, 1000)
Other APIs include things such as:
String · Number · Promise · JSON · typed arrays · timers · URL helpers · global functions
This JavaScript-like layer also powers some of domonic's more unusual experiments.
Web APIs
The web platform is much bigger than the DOM.
domonic implements or experiments with Python versions of APIs including:
URLURLSearchParamsURLPattern- Fetch / XHR helpers
- Web Storage
- Cookie Store
- History
- File API
- Web Crypto
- Web Workers
- WebSocket
- Server-Sent Events
- Messaging
- Permissions
- Notifications
- Performance APIs
- Scheduler /
postTask - Sanitizer
- Compression streams
- Canvas / WebGL
- CSS font loading
- Gamepad
- Media APIs
- Import maps
- Speculation rules
- Custom elements
- Shadow DOM
- Mutation / tree observation
- XPath
…and more.
The README deliberately doesn't try to document all of them.
👉 Browse the domonic documentation
SVG, XML, MathML and more
The DOM isn't only HTML.
domonic can build other document types using the same object-oriented approach.
SVG
from domonic.html import *
from domonic.svg import *
icon = svg(
circle(
_cx="50",
_cy="50",
_r="40",
_stroke="green",
_fill="yellow"
),
_width="100",
_height="100"
)
print(icon)
There is also support for:
- XML
- MathML
- RSS
- Atom
- sitemaps
- ODF
- A-Frame
- X3D
- custom elements
See the documentation for the individual packages.
Style elements from Python
DOM-style property access works too.
from domonic.html import *
box = div("hello", _id="message")
box.style.backgroundColor = "black"
box.style.fontSize = "12px"
print(box)
<div id="message" style="background-color:black;font-size:12px;">hello</div>
dQuery
Yes, there is also a jQuery-inspired API.
Because apparently implementing the DOM wasn't enough.
from domonic.html import *
from domonic.dQuery import º
page = html(
body(
li(_class="thing"),
div(_id="test")
)
)
print(º("#test"))
print(º(".thing"))
Append nodes:
new_div = º('<div class="child"></div>')
º("#test").append(new_div)
dQuery is useful in its own right, but it also serves as a demanding consumer of the underlying DOM implementation.
d3-inspired utilities
domonic also contains a Python port / interpretation of useful parts of the d3 ecosystem built on top of its JavaScript and DOM layers.
from domonic.d3 import *
See the documentation and examples for current coverage.
BeautifulSlop
domonic includes BeautifulSlop, a BS4-style compatibility experiment built over the domonic parsing system.
It exists for code that wants familiar soup-like ergonomics while still landing in the domonic world.
See the documentation and examples for current compatibility.
JSON utilities
Convert Python data to JSON:
from domonic.decorators import as_json
@as_json
def response():
return {
"hello": "world",
"items": [1, 2, 3]
}
print(response())
JSON arrays can also be turned into HTML tables or CSV:
import domonic.JSON as JSON
data = JSON.parse_file("data.json")
table = JSON.tablify(data)
JSON.csvify(data, "data.csv")
And CSV can go the other way:
data = JSON.csv2json("data.csv")
Animation / tweening
There is a small tweening library too.
from domonic.lerpy.easing import *
from domonic.lerpy.tween import *
position = {
"x": 0,
"y": 0,
"z": 0
}
tween = Tween(
position,
{"x": 10, "y": 5, "z": 3},
6,
Linear.easeIn
)
tween.start()
Terminal APIs
domonic even contains Python wrappers around common command-line tools on Unix-like systems:
from domonic.terminal import *
print(ls())
print(pwd())
print(git("status"))
print(df())
Or run an arbitrary command:
from domonic.terminal import command
command.run("echo hello")
Windows users can use domonic.cmd.
These utilities are not the core reason to install domonic, but they're part of the project's broader experiment:
what familiar platform APIs become interesting when exposed naturally to Python?
Command line
domonic comes with a CLI for working with HTML without writing a script.
Install it as a standalone command with pipx:
brew install pipx
pipx ensurepath
pipx install domonic
domonic -x https://example.com '//title'
Help
domonic -h
Version
domonic -v
Query a URL with CSS
domonic -q https://example.com 'a'
domonic -q https://example.com 'a' --parser selectolax
Query a URL with XPath
domonic -x https://example.com '//a'
domonic -x https://example.com '//a' --parser selectolax
Extract text
domonic -q https://example.com 'h1' --text
Extract attributes
domonic -q https://example.com 'a' --attr href
First result
domonic -q https://example.com 'a' --first
Count results
domonic -x https://example.com '//a' --count
Local files
domonic --xpath-file ./page.html '//title'
domonic --query-file ./page.html 'a.cta' --parser selectolax
Pipes
curl -s https://example.com | domonic -x '//a' --count
cat page.html | domonic -q 'a.cta' --attr href --parser selectolax
Evaluate pyml
domonic -e 'html(head(), body(h1("hello")))'
Scaffold a project
domonic -p myproject
Choose a server:
domonic -p myproject --server fastapi
Server-side HTML
Because domonic elements are Python objects that render to markup, they work naturally in Python web applications.
The repository contains examples for frameworks including:
- FastAPI
- Flask
- Django
- Sanic
…and others.
One library, a lot of surface area
A rough map of the project:
| Area | Includes |
|---|---|
| HTML | HTML5 tag generation and rendering |
| DOM | Document, Element, Node, events, ranges, traversal, fragments, observers |
| Selectors | CSS selectors + XPath |
| Parsing | html5lib, html.parser, lxml, markupever, selectolax, turbohtml, html5_parser, justhtml, expat |
| Documents | SVG, XML, MathML, RSS, Atom, ODF, sitemaps, A-Frame, X3D |
| Web APIs | URL, storage, workers, crypto, messaging, permissions, performance and more |
| JavaScript | Array, Date, Math, Promise, timers, typed arrays and helpers |
| Experiments | dQuery, d3-inspired utilities, BeautifulSlop, diffdom |
| Utilities | JSON/CSV tools, decorators, tweening, string/number/byte helpers |
| Developer tools | CLI, terminal wrappers, project scaffolding |
Not every API is implemented to browser-complete parity.
The goal is to make useful parts of the browser and document ecosystem available naturally from Python, while continuing to move closer to the real standards.
What can you build with it?
domonic is useful anywhere you want documents as programmable Python object trees.
For example:
- server-side rendering
- static-site generation
- HTML generation
- scraping utilities
- document transformation
- HTML repair and parsing
- XML / SVG generation
- testing markup
- browser API experiments
- Python-first templating
- command-line extraction
- document diffing
- web framework responses
- programmatic sitemaps and feeds
- tools that need to both read and write HTML
The interesting part is that these don't need separate mental models.
A generated document and a parsed document can live in the same DOM world.
Examples
There are working examples throughout the repository:
👉 github.com/byteface/domonic/tree/master/examples
Some projects built with domonic:
Blueberry
A browser-based file OS and an example of building components with domonic.
ezcron
A cron viewer.
bombdisposer
A small game.
htmlx
A lightweight, low-dependency DOM-focused relative of domonic.
Documentation
The README is the tour.
The docs are the manual.
📚 domonic.readthedocs.io
Use the docs for detailed API coverage, package-specific examples and less common functionality.
Useful links:
Development
Clone the repository and install the development dependencies:
python3 -m pip install -r requirements-dev.txt
Run the test suite:
make test
Or:
pytest tests
Run an individual module:
python -m unittest tests.test_html
Coverage:
coverage run -m unittest discover tests/
coverage report
The tests are also useful as executable examples of the API.
Contributing
Contributions are welcome.
- Fork the repository
- Create a branch
- Make your change
- Add or update tests where appropriate
- Open a pull request
See CONTRIBUTING.md for more information.
Philosophy
domonic started with a simple idea:
HTML should be easy to create from Python.
That led naturally to elements.
Elements led to a DOM.
A DOM led to selectors, events, traversal and parsing.
Then came JavaScript APIs, Web APIs, SVG, XPath, dQuery, workers, URL APIs, parsers, diffing and everything else that makes documents programmable.
The project is still guided by the same question:
What would the browser platform feel like if Python could use it directly?
If that sounds useful — or just interesting — give domonic a try.
pip install domonic
⭐ If you find it useful, consider starring the project.
Documentation · PyPI · Examples · Releases
Metadata
Release files for domonic 1.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| domonic-1.6.0.tar.gz | 722.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| domonic-1.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.3 MB
Release files / domonic-1.6.0.tar.gz
| Download URL | domonic-1.6.0.tar.gz |
|---|---|
| Size | 722.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
69a64a2124b3bea0bd2e4cf16d541524d9cca801877451f83d8015546454cc87
|
|
BLAKE2b-256 checksum How to use checksums |
316204acd1884237bf8d31fad5809b90844577b42e2ce4c194470c7f1b7d514c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.12
|
Release files / domonic-1.6.0-py3-none-any.whl
| Download URL | domonic-1.6.0-py3-none-any.whl |
|---|---|
| Size | 575.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bd4f42e1e5b7536364ce3e891410a81315e96dccf8186e9891ef1bb3a774a420
|
|
BLAKE2b-256 checksum How to use checksums |
8f953379aa8a6d1bbfccf7d65cd09bb523abe4f5408aa944b39120418ab1e8d0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.12
|