Skip to content

Repository files navigation


𖤐 domonic 𖤐

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!

PyPI version Downloads Python version Python package Documentation License: MIT GitHub stars


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)           # Hello

New here? Start with the examples gallery.


Recipes

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 blob

More task guides: scraping · server-side HTML · live DOM updates · parser performance · compiled SSR


Features

🏗️ 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+.


Install

python3 -m pip install domonic          # or: pip install --upgrade domonic
from domonic.html import *
print(h1("hello world"))

Want only the command line tool? Install it isolated with pipx — see the CLI guide.


HTML that is actually Python

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 identifiers

A real DOM

domonic 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)                  # attach

domonic follows the WHATWG DOM and HTML standards where practical. Full API: DOM documentation.


Query with CSS or XPath

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 href

Repeated queries over the same page are significantly faster. 🚀 Parser performance.


Parse HTML

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" chose

The 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.


Render back to markup

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 disk

Rendering is configurable through DOMConfig (GLOBAL_AUTOESCAPE, RENDER_OPTIONAL_CLOSING_TAGS, ATTRIBUTE_QUOTES, an opt-in render cache, …). See the DOM documentation.


More in domonic

Each of these is a package with its own docs

JavaScript-style APIsMath, 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)

JavaScript documentation

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>

Style documentation

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 list

diffdom documentation

dQuery — 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")

JSON documentation

Animation / tweening
from domonic.lerpy.tween import Tween
from domonic.lerpy.easing import Linear

Tween({"x": 0}, {"x": 10}, 6, Linear.easeIn).start()

tween documentation

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


Command line

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 project

Full flag reference: CLI guide.


Server-side HTML

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 &amp; Bob</p></div>

Supported syntax, caching, route integration and how the compiler works are in the compiled-rendering guide.


Examples & projects

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

Documentation

📚 domonic.readthedocs.io — API coverage, package guides and less common functionality · Release notes


Development & contributing

Contributions are welcome — fork, branch, add or update tests, open a PR.

python3 -m pip install -r requirements-dev.txt
make test                 # or: pytest tests

The 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

About

Python DOM and HTML toolkit - generate, parse and manipulate HTML/XML/SVG with CSS selectors, XPath, Web APIs and JavaScript-like utilities.

Topics

Resources

Contributing

Stars

162 stars

Watchers

5 watching

Forks

Releases

Packages

Used by

Contributors

Languages