pyxsd compared with lxml¶
lxml is a general-purpose XML and HTML toolkit with
Python APIs backed by libxml2 and libxslt. It provides tree editing, XPath,
validation, and stylesheet execution. pyxsd focuses on XSD-driven Python
data: it compiles schema declarations into classes and binds documents to
instances of those classes.
Choose pyxsd when the schema should define your application objects and their validation, extensions, and exports. Keep lxml when the main job is general tree processing, HTML, or an existing lxml-based pipeline. You can also use the libraries at different stages of an application. pyxsd does not depend on lxml or use its validator internally.
At a glance¶
Task or property |
pyxsd |
lxml |
|---|---|---|
Primary model |
XSD-derived Python classes and a bound |
|
XSD validation |
XSD 1.1 default and explicit 1.0 mode; see Supported features |
|
Typed values |
XSD datatype classes and schema-derived restrictions |
|
Application methods |
Mixins selected from schema declarations at compile time |
Custom element classes and class-lookup schemes |
Queries |
|
ElementPath, XPath 1.0, compiled XPath, and Python extension functions |
Validation findings |
Report attached to the document, stable codes, phase tags, optional occurrence locations |
Validation booleans or exceptions; structured error logs with source/error details |
Python data output |
Dict/JSON export; optional XSD-derived Pydantic and Arrow/Parquet projections |
Application-defined extraction from |
Large instance documents |
Full bound instance tree |
|
Other document tools |
XSD validation and Python callable transforms |
HTML APIs, DTD/Relax NG/ISO Schematron validation, XSLT 1.0, canonical XML |
Installation |
Pure-Python base; optional extras may contain compiled packages |
Compiled extension and native libraries; binary wheels available |
These are interface and scope differences, not throughput or memory measurements. The separate pyxsd compared with libxml2 probe concerns one native validator build, not current lxml releases. See lxml’s standards overview for its supported XML languages.
Where pyxsd helps¶
From a schema to application data¶
In lxml.etree, schema validation checks a tree but does not turn its text
into schema-derived Python values. With the trusted input files from
Quickstart and lxml installed (python -m pip install lxml), a
validation-and-extraction step looks like this:
from lxml import etree
validator = etree.XMLSchema(file="inventory.xsd")
tree = etree.parse("inventory.xml")
validator.assertValid(tree)
ns = {"i": "urn:inventory"}
quantity = int(tree.findtext("i:item/i:quantity", namespaces=ns))
With pyxsd, the binding step already applies the schema’s declared datatype:
from pyxsd import ParseModes, Schema
schema = Schema.compile("inventory.xsd", mode=ParseModes.NAMESPACED)
schema.require_valid()
document = schema.parse("inventory.xml")
document.require_valid()
item = document.find("i:item", namespaces={"i": "urn:inventory"})
quantity = item.quantity
Both produce a quantity equal to 4. In pyxsd, quantity uses the XSD
positiveInteger datatype, and the same approach applies across the
schema’s declarations. You avoid writing a separate text-to-value mapping
for each field. Runtime class generation needs no source-generation step;
optional Type stubs for generated binding classes describe the classes for static tools.
Add behavior where the schema defines it¶
pyxsd turns XSD type extension into Python inheritance and lets you attach
mixins to schema-declared types. Application methods then live on the bound
objects, and a __pyxsd_validate__ hook contributes findings to their
document’s report. Selection can use a type, namespace, declaration path,
or predicate. See Mixin extensions.
lxml also supports custom element classes. Its class lookup mechanisms associate Python classes with XML elements; they do not compile an XSD type hierarchy into application classes for you.
Carry the schema into downstream tools¶
pyxsd’s optional integrations derive Pydantic models and Arrow record schemas from XSD, including before any XML records are present. Record projections can write Parquet and include ancestor/root context in selected columns. This is useful when the same schema must drive both XML checks and the declared shape of application output. See Optional Pydantic and Arrow integrations for supported shapes and fidelity limits.
The report also stays with the bound Document: queries select the objects
your functions consume, occurrence locations help identify findings, and
revalidate() returns a freshly checked tree after edits. Lax retention
can preserve more data for repair while leaving validation findings visible.
See Validation and Parse modes and binding policies.
How lxml.objectify differs¶
lxml.objectify provides attribute-style child access,
typed data elements, and normal Python operations on values. It can also
parse with an XMLSchema validator.
Objectify chooses data element classes using Python type annotations,
xsi:type hints, and text-value inference. Structural elements use a tree
class. Passing a schema validates the XML; it does not generate classes
from each XSD complex type or use declared simple types as a general binding
map. pyxsd instead builds its classes from the compiled schema and uses
those declarations when binding. Prefer it when schema-derived types,
inheritance, and compile-time extensions should define the object model.
Where lxml is a better fit¶
General XML and HTML processing. lxml has HTML parsing and manipulation, comment and processing-instruction nodes, and canonical serialization. pyxsd’s bound tree targets schema-defined data and does not preserve every source construct. Its mixed text can survive writing, but structured exports have narrower limits; see Data model.
Incremental processing. lxml’s parser APIs include
iterparse, pull/feed parsing, and target callbacks. Releasing processed elements can reduce retained memory;iterparsealone does not guarantee bounded use. pyxsd builds a full instance tree and allows only one parse at a time per compiledSchema.Other XML languages and existing assets. lxml supports DTD, Relax NG, and ISO Schematron validation, plus XPath 1.0 and XSLT 1.0 with Python extensions. pyxsd’s validation language is XSD and its transforms are Python callables. See lxml’s validation and query/stylesheet documentation for those APIs.
Native-library integration. Keep lxml when your application depends on its element APIs, custom resolvers, catalogs, or other libraries that consume lxml trees. pyxsd’s bound nodes are a different object model, not drop-in
etreeelements.
Use both with an explicit boundary¶
An application can use lxml for general document work and pyxsd for the XSD-driven data stage. Serialize XML at that boundary, then compile/parse with pyxsd through a supported byte stream or file input. This is an application pipeline, not a built-in lxml adapter: parsing again costs time and memory, and bound output is not a byte-for-byte copy of its source.
Apply input protections at each stage. lxml exposes parser options, resolvers and catalogs, and stylesheet access controls; capabilities and defaults depend on its version and native build. pyxsd has opt-in parser profiles, structural limits, and resource policies described in XML security and untrusted input. Neither a valid document nor a local-only loader establishes a security boundary by itself.
Native engine evidence¶
The page below records the scope and results of a dated libxml2 C-API probe. lxml may bundle a different libxml2 version and adds its own parsing, resource, and error-handling behavior, so those results must not be read as lxml benchmark or conformance results.
See also¶
Why pyxsd: reasons to choose the schema-driven workflow.
pyxsd compared with xmlschema: another Python XSD processor and its converters.
Quickstart: runnable pyxsd validation, query, and edit examples.
Supported features: XSD coverage and known gaps.