Why pyxsd

pyxsd is built for applications that need to work with schema-defined XML as Python data. Compile an XSD once, then parse documents into typed objects, inspect validation findings, and use the same bound tree for queries, edits, and transforms. The schema supplies the structure; your Python code supplies the application behavior.

Let the schema build the classes

Schema.compile() creates Python classes in memory from XSD declarations. Schema extensions become Python inheritance, declared elements and attributes get descriptors, and simple values use XSD datatype classes. You can reuse that compiled schema across documents without a code-generation command, generated modules to check in, or a second model definition to keep in sync with the XSD.

This matters when you consume upstream schemas, handle several schema versions, or want to start processing a new vocabulary immediately. Recompile the chosen schema to build its bindings. Changes can still require changes to application code, but there is no generated-source refresh step.

For editor support, Schema.stub() emits optional .pyi descriptions of the runtime classes. Stubs supplement the bindings; they do not implement them. See Type stubs for generated binding classes for typing limits and Architecture for the compiler model.

Validate XSD 1.1 with published evidence

pyxsd defaults to XSD 1.1, including assertions and conditional type assignment, and offers an explicit XSD 1.0 mode. You can use those schema rules in the same Python workflow as the bindings. See Supported features for the implemented constructs and remaining gaps.

The W3C test-suite results are published from reviewed baselines:

Raw checked-case agreement for pyxsd against the pinned main suite.xml corpus:

Profile

Passed

Checked

Agreement on checked

XSD 1.1

41,530

41,605

99.8197%

XSD 1.0

39,306

39,438

99.6653%

Checked cases are pass + fail; agreement is 100 * pass / (pass + fail). Main-suite cases only: extra-suite.xml is excluded, and no combined XSD 1.0/1.1 rate is reported. See XSTS methodology and residuals.

These figures measure validity-verdict agreement, not certification or complete standards coverage. XSTS methodology and results identifies the pinned corpus, non-verdict outcomes, residual failures, and reproduction commands, so you can inspect the evidence behind the result.

Keep validation and application work together

A parsed Document carries both its bound tree and a ValidationReport. Recoverable findings accumulate with stable codes and schema/instance phase tags. Call require_valid() where your application needs to reject errors, or inspect the report to decide what to repair. Unreadable or malformed XML can still stop a run before a document is returned.

Binding retention is a separate choice. Lax retention can keep invalid lexical values as raw text and undeclared content as generic nodes while validation findings remain visible. That is useful for import review and repair tools: inspecting the bad data need not mean suppressing its errors. Retained trees are best effort, not a lossless source copy. See Parse modes and binding policies.

Findings with source locations identify indexed occurrences, so repeated siblings can be distinguished even when their values are identical. Document.locate() connects a finding to a bound node or its source-only occurrence when available. Location precision is explicit; missing evidence is not filled with guessed line numbers. See Validation.

Query and extend the objects you already have

find(), findall(), and xpath() return the original bound nodes for element results. You can select an inventory item, read its typed quantity, and pass that object to ordinary Python functions. Callable transforms work on the same model; after editing, revalidate() returns a freshly checked document for writing. The Quickstart walks through that complete path.

Mixin extensions add your methods to selected schema-generated classes at compile time. A __pyxsd_validate__ hook can contribute application findings to the document’s report alongside XSD findings. You keep handwritten behavior separate from the schema-derived structure, without editing generated files. See Mixin extensions and Using transforms.

Import and export JSON using Parker, BadgerFish, JsonML, or pyxsd’s own formats. Native pyxsd keeps the dictionary layout; pyxsd-json uses the XSD to choose scalar, object, and array layouts while retaining namespaces, child order, and mixed text positions. Generate JSON Schema for any of these formats directly from XSD. See Data conversion for examples and limits.

Derive application models and tables from XSD

Optional Pydantic and Arrow integrations prepare models and record schemas from the compiled XSD before instance XML is available. An empty record selection still has a declared Arrow schema; column types do not depend on which values happen to appear in the first file.

Use Pydantic snapshots for Python/JSON application boundaries, or select records for Arrow tables and Parquet. Named columns can include values from the record’s ancestors or document root, such as an order number beside each line item. These are explicit projections with supported-shape limits, not lossless XML codecs or substitutes for full XML validation. See Optional Pydantic and Arrow integrations and Data workflow examples.

Use it in Python, pipelines, and agent tools

The base library is pure Python with two dependencies, elementpath and defusedxml; it needs no compiled extension. Optional integrations bring their own dependencies. The CLI exposes validation, transforms, and stub emission, while the optional local MCP server adds schema exploration, source-linked diagnostics, queries, and exports for agent clients. See Command-line reference and MCP integration.

Choose for your workflow

Choose pyxsd when you want the XSD to drive a Python object model and the work that follows validation. Start with Quickstart, or see how pyxsd compares with xmlschema, lxml, and libxml2.

Check the boundaries before adopting it: pyxsd builds a full instance tree, parses one document at a time per compiled Schema, and requires a schema for Python/JSON-to-XML conversion. Some parsed complex-child descriptors require document queries. Namespace-aware binding is the default with ParseModes.NAMESPACED; explicit ParseModes.STRICT, ParseModes.LAX, or --namespaces legacy restores local-name matching. For untrusted input, select parser protections and resource rules explicitly using XML security and untrusted input; local-only loading is not filesystem confinement.