Skip to main content

leptris (Python) — lxml-shaped bindings for libleptris

leptris wraps the libleptris C API (XML 1.0 parsing, XPath 1.0) using cffi in ABI mode, with a required C accelerator for Element allocation and the hot accessors (tag, text, attrib, get, indexing, sibling navigation and plain-path XPath evaluation). Wheels ship it compiled; sdist builds require a C compiler.

The pinned libleptris version lives in libleptris-version.txt (lockstep releases); CI builds it from the release tarball. The binding loads the shared library from LEPTRIS_LIB_PATH or the loader path.

Requirements

  • Python 3.9+
  • cffi (installed automatically)
  • libleptris 1.9.3+ as a shared library (1.9.1 has an options-struct ABI break — leptris/leptris#568)
  • libleptris as a shared library (libleptris.dylib / .so / .dll) on the loader path, or pointed to by LEPTRIS_LIB_PATH (which must name the library file — the loader dlopens it verbatim). For a development checkout:
cmake -B build -S /path/to/leptris -DLEPTRIS_BUILD_SHARED=ON
cmake --build build --target leptris_shared
export LEPTRIS_LIB_PATH=/path/to/leptris/build/src/libleptris.dylib

Quick start

from leptris import fromstring, tostring

root = fromstring("<library><book id='1' lang='en'>Ulysses</book></library>")

root.tag                                # "library"
root[0].get("id")                       # "1"
root[0].attrib                          # {"id": "1", "lang": "en"}
root[0].text                            # "Ulysses"

root.xpath("count(//book)")             # 1.0
[b.text for b in root.findall("book")]  # ["Ulysses"]

tostring(root[0], encoding="unicode")   # "<book id=\"1\" lang=\"en\">Ulysses</book>"

Documents own the tree; use the context manager or close():

from leptris import parse

with parse("catalog.xml") as doc:
    doc.xpath("//book[@lang='en']")

Namespaces, variables, canonical XML and streaming:

root.xpath("//x:item", namespaces={"x": "urn:ex"})
root.xpath("//book[@id=$id]", variables={"id": "2"})
c14n(root, exclusive=True)

from leptris import sax
sax.parse(xml, handler)                       # one-shot
with sax.StreamingParser(handler) as parser:  # push, constant memory
    parser.feed(chunk, final=last)

XSLT and XPath version support

leptris.XSLT(stylesheet) compiles once, applies to any Document, and returns a Document; leptris.XPath(expression) compiles an XPath for repeated evaluation. Which language constructs work is decided by the engine — the matrix below is measured against libleptris 1.9.32 (audited through this binding; the upstream gap ledger is leptris/leptris#685).

language status notes
XSLT 1.0 full libxslt conformance suite 205/205 upstream; EXSLT math/set/str/date included
XSLT 2.0 partial ✓ for-each-group, analyze-string + regex-group(), xsl:number formats, xsl:assert, xsl:sequence (multi-item), xsl:perform-sort · ✗ xsl:function, tunnel parameters, shadow attributes, xsl:result-document, @separator
XSLT 3.0 increments ✓ try/catch (with $err:description), accumulator (gated by xsl:mode use-accumulators), iterate + break + on-completion, on-empty, evaluate, grouping, modes, where-populated, on-non-empty, next-match, fork, @start-at, composite keys, tunnel params, copy/@select, xsl:namespace, xsl:document, @default, merge, result-document (writes the href target), character maps · ✗ package, xsl:map, xsl:evaluate with-params, next-iteration chaining
XPath 1.0 full complete core function library
XPath 2.0 grammar complete ✓ quantified (some/every), set algebra (union/intersect/except), node comparisons (is, <<, >>), (), ends-with, deep-equal, value comparisons, cast/castable/treat/instance of, function items + HOFs (1.9.73+) · function slices by group (sequences, regex, math:, strings/QNames/URIs, dates, xs: constructors); remaining: format-date/format-time, JSON, map:/array: constructors · format-number works in plain XPath since 1.9.80
XPath 3.1 lane 0 ✓ let, simple map !, arrow =>, string concat || — through both XPath() and XSLT · ✗ function items, inline functions, maps, arrays, string constructors
RELAX NG core subset leptris.RelaxNG(schema) / .from_file(path) — compile once, .validate(doc) → bool with Jing-shaped .error_log; element/attribute/text/data/value/choice/group/interleave/repeats/ref — libleptris 1.9.115+ (#878)
XQuery 1.0 core + 3.x increments leptris.XQuery(query) — FLWOR (for/let/where/order by/return/group by, positional at, tumbling/sliding windows), prolog (declare variable/namespace/function local:*), constructors, doc()/collection(), try/catch, typeswitch — libleptris 1.9.68+; 3.1 remainder (maps/arrays, modules) tracked in #684

XQuery 1.0 core is a first-class API since libleptris 1.9.64:

from leptris import Document, XQuery

with Document.parse("<r><item v='1'>alpha</item><item v='5'>beta</item></r>") as doc:
    XQuery("for $i in //item where $i/@v > 1 return string($i)")(doc)  # ['beta']
    XQuery("declare variable $n := 3; <out>{$n * 2}</out>")(doc)       # '<out>6</out>'
    XQuery("declare function local:dbl($x) { $x * 2 }; local:dbl(4)")(doc)  # 8.0

XPath 3.1 composition and XSLT 3.0 instructions flow through the existing API with zero binding change:

from leptris import Document, XPath, XSLT, tostring

with Document.parse("<r><item v='1'>alpha</item><item v='5'>beta</item></r>") as doc:
    doc.getroot().xpath("let $n := //item[2] return $n/@v")      # ['5']
    doc.getroot().xpath("(//item ! string(.)) => count()")        # 2.0

    style = XSLT("""<xsl:stylesheet version="3.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:mode use-accumulators="depth"/>
      <xsl:accumulator name="depth" initial-value="0">
        <xsl:accumulator-rule match="*" phase="start" select="$value + 1"/>
        <xsl:accumulator-rule match="*" phase="end" select="$value - 1"/>
      </xsl:accumulator>
      <xsl:template match="/"><o>
        <xsl:iterate select="//item">
          <i d="{accumulator-before('depth')}"/>
        </xsl:iterate>
      </o></xsl:template>
    </xsl:stylesheet>""")
    print(tostring(style(doc), encoding="unicode"))
    # <o><i d="2"/><i d="2"/></o>   # items sit at depth 2 (r → item)

Unsupported constructs fail at XSLT() compile time or at evaluation with LeptrisError — except two instructions that still produce empty output instead of an error (xsl:map/xsl:map-entry, and xsl:value-of/@separator is ignored); tracked in the #685/#690 ledgers above.

HTML parsing

leptris.html (libleptris 1.9.75+) — tolerant HTML in the shape of lxml's etree.HTMLParser: implied end tags, void elements, case-insensitive names, unquoted attributes, the named-entity table, and a synthesized <html>/<head>/<body> wrapper:

from leptris import html

r = html.fromstring("<p>hello <b>world")   # <html><head/><body><p>hello <b>world</b></p></body></html>
with html.document("<td>c") as doc:
    doc.xpath("count(//td)")               # 1.0

Since libleptris 1.9.76 the output is byte-exact with lxml's etree.HTMLParser (minimized attributes are empty strings; no empty <head/> is emitted) — leptris/leptris#813. One known divergence (libleptris 1.9.104+): a leading script/style run stays in body where lxml's libxml2 lifts it to head (leptris/leptris#659).

Two modes (libleptris 1.9.104+): the default html4 keeps this compatibility shape; mode="whatwg" selects the WHATWG-conformant engine (leading script/style/noscript/template runs lift into the implied head — not lxml byte-compatible).

Migrating from lxml

lxml leptris Notes
etree.fromstring / etree.XML fromstring / XML
etree.parse parse paths and file-likes; no URLs
etree.tostring(elem, …) tostring(elem, …) bytes by default, encoding="unicode" for str
elem.tag / .text / .tail same tag uses {uri}local Clark notation; CDATA merges into text (lxml's default parser behavior)
elem.attrib / .get() / .keys() / .items() same attrib is a read-only Mapping
elem.getparent/getnext/getprevious same
elem[i], len(elem), iteration, slices same indexing is child indexing, never attribute lookup
elem.iter() / .iterdescendants() / .itertext() same elements only (ElementTree semantics); lxml's iter() also yields comments/PIs
elem.find/findall/findtext same accepts full XPath 1.0 — a superset of ElementPath — including {uri}local names
elem.xpath(expr, namespaces=…) same plus variables={…} (leptris extension)
etree.c14n / etree.XInclude c14n(…) / doc.process_xinclude()
etree.XMLSyntaxError ParseError XPath failures raise XPathError; XSLT raises XSLTError, XQuery XQueryError — all subclass LeptrisError
etree.Element, SubElement, append, set, remove not exposed libleptris has partial mutation upstream (node content setters, set_root, remove_children) — not surfaced here; build trees elsewhere
document-level comments / PIs doc.toplevel_comments() / doc.toplevel_pis() prolog then epilog; requires libleptris 1.9.3+
etree.iterparse leptris.iterparse(source, full_document=False) bounded by the largest subtree; yields ("end", element); elements borrowed until the next yield; tags resolve namespaces (Clark notation, libleptris 1.9.4+). Truncated or malformed input raises ParseError (both modes, libleptris 1.9.15+). full_document=True yields every element in completion order
smart strings plain str XPath string/attribute results
elem.nsmap absent use elem.namespace / elem.prefix and xpath(namespaces=…)
etree.XPath compiled objects leptris.XPath(expression) compile once, evaluate many; contexts, namespaces, and variables supported
etree.XSLT leptris.XSLT(stylesheet) compile once, apply to any Document — see the version support matrix above
parser options (resolve_entities, …) absent libleptris 1.2.0 has no per-parse options
elem.sourceline same requires libleptris 1.3.0+
undeclared XPath prefix raises in lxml evaluates to an empty nodeset here
ATTLIST default attributes applied by lxml's default parser excluded by default (ElementTree-like; XML 1.0 §5 permits either) — Document.parse(xml, attribute_defaults=True) opts in
declared non-UTF-8 bytes (UTF-16, latin-1, …) auto-detected auto-detected — declared encodings route through the converter, others retry on failure (libleptris 1.9.15+)
parser options (remove_blank_text, …) etree.XMLParser(remove_blank_text=True) Document.parse(xml, remove_blank_text=True) — ~35% faster on pretty-printed input; also attribute_defaults=True, recover=True

Layout

  • leptris/_ffi.py — cdef mirror of the public headers + loader (the only place libleptris is declared)
  • leptris/_engine.py — the compile-once lifecycle base shared by the compiled language objects (XSLT, XQuery, future RelaxNG)
  • leptris/_results.py — the result model: engine results to Python values (nodeset wrapping, scalar conversion), shared by every evaluation surface
  • leptris/_leptrisaccel.c — the C accelerator (abi3): allocates Elements and runs the hot accessors, subtree iteration, the parse and serialization seams, and the per-document element registry; bound to libleptris by the positional protocol in element.py
  • leptris/element.py, document.py, node.py, xpath.py — the Python surface: queries, walks, documents; node.py exposes the full DOM (comments, CDATA, PIs) beneath the ElementTree shape
  • leptris/api.py — fromstring/parse/tostring/c14n/ iterparse
  • leptris/sax.py — SAX one-shot and streaming
  • tests/ — pytest suite (pytest with LEPTRIS_LIB_PATH set)
  • benchmarks/ — matrix vs lxml/ElementTree/minidom (pip install .[bench], then python -m benchmarks.matrix)

Memory model

The Document owns the whole tree and its pool. Accessor strings are copied into Python str at the boundary, so nothing depends on document lifetime after a call returns. Elements keep a reference to their Document, so the pool cannot be freed while any wrapper is alive. Prefer explicit close() / the context manager; __del__ is a refcounting safety net, not a contract. Using an element after its document is closed raises LeptrisError.

Versioning

libleptris-version.txt pins the library release the binding is built and tested against. Since 1.9.76.0 the package version is {c-full-semver}.{patch} — the pinned libleptris version plus a binding-local patch counter: lib 1.9.76 → 1.9.76.0, then 1.9.76.1 for binding-only fixes against the same pin, resetting the patch whenever the pin moves. (1.2.0–1.27.1 were the interim own-semver line.) pyproject.toml and leptris/__init__.py must agree at release time.

Publishing

Releases publish to PyPI via .github/workflows/release.yml, using PyPI trusted publishing (no stored credentials). The workflow runs on manual dispatch (ships the version in pyproject.toml) and is called by the libleptris release flow (publish: true), so every libleptris release ships the wheel.

Local development

python3 -m venv .venv
./.venv/bin/pip install --upgrade build pytest cffi
./.venv/bin/pip install -e .[test,bench]
LEPTRIS_LIB_PATH=/path/to/libleptris.dylib ./.venv/bin/python -m pytest tests/ -q

Release files for leptris 1.9.115.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for leptris 1.9.115.0
File Size Uploaded
leptris-1.9.115.0.tar.gz 77.3 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for leptris 1.9.115.0
File
leptris-1.9.115.0-cp39-abi3-win_amd64.whl CPython 3.9 abi3 Windows x86-64 Details
leptris-1.9.115.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.9 abi3 Linux glibc 2.17+ ARM64 Details
leptris-1.9.115.0-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.9 abi3 Linux glibc 2.17+ x86-64, Linux glibc 2.5+ x86-64 Details
leptris-1.9.115.0-cp39-abi3-macosx_11_0_arm64.whl CPython 3.9 abi3 macOS 11.0+ ARM64 Details
leptris-1.9.115.0-cp39-abi3-macosx_10_9_x86_64.whl CPython 3.9 abi3 macOS 10.9+ x86-64 Details

Total release size: 446.9 kB

Release files / leptris-1.9.115.0.tar.gz

Download URL leptris-1.9.115.0.tar.gz
Size 77.3 kB
Tags Source
SHA-256 checksum
How to use checksums
3578875ca1147a917380d0bc200cffe125a612a8bae5d043d297bf04cd0a79a5
BLAKE2b-256 checksum
How to use checksums
145b03a82a4cfa198b229b18a55dd2b1cbd5bcaaa9f55cae7e4df31bd8e383d2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.

Transparency log

Release files / leptris-1.9.115.0-cp39-abi3-win_amd64.whl

Download URL leptris-1.9.115.0-cp39-abi3-win_amd64.whl
Size 53.0 kB
Tags CPython 3.9 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
24f6d1e5a61b9f73775ee2d37e18c77e419f8c44b14113c717fe5e9445d7c100
BLAKE2b-256 checksum
How to use checksums
687274175603e89f535153dbc6ddeb61f9eea54e148507e91213da534802642b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.

Transparency log

Release files / leptris-1.9.115.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL leptris-1.9.115.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 109.3 kB
Tags CPython 3.9 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
ef1df2f2cf2c1756b3d859e67f0c6cf2742eadb832560a6c38b83d58d7c01f4e
BLAKE2b-256 checksum
How to use checksums
8322bd7e92ce63af3fc2e044c07c3748ce6d2e999601ee50c9a62e1de59fa5d0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.

Transparency log

Release files / leptris-1.9.115.0-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL leptris-1.9.115.0-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 105.2 kB
Tags CPython 3.9 Linux glibc 2.17+ x86-64 Linux glibc 2.5+ x86-64 abi3
SHA-256 checksum
How to use checksums
879de2f1bfe57e79f51f718b82cd72813cfed151b4ba09df18251f59f808f21a
BLAKE2b-256 checksum
How to use checksums
a316638b6505a30c6a84341e395da8252b5c8e719dbfa8419fa7792a2f53ecb6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.

Transparency log

Release files / leptris-1.9.115.0-cp39-abi3-macosx_11_0_arm64.whl

Download URL leptris-1.9.115.0-cp39-abi3-macosx_11_0_arm64.whl
Size 51.3 kB
Tags CPython 3.9 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
f49ec631c23d672143035a634a28bd3c793d75023e1089924d56f87869279e59
BLAKE2b-256 checksum
How to use checksums
dd75fe5abc22edf257e1f15f21d661fd2f8588b5e89ab06e651c9f34b1696782
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.

Transparency log

Release files / leptris-1.9.115.0-cp39-abi3-macosx_10_9_x86_64.whl

Download URL leptris-1.9.115.0-cp39-abi3-macosx_10_9_x86_64.whl
Size 50.8 kB
Tags CPython 3.9 abi3 macOS 10.9+ x86-64
SHA-256 checksum
How to use checksums
20ad6c069591fea50c32d9fec2032d8fa5e27a48c7759fccdefae984b0b926b6
BLAKE2b-256 checksum
How to use checksums
256e32b5961db78fe390411ee78b8c589c892926ced35e25a0a7ebd738a1c51e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.

Transparency log

Release history Release notifications | RSS feed

1.24.2

6 release files

1.24.1

6 release files

1.24.0

6 release files

1.23.3

6 release files

1.23.2

6 release files

1.23.1

6 release files

1.23.0

6 release files

1.22.2

6 release files

1.22.1

6 release files

1.22.0

6 release files

1.21.0

6 release files

1.20.0

6 release files

1.19.0

6 release files

1.18.0

6 release files

1.17.1

6 release files

1.17.0

6 release files

1.16.1

6 release files

1.16.0

6 release files

1.15.1

6 release files

1.15.0

6 release files

1.14.3

6 release files

1.14.2

6 release files

1.14.1

6 release files

1.14.0

6 release files

1.13.3

6 release files

1.13.2

6 release files

1.13.1

6 release files

1.13.0

6 release files

1.12.0

6 release files

1.11.1

6 release files

1.11.0

6 release files

1.10.0

6 release files

This release

1.9.115.0 This release

6 release files

1.9.0

6 release files

1.8.0

6 release files

1.7.0

6 release files

1.6.1

6 release files

1.6.0

5 release files

1.5.0

2 release files

1.4.1

2 release files

1.4.0

2 release files

1.3.1

2 release files

1.3.0

2 release files

1.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page