API reference

The public entry points. The metaprogramming core (generated classes, descriptors) is intentionally dynamic; type[SchemaBase] and the XMLNode XMLNode protocol are the stable seams.

Schema and documents

class pyxsd.schema.Schema(classes, components, namespace_context, mode, target_namespace, report, source, schema_context=None)[source]

A compiled XML Schema and the classes generated from it.

Parameters:
xsd_version: str

The XSD processor version this schema was compiled under (“1.0” or “1.1”). It is the declared V that vc:* selectors were tested against; the schema’s own vocabulary is not restricted by it (that is the compiler’s job, see version_gates).

classes: dict[str, type[SchemaBase]]

Generated classes keyed by declaration name (bare local names and, where the type has an expanded name, Clark names).

components: Any

The component table registered during compilation.

namespace_context: Any

Prefix-to-URI bindings captured while parsing the schema documents.

mode: Any

The binding policy the compilation ran under.

target_namespace: str | None

The main schema document’s targetNamespace, if it declared one.

report: ValidationReport

Schema-phase validation issues (composition, declaration, and derivation problems). Instance-phase issues never land here.

source: str | Path | PathLike[str] | IO[str] | None

The source the schema was compiled from, as given to compile.

classmethod compile(source, *, mode=BindingPolicy(invalid_value='drop', unresolved_type='error', undeclared_content='error', whitespace='xsd', namespaces='legacy', facets='strict'), xsd_version='1.1', namespace_schemas=None, overlay=None, namespace_context=None, schema_location_pairs=None, extensions=None, resource_policy=None, xml_policy=None, xml_limits=None, _source_purpose='schema')[source]

Compiles source into a Schema.

  • source - a schema file path or a file object open for reading. An unopenable file, malformed XML, or a document whose root is not xs:schema raise PyXSDError; schema-legality problems are collected on report instead.

  • mode - a BindingPolicy (usually a ParseModes preset). Defaults to STRICT.

  • xsd_version - the XSD processor version the document is compiled as, "1.0" or "1.1" (default "1.1"). It is the declared version vc:* conditional-inclusion selectors test against: in "1.0" mode a declaration carrying vc:minVersion="1.1" is dropped (XSD 1.1 §4.2.2). The vocabulary itself is checked separately (see pyxsd.version_gates).

  • namespace_schemas - an optional {namespace_uri: path} mapping supplying schemas for namespaces referenced by xs:import without a schemaLocation.

  • overlay - an optional overlay class file, loaded after class generation (experimental; see load_overlay_classes()).

  • namespace_context - an existing prefix-to-URI binding context for the schema documents’ bindings to accumulate into, instead of a fresh one (parse() passes the context that already holds the instance’s bindings, so one context accumulates them all).

  • schema_location_pairs - extra (namespace, location) pairs from an instance document’s xsi:schemaLocation beyond the main schema. Advisory hints: consulted in strict namespace mode only, and one that cannot be loaded is a schema-hint warning (parse() passes the instance’s pairs; a schema-only compile has none).

  • extensions - an optional Extensions registry selecting mixins to fold into the generated classes’ bases at build time (XsdType. getBaseList). Without one, class generation is unchanged.

  • resource_policy - a trusted application resource policy. Its parse() owns XML parsing, so xml_policy and xml_limits cannot be combined with it.

  • xml_policy - an opt-in XmlParserPolicy applied to the main schema and every included/imported dependency. None keeps stdlib parsing and its current DTD/entity behavior. The choice is compile- scoped: it never becomes the default for later parse() calls.

  • xml_limits - optional XmlLimits byte/depth/element budgets for the same documents.

  • _source_purpose - internal: the resource purpose the main schema document reads under. pyxsd.parse() passes "schema-hint" when the instance’s own location hint selected the schema and "schema" when the caller named it.

Use require_valid() to reject a schema whose report holds errors.

Parameters:
Return type:

Schema

require_valid()[source]

Raises pyxsd.ValidationError if the report has errors.

Return type:

None

stub(*, compact=False, component_ids=None, depth=None)[source]

Returns .pyi-style source for the classes this schema compiled.

The text is a typing surface mirroring the runtime: one class block per generated class, annotated with the classes a valid parsed document actually reads back, and the root elements as module-level class aliases. A member whose type cannot be named truthfully degrades to Any with the reason in a comment, never to a name the runtime does not use.

  • compact - emit the agent-oriented compact description instead of the full document: one line per class and member, a short alias legend, and the root elements in a trailing comment. The compact text is a description, not valid Python; the default full form is importable-quality .pyi source:

    schema.stub()              # full .pyi document
    schema.stub(compact=True)  # compact agent-oriented view
    

The CLI emits the same text: pyxsd --emit-stubs [PATH] -s SCHEMA writes the full document to PATH (stdout when PATH is omitted), and adding --stubs-compact selects the compact view.

component_ids optionally selects native declaration IDs, with depth bounding their outgoing dependencies (None follows all). Unknown IDs raise KeyError and negative depths raise ValueError. See pyxsd.introspection for IDs and declaration metadata.

Unfiltered emitting never raises for a schema that compiled and does not require a valid report; call require_valid() separately to reject a schema whose report holds errors.

Parameters:
Return type:

str

iter_parse(sources, *, errors='raise')[source]

Lazily parse explicit local sources using this compiled schema.

On first iteration, require a valid schema before consuming inputs. Default raise mode stops with pyxsd.batch.BatchParseError; report mode yields invalid documents and expected input failures. Source IDs must be unique. Internal defects always propagate. Do not parse concurrently or reentrantly on this schema.

Parameters:
Return type:

Iterator[ParseOutcome]

parse(xml, *, xml_policy=None, xml_limits=None)[source]

Binds xml against this schema and returns a Document.

  • xml - the instance document: a file path, a file object open for reading, or a parsed xml.etree.ElementTree.Element used as the document root directly. An unopenable file or malformed XML raises PyXSDError (the fatal-input rule); content problems instead land on the document’s report.

  • xml_policy / xml_limits - opt-in parser posture and structural budgets for this instance parse only. They do not inherit a nondefault policy from compile(); None keeps stdlib parsing and unlimited structure. They cannot be combined with a custom resource_policy, whose parse() is trusted application code. A pre-parsed Element is also a caller-trusted boundary, so no parser option is applied.

Each parse binds with its own fresh instance report, so a schema may serve many documents without their findings mixing: the returned document’s report merges this schema’s (frozen) compilation issues with the document’s own binding issues. Document.root is None when the document root matched no global element declaration (the unknown-root issue records why).

Parameters:
Return type:

Document

class pyxsd.document.Document(schema, root, source, report, *, provenance=None)[source]

A schema-bound instance document: the interaction hub.

Parameters:
schema: Schema

The schema the document was parsed against.

root: Any

The bound root instance, or None when binding failed (for example an unknown document root element).

source: str | Path | os.PathLike[str] | IO[str] | ET.Element | None

The source the document was parsed from, as given to parse.

locate(location)[source]

Resolve a source occurrence in this parse, not by value or local name.

Addresses are parse-revision-specific. Manually built/transformed Documents have no provenance until freshly parsed with revalidate(). Attribute lookups return the original lexical attribute value.

Parameters:

location (IssueLocation)

Return type:

NodeLookup

property report: ValidationReport

schema-phase issues, then instance-phase.

Type:

The merged report

property is_valid: bool

Whether the report holds no error-severity issues.

require_valid()[source]

Raises pyxsd.ValidationError if the report has errors.

Return type:

None

write(dest)[source]

Writes the bound tree to dest (a path or a file object).

Parameters:

dest (str | Path | PathLike[str] | IO[str])

Return type:

None

to_string()[source]

Returns the bound tree serialized as XML text.

Return type:

str

to_dict(*, typed=True, always_list=False)[source]

Returns the bound tree as a plain dict.

The export convention (child element keys, @ attributes, the $ text key, repeated children as lists) is documented in docs/data-model.md. For an element whose content admits character data (mixed content, xs:anyType, wildcard pass-through), $ holds all of its recorded character data in document order: the leading text followed by every child’s _tail_, concatenated. The dict shape cannot represent text/element interleaving; to_string() and xpath() are the faithful mixed-content views.

Parameters:
Return type:

dict

to_json(*, indent=None, **to_dict_kwargs)[source]

Returns to_dict() encoded as JSON text.

Keyword arguments are forwarded to to_dict(). Values the JSON encoder cannot represent natively (for example a decimal.Decimal) are encoded as strings.

Parameters:
  • indent (int | None)

  • to_dict_kwargs (Any)

Return type:

str

transform(fn, *args, **kwargs)[source]

Applies fn to the bound tree.

  • fn(root, *args, **kwargs) may return a new tree root (a bound SchemaBase instance), which becomes the root of a new Document against the same schema — or anything else, including None, which is returned unchanged. When a transform mutates the tree in place and returns None, the original document remains available to the caller; no new document is created.

A returned document shares this document’s report, which reflects the tree as it was parsed, not the transformed shape; revalidate() is the refresh path.

Parameters:
Return type:

Document | Any

revalidate()[source]

Re-parses the serialized tree, returning a fresh Document.

The bound tree is written out and parsed again against the same schema, so the returned document carries a fresh instance report for the tree’s current shape (transforms may have changed it). The source is carried over.

Return type:

Document

walk()[source]

Yields every node of the bound tree, pre-order.

Return type:

Iterator[Any]

xpath(expr, *, namespaces=None)[source]

Evaluates an XPath expression over the bound tree.

Returns node results as the original bound instances and passes scalar results (numbers, strings, booleans) through. The expression is evaluated with the document’s root element as the context item, so an absolute path starts /root. namespaces maps query prefixes to namespace URIs.

The query runs over a projection of the bound tree, so it sees exactly what the object model holds: descriptor write-through is reflected, text mirrors _value_ and a child’s tail mirrors its recorded _tail_, so mixed character data is queryable in document order. Parsing does not preserve comment nodes, and an element with _value_ is None projects text=None. The projection is document-faithful, not source-XPath-exact: element-only content’s recorded layout whitespace (the tails between block children) is part of the text a value query returns. See docs/data-model.md.

Parameters:
Return type:

Any

find(path, *, namespaces=None)[source]

Finds the first node matching an ElementTree path.

Unlike xpath(), this accepts ElementTree’s path subset ({uri}name qualified names, *, ., .., //). Returns the original bound node, or None. The projection contract documented on xpath() applies here too.

Parameters:
Return type:

Any

findall(path, *, namespaces=None)[source]

Finds every node matching an ElementTree path.

Like find(), but returns a list of the original bound nodes (possibly empty). See xpath() for the projection contract.

Parameters:
Return type:

list[Any]

pyxsd.schema.compile(source, *, mode=BindingPolicy(invalid_value='drop', unresolved_type='error', undeclared_content='error', whitespace='xsd', namespaces='legacy', facets='strict'), xsd_version='1.1', namespace_schemas=None, overlay=None, namespace_context=None, schema_location_pairs=None, extensions=None, resource_policy=None, xml_policy=None, xml_limits=None, _source_purpose='schema')

Schema.compile() exposed as a module-level function.

Parameters:
Return type:

Schema

pyxsd.schema.parse(xml, *, xsd=None, mode=BindingPolicy(invalid_value='drop', unresolved_type='error', undeclared_content='error', whitespace='xsd', namespaces='legacy', facets='strict'), namespace_schemas=None, overlay=None, resource_policy=None, xml_policy=None, xml_limits=None)[source]

Parses xml against its schema and returns a Document.

The schema is the one named by xsd when given; otherwise the instance’s own xsi:schemaLocation / xsi:noNamespaceSchemaLocation hints name it, resolved relative to the instance document’s directory (the working directory for stream sources). An instance that names no schema raises PyXSDError. A hint naming an absolute URI stays a URI and is never reinterpreted as a local path: a policy reads it under purpose "schema-hint" and, without a remote-capable policy, it raises ResourcePolicyError.

  • xml - the instance document: a file path, a file object open for reading, or a parsed xml.etree.ElementTree.Element used as the document root directly.

  • xsd - the schema to bind against: a path or a file object open for reading. Defaults to the instance’s schema hints.

  • mode, namespace_schemas, overlay - passed to Schema.compile().

  • resource_policy - a trusted application resource policy for both the instance and schema documents. Its parse() owns XML parsing, so xml_policy/xml_limits cannot be combined with it.

  • xml_policy / xml_limits - opt-in parser posture and structural budgets applied explicitly to the instance and to the schema compile. They never become an implicit default for later Schema.parse() calls.

Parameters:
Return type:

Document

Schema.compile (also exposed as pyxsd.compile()) builds the schema once; Schema.parse binds one instance document and returns a Document. The module-level pyxsd.parse() is the one-call convenience: it resolves the schema from an argument or the instance’s own schema hints, compiles, and binds.

Both methods accept optional xml_policy and xml_limits arguments. The policy is opt-in: None keeps stdlib parsing and unlimited structure. A compile-time policy applies to the schema and its dependencies but is not inherited by later Schema.parse calls; pass it again per instance. Neither argument can be combined with a custom resource_policy, whose parse() owns parsing. pyxsd.parse applies both values to the instance and the schema compile. See XML security and untrusted input for the profiles and limits and the section below for the reference.

Writing is deterministic: Document.write, Document.to_string, and XmlTreeWriter emit no generated timestamp, so serializing the same tree twice produces the same bytes. XmlTreeWriter.writeHeaderInfo() remains a no-op subclass hook for callers that want their own header (issue #48).

Parser and resource policies

XmlParserPolicy selects the stdlib backend (the default) or a defusedxml-backed profile. XmlLimits bounds bytes, element depth, and start-tag count. RemoteResourcePolicy is the only built-in remote XSD loader: HTTPS only, explicit origin/hint authorization, finite budgets, no persistent cache. A custom ResourcePolicy replaces reading and parsing entirely.

class pyxsd.xml_policy.XmlParserPolicy(use_defusedxml=False, forbid_dtd=False, forbid_entities=False, forbid_external=False)[source]

An immutable, opt-in XML parser posture.

The forbid flags are meaningful only with use_defusedxml=True; constructing a policy that asks for declaration denial without the defusedxml parser would silently ignore the request, so it is rejected.

Parameters:
  • use_defusedxml (bool)

  • forbid_dtd (bool)

  • forbid_entities (bool)

  • forbid_external (bool)

classmethod default()[source]

Stdlib ElementTree with today’s DTD/entity behavior and no overhead.

Return type:

XmlParserPolicy

classmethod protected()[source]

Defusedxml; forbid entity declarations and external references.

Plain DTD syntax stays allowed, so harmless internal ATTLIST defaults still apply.

Return type:

XmlParserPolicy

classmethod strict()[source]

Defusedxml; forbid DTDs, entity declarations, and external references.

Return type:

XmlParserPolicy

class pyxsd.xml_policy.XmlLimits(max_bytes=None, max_depth=None, max_elements=None)[source]

Optional structural budgets for one XML parse.

Every bound is inclusive and counted on the input as supplied: max_bytes on file/binary-stream bytes or UTF-8 encoded bytes for text-mode streams, max_depth on element nesting (the root is depth 1), and max_elements on start tags. None leaves that budget unset.

Parameters:
  • max_bytes (int | None)

  • max_depth (int | None)

  • max_elements (int | None)

class pyxsd.remote.RemoteResourcePolicy(*, allowed_origins=(), allow_remote_hints=False, allow_local_remote=False, allow_private_addresses=False, limits=None, transport=None, resolver=None)[source]

Opt-in HTTPS schema loading with per-compilation budgets and trust rules.

Selecting the policy authorizes the application’s remote root schema. Dependencies of an admitted remote document may reference the same origin as the root or the requesting document; anything else must appear in allowed_origins. An instance xsi:schemaLocation / xsi:noNamespaceSchemaLocation that names a remote schema needs allow_remote_hints=True as well. A local schema (or anonymous stream) may reference a remote dependency only with allow_local_remote=True.

Every remote read is HTTPS, address-checked, and budgeted: one response’s bytes never exceed limits.response_bytes, the whole closure never exceeds limits.closure_bytes, limits.resources unique URLs, and limits.depth dependency levels. Redirects are followed one request at a time, with the scheme, origin, and addresses revalidated before each new request, up to limits.redirects hops. Every downloaded body counts toward closure_bytes, including redirect and error responses, and limits.redirects is a closure-wide total across all resources (not a per-resource cap).

The main schema of an explicit pyxsd.compile() is the only root read. An anonymous stream has no root read, so every memory-base dependency is still a local-to-remote dependency and needs allow_local_remote=True; a stream’s document-authored remote reference can never authorize an arbitrary origin as a root or reset the closure budgets. compilation() gives each in-flight compile its own closure state (a context variable), so one policy instance can serve concurrent compiles without sharing origins or budgets. Direct read calls outside a compilation share one fallback closure and each explicit root read resets it.

Pivots are denied: a remote document may never resolve to a local path or a file:/other non-HTTPS URL, remote instance documents are refused, and a remote schema never loads a Python overlay (a <memory> schema has no local directory for a module-name overlay either).

parse always uses XmlParserPolicy.strict() and the policy’s XML limits (response_bytes for bytes, xml_depth and xml_elements for structure), so a downloaded document declaring a DTD or entity is denied. There is no persistent cache and no external-entity handler.

Once this policy is selected, transport and resolver failures abort the compile even for a document-authored xs:include/xs:import; only the no-policy path keeps the offline advisory missing-resource behavior.

resolver is consulted before every request. The transport resolves the hostname again for the connection, so this preconnection check cannot claim DNS-rebinding immunity (see the module docstring).

limits defaults to a fresh RemoteLimits; transport defaults to HttpClientTransport; resolver defaults to the system resolver.

Parameters:
compilation(*, root_read=True)[source]

Isolate the closure state of one in-flight compilation.

root_read is true when the compilation’s main schema is an explicit path or URL (so the first schema read is the authorized root) and false for an anonymous stream, whose dependencies must still authorize local-to-remote loading. Each entered scope has its own resources, bytes, redirects, depths, and root origin, so concurrent compiles on one policy do not share or reset each other’s budgets.

pyxsd.compile() calls this automatically for this policy.

Parameters:

root_read (bool)

Return type:

Iterator[None]

read(source, *, base, purpose)[source]

Read a local resource or an authorized remote schema.

Parameters:
  • source (str)

  • base (str | None)

  • purpose (Literal['schema', 'schema-hint', 'instance'])

Return type:

ResourceData

parse(data, context)[source]

Parse admitted bytes with the strict defused profile and XML limits.

Parameters:
  • data (ResourceData)

  • context (NamespaceContext)

Return type:

Element

class pyxsd.remote.RemoteLimits(response_bytes=16777216, closure_bytes=134217728, resources=128, depth=16, redirects=5, connect_timeout=10.0, read_timeout=30.0, request_seconds=30.0, xml_depth=256, xml_elements=250000)[source]

Finite budgets for remote retrieval and the XML it admits.

response_bytes bounds one downloaded resource and closure_bytes bounds all resources fetched for one compilation. resources and depth bound the dependency closure, redirects bounds the hops the caller may authorize, and the three timeouts configure connecting, each socket read, and the total request. xml_depth and xml_elements are structural limits for the strict parser that consumes a downloaded resource.

Parameters:
  • response_bytes (int)

  • closure_bytes (int)

  • resources (int)

  • depth (int)

  • redirects (int)

  • connect_timeout (float)

  • read_timeout (float)

  • request_seconds (float)

  • xml_depth (int)

  • xml_elements (int)

class pyxsd.remote.RemoteTransport(*args, **kwargs)[source]

The retrieval seam a remote resource policy calls one hop at a time.

fetch(url, *, limits)[source]

Perform one HTTPS request without following a redirect.

Parameters:
Return type:

RemoteResponse

class pyxsd.remote.HttpClientTransport(*, clock=<built-in function monotonic>)[source]

The stdlib HTTPS transport used when a policy opts into remote access.

Each call creates a fresh default TLS context (certificate and hostname verification enabled), connects with at most connect_timeout, then reads the response through a deadline-aware socket proxy. Every low-level socket read, including the status line, each header line, chunk framing, and body data, re-arms the timeout to the smaller of read_timeout and the time left before request_seconds. The deadline therefore covers connecting, the response headers, and all body reads, so a server that drips bytes cannot hold the request open indefinitely. Content-Length is never trusted; a response whose streamed bytes exceed the budget is rejected. The response and connection are closed on every outcome.

Failures that leave no usable response raise RemoteTransportError: a non-HTTPS URL, a missing host or invalid port, an oversized response, a malformed HTTP response, or an expired request_seconds deadline. DNS, TLS, and socket failures (including a read timeout shorter than the remaining deadline) propagate as the original OSError subclass. Either way the connection is already closed when the exception escapes.

Parameters:

clock (Callable[[], float])

class pyxsd.resource_policy.ResourcePolicy(*args, **kwargs)[source]

Application-controlled loading and parsing for a schema and its instances.

Source IDs are canonical strings: a local path (including a virtual path a policy defines, such as /workspace/...), an absolute remote URI, or <memory> for an anonymous stream. A dependency’s base is its including document, not its directory: a policy resolves a remote base with URI semantics and any other base with its own filesystem semantics. purpose is a ResourcePurpose: "schema" for an explicitly supplied schema, a caller’s namespace mapping, and an ordinary schema dependency; "schema-hint" for a schema an instance document selected through xsi:schemaLocation or xsi:noNamespaceSchemaLocation; and "instance" for an instance document. Parsers must populate the supplied namespace context. Anonymous streams use <memory> and have no implicit filesystem base.

read(source, *, base, purpose)[source]

Authorize and read a named resource, retaining its original bytes.

Parameters:
  • source (str)

  • base (str | None)

  • purpose (Literal['schema', 'schema-hint', 'instance'])

Return type:

ResourceData

parse(data, context)[source]

Parse admitted bytes and record namespace evidence.

Parameters:
  • data (ResourceData)

  • context (NamespaceContext)

Return type:

Element

class pyxsd.resource_policy.ResourcePolicyError[source]

A resource/parser policy refused input; never a missing-resource hint.

RemoteTransport.fetch(url, *, limits) is the retrieval seam: one HTTPS request, no redirect following. Supply transport= and/or resolver= to RemoteResourcePolicy to audit or constrain retrieval. RemoteResourcePolicy.read receives one of the ResourcePurpose values: "schema" for an explicit or mapped schema and ordinary dependency, "schema-hint" for a schema selected by the instance’s location hint, and "instance" for an instance document. A hint-selected main schema is a root read (requiring allow_remote_hints but no allowed_origins entry); an additional remote hint from a local base is also a local-to-remote read and needs both allow_remote_hints and allow_local_remote. Remote instance documents are refused.

Source spans

pyxsd.source_spans.SourceIndex indexes exact original-byte ranges for element occurrences without building another tree. It is the mechanism behind the MCP server’s source-faithful subtrees; the core library does not expose spans from Document directly. Paths use the same expanded-name and same-name-sibling addressing as ValidationIssue locations, and the whole input is parsed and policy-checked before a span is returned.

class pyxsd.source_spans.SourceIndex(source, content, *, policy=None, limits=None)[source]

Eagerly parsed index of element paths to exact source byte spans.

The constructor parses and validates the whole input once; span and extract are cheap dictionary lookups. source names the input in diagnostics. content is the original encoding; spans index its bytes, so a UTF-16 or BOM-prefixed document is sliced in that encoding rather than re-encoded. policy defaults to default() (the same declaration behavior as core parsing); limits bounds the supplied bytes, element depth (the root is depth one), and start-tag count. A document that references an entity the index cannot expand is rejected, so the index never accepts an input the semantic parser rejected.

Parameters:
span(path)[source]

Return this occurrence’s exact byte range, or None if unknown.

Parameters:

path (tuple[PathStep, ...])

Return type:

SourceSpan | None

extract(path)[source]

Return this occurrence’s original bytes, or None if unknown.

Parameters:

path (tuple[PathStep, ...])

Return type:

bytes | None

class pyxsd.source_spans.SourceSpan(start, end)[source]

A half-open byte range in the original input: content[start:end].

Parameters:

Mixin extensions

class pyxsd.extensions.Extensions[source]

A per-compile registry of mixin selectors.

Each registration method may be used as a decorator (@ext.for_type(...)), which registers the decorated class and returns it, or called directly (ext.for_type(name, mixin=Cls)). Every for_* selector compiles to a predicate over the type ER immediately; for_path is stored raw for later resolution.

Registration order is precedence: a later registration outranks an earlier one, so mixins_for() lists the most recently registered match first. Instances are standalone — no global registry state.

for_type(name, mixin=None)[source]

Registers mixin for a type by name.

name is a plain local name ("Code") or a Clark name ("{uri}Code"). Without mixin, returns a decorator that registers the decorated class.

Parameters:
Return type:

Any

for_namespace(uri, mixin=None)[source]

Registers mixin for every type declared in namespace uri.

An empty or None uri selects declarations that have no namespace.

Parameters:
  • uri (str | None)

  • mixin (type | None)

Return type:

Any

for_kind(kind, *, derived_from=None, mixin=None)[source]

Registers mixin for types of kind (simpleType or complexType).

With derived_from, only types whose superClassNames list the given base (matched by local or expanded spelling) qualify.

Parameters:
  • kind (str)

  • derived_from (str | None)

  • mixin (type | None)

Return type:

Any

for_path(path, mixin=None)[source]

Registers mixin for the type of the element at an instance path.

path names element declarations from the document root down ("/ClinicalDocument/recordTarget/patient"); every segment is a local element name. The final segment’s declaration contributes the selected type — its inline anonymous type when one is declared, else the referenced global type — matched later by ER identity, never by the anonymous type’s synthesized (and collidable) name. Resolution happens at class-building time against the composed declaration tree; a segment matching no declaration fails the compile loudly. Traversal stays within each element’s own content model — the base chain of an extension-typed element is not followed, so a segment naming a base-declared child dangles loudly too.

Parameters:
Return type:

Any

where(predicate, mixin=None)[source]

Registers mixin for every type ER satisfying predicate.

Parameters:
Return type:

Any

mixins_for(er, schema=None)[source]

The mixins matching the type ER er, in precedence order.

The first element is the highest-precedence (most recently registered) match; a mixin registered by several matching selectors appears once, at its highest-precedence position.

Raw path entries resolve against the owning schema ER’s composed declaration tree — class building happens after schema composition, so the tree is complete then — once per compile (the resolutions are cached per schema, and a registry reused across compiles resolves the same path again against each new tree). Without schema (an ER-tree-only caller) path entries stay skipped and silent.

Parameters:
Return type:

tuple[type, …]

Extensions (also exported as pyxsd.Extensions) is a per-compile registry: register mixins with its for_type / for_namespace / for_kind / for_path / where selectors — each usable as a decorator or called directly with mixin= — and pass the registry to Schema.compile(..., extensions=...). Registration is compile-time only; the registry’s mixins_for and the __pyxsd_validate__ hook contract are documented in Mixin extensions.

Type stubs for generated classes

The generated classes live in schema.classes, created at runtime, so mypy and IDEs cannot import them. Schema.stub() renders their typing surface as .pyi text, and the same renderer is importable without the method:

pyxsd.stubs.emit_stub(schema, *, compact=False, component_ids=None, depth=None)[source]

Returns the stub text for the classes schema compiled.

The text is generated from the live schema at call time (the class table, the declarations behind it, and the compiled particle model), so re-emitting after an overlay is loaded shows the overlay’s members — nothing is cached at compile time.

Full mode is an importable-quality .pyi document: a provenance and consumption header, imports, one class block per generated class, and a root-element alias section. Annotations name the real runtime classes, so a member typed Int reads back a pyxsd.xsd_data_types.Int.

compact selects the agent-oriented compact formatter, which shares this module’s member model: a two-line header (provenance and an alias legend mapping the short spellings back to the pyxsd classes), one line per class and per member, facet-only simple types folded onto the members that use them, and a trailing comment line listing the root elements. The compact text is a description, not importable Python; every annotation spelling it uses is a rendered class name, a pyxsd builtin’s own name, or a legend alias. Degradation comments are shared through the model and render verbatim in both modes (see _render_compact()).

component_ids selects native declaration IDs; depth bounds outgoing declaration dependencies (None follows all reachable ones). An empty selection emits no classes or root aliases. Explicitly selected simple types are not folded away in compact mode. Unknown IDs raise KeyError; negative depths raise ValueError. Selection does not modify the schema’s class table.

Unfiltered rendering never raises on a schema that compiled: a construct a stub cannot type truthfully (an unresolvable type, a member with no Python accessor) degrades to Any with a comment, and an error-bearing schema still emits. This never calls schema.require_valid().

Parameters:
Return type:

str

Schema.stub(*, compact=False, component_ids=None, depth=None) -> str reads the live schema at call time — nothing is cached at compile time — so a stub regenerated after an overlay is loaded shows the overlay’s members.

The two modes

The full form (the default) is an importable-quality .pyi document: a provenance and consumption header, imports of the real pyxsd datatype classes (annotations name pyxsd.xsd_data_types.Int, not a stand-in int), one class block per generated class with inheritance mirroring the XSD derivation and simple types carrying their facets as comments, and the global elements as module-level aliases. There is no importable module to point at; save the text next to your code and name it under TYPE_CHECKING:

if TYPE_CHECKING:
    from schema_stubs import PurchaseOrderType
root = cast(PurchaseOrderType, document.root)

pyxsd ships the PEP 561 py.typed marker, so the stub’s imports of SchemaBase and the pyxsd.xsd_data_types classes resolve statically: mypy checks code written against the stub with the real pyxsd types behind the annotations.

The compact form (schema.stub(compact=True)) is the agent-oriented description: a two-line header (provenance and an alias legend mapping short spellings such as str, int, and date back to the pyxsd classes they stand for), one line per class and member, facet-only simple types folded onto the members that use them, and a trailing # roots: comment line. It is a description, not Python source.

Native schema exploration and filtered views

pyxsd.introspection describes compiled declarations without Pydantic or Arrow:

from pyxsd.introspection import describe_component, find_components, related_components

matches = find_components(schema, text="order", kind="type")
declaration = describe_component(schema, matches[0].id)
dependencies = related_components(schema, declaration.id, direction="uses", depth=2)
print(schema.stub(compact=True, component_ids=(declaration.id,), depth=1))

describe_schema(schema) returns the complete inventory and global root IDs. Descriptions are frozen records containing declaration origins, Python accessors, effective facets, inherited assertions, derivation, substitution and identity constraints, and compiled particle trees. They are not adapter projections and can describe recursive, mixed or wildcard content that those adapters reject. Pattern facets retain their AND-of-OR restriction groups; their representation identifies authored XSD patterns versus compiled Python regexes. Namespace filters use expanded-name namespaces; namespace="" selects unqualified declarations.

Member occurrence bounds are conservative summaries shared with the stub renderer; their occurrence_precision is conservative. Exact choice/group relationships and finite repetition bounds live in particle. Attribute-use bounds are exact, including prohibited uses. Never treat independent member summaries as a substitute for the particle tree or full document validation.

Global component IDs combine kind and expanded name; local and anonymous IDs use source/declaration addresses, not Python object identities. Invalid schemas with duplicate global declarations retain every declaration: the first source occurrence keeps the ordinary ID and later occurrences receive #2, #3, and so on. Origins refer to the original schema before composition rewrites; line numbers are not fabricated. Descriptions are cached snapshots: recompile after changing schemas or overlays. They do not mutate schema.classes, and unknown IDs raise KeyError.

Filtered stubs select this inventory, include dependency declarations up to depth, and omit unrelated root aliases. Omit depth to follow all reachable declarations; zero selects only the requested declarations’ bindings. An empty tuple selects nothing. Explicitly selected simple types remain visible in compact mode instead of being folded away. Unfiltered stub rendering retains its existing behavior.

Truthfulness contract

Stubs describe reads from valid parsed documents, and every reachable absence is annotated:

  • an element with maxOccurs > 1 reads as list[T]; an optional one (minOccurs="0", a branch of a choice, or an optional ancestor group) adds | None — an absent repeated member reads as None, because the list exists only after the first bind;

  • an attribute with use="required" reads as T; every other attribute reads as T | None;

  • a member carrying a default or fixed value annotates T | str | None with the value in a comment: a declaration-local element default reads back the raw lexical str when its element is absent, a fixed element reads None, and an attribute’s default/fixed is applied typed by the binder.

Two asymmetries the annotations cannot encode are stated in the emitted preamble: an absent optional member — and a required member of an invalid document — reads as None, and repeated-element assignment appends a single item (obj.x = item; assigning a list raises).

One more runtime divergence is stated there too: the parser binds a complex-typed child in the parsed tree only, so obj.child reads None for it, while a primitive-typed child is accessor-bound. The annotations describe the descriptor and construction API.

Where a construct cannot be typed honestly the stub degrades instead of lying: a member whose value comes from a union, a recursive back edge, an unresolvable declaration, and a schema name that is not dot-accessible become Any, a comment, or both; mixed content, wildcards, and open content become class-body comments. Rendering never raises for a schema that compiled, even one whose report carries errors: stub() never calls require_valid(), so call it yourself when you want to reject a bad schema.

Two rendering limitations are known and deliberate. An overlay method parameter whose default has a non-literal repr (for example p=Path("x"), emitted as PosixPath("x")) is written verbatim and may fail mypy: only annotations are resolved or degraded, not defaults. And a schema type literally named Any or SchemaBase renders a class that collides with the emitted imports (mypy no-redef).

The overlay (extension) loop

Because rendering reads the live class table, the overlay mechanism composes with stubs into the extension workflow:

  1. compile the schema and emit the stub;

  2. write overlay code against the stub — autocomplete and mypy work;

  3. load the overlay and emit the stub again — the overlay’s members now appear in its class block.

from pathlib import Path

from pyxsd.schema import Schema

schema = Schema.compile("orders.xsd")
Path("orders.pyi").write_text(schema.stub())

schema = Schema.compile("orders.xsd", overlay="overlay.py")
Path("orders.with_overlay.pyi").write_text(schema.stub())

An overlay class is loaded under its name attribute in place of the generated class of the same schema type (see Architecture), so stub regeneration describes what the overlay’s class actually provides. The CLI emits the same text with pyxsd --emit-stubs [PATH] -s SCHEMA; see Command-line reference.

Example: full mode

Using the ORDERS schema throughout this section:

import io

from pyxsd.schema import Schema

ORDERS = """<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:simpleType name="SKU"><xs:restriction base="xs:string">
    <xs:pattern value="\\d{3}-[A-Z]{2}"/>
  </xs:restriction></xs:simpleType>
  <xs:complexType name="Item"><xs:sequence>
    <xs:element name="sku" type="SKU"/>
    <xs:element name="quantity" type="xs:int"/>
    <xs:element name="note" type="xs:string" minOccurs="0"/>
  </xs:sequence>
  <xs:attribute name="id" type="xs:string" use="required"/>
  </xs:complexType>
  <xs:element name="orders"><xs:complexType><xs:sequence>
    <xs:element name="item" type="Item" maxOccurs="unbounded"/>
  </xs:sequence></xs:complexType></xs:element>
</xs:schema>"""

print(Schema.compile(io.StringIO(ORDERS)).stub())
# Generated by pyxsd 1.2.1 from <stream>. Do not edit by hand.
#
# This file is a generated typing surface for the classes a compiled
# schema creates at runtime; there is no importable module (the classes
# live in ``schema.classes``). Save this stub next to your code and
# import it for type checking:
#     if TYPE_CHECKING:
#         from schema_stubs import Orders
#     root = cast(Orders, document.root)
#
# Semantics: annotations model valid parsed documents. Absent optional
# members read as None (including absent repeated members); required
# members of invalid documents read as None too. Repeated assignment
# appends one item (obj.x = item); it does not take a list. The parser
# binds a complex-typed child in the parsed tree only (obj.child reads
# None), while a primitive-typed child is accessor-bound; the
# annotations describe the descriptor and construction API. Constructs
# a stub cannot type truthfully are Any with a comment.

from typing import Any

from pyxsd.schema_base import SchemaBase
from pyxsd.xsd_data_types import Int, String

class SKU(String):
    ...
    # pattern: \d{3}-[A-Z]{2}

class Item(SchemaBase):
    sku: SKU
    quantity: Int
    note: String | None
    id: String

# bookkeeping name 'orders|complexType'; stub name synthesized
class Orders(SchemaBase):
    item: list[Item]

# Root elements:
orders: type[Orders]

The inline orders declaration has no name of its own, so the class is synthesized as Orders with the bookkeeping name kept in a comment — class names are never guessed silently.

Example: compact mode

print(Schema.compile(io.StringIO(ORDERS)).stub(compact=True))
# Generated by pyxsd 1.2.1 from <stream>; compact view, not Python source.
# aliases: str=String, int=Int

class Item:
  sku: str  # pattern: \d{3}-[A-Z]{2}
  quantity: int
  note: str | None
  id: str

# bookkeeping name 'orders|complexType'; stub name synthesized
class Orders:
  item: list[Item]

# roots: orders: Orders

Every annotation spelling is a class the document declares, a pyxsd builtin’s own name, or a legend alias; a folded simple type’s facets ride on the members that use it.

Batch parsing

Schema.iter_parse reuses the compiled schema for explicit local files. See Sequential multi-document parsing for lazy stop/report behavior and resource limits.

class pyxsd.batch.DocumentSource(id, path)[source]

Caller label and local path, anchored when constructed.

Parameters:
class pyxsd.batch.ParseOutcome(source, status, document, issues, error)[source]

One input’s result with a snapshot of its validation issues.

Parameters:
class pyxsd.batch.InputFailure(kind, message)[source]

Expected input failure, without a live exception or traceback.

Parameters:
  • kind (Literal['io', 'malformed_xml'])

  • message (str)

class pyxsd.batch.BatchParseError(outcome)[source]

First failed input in raise mode; inspect outcome.

Parameters:

outcome (ParseOutcome)

pyxsd.batch.iter_parse(schema, sources, *, errors='raise')[source]

Parse in caller order, lazily, without retaining earlier outcomes.

Configuration and schema checks run on first iteration. Expected input failures stop with BatchParseError by default; report mode yields them. Do not parse concurrently or reentrantly on this Schema.

Parameters:
Return type:

Iterator[ParseOutcome]

Optional Parquet datasets

pyxsd.integrations.arrow.RecordProjection.write_dataset(sources, destination, *, selector=None, namespaces=None, batch_size=10000, errors="raise") uses the projection’s own compiled Schema. Install pyxsd[arrow] for the writer; the result/error records below need no optional dependencies. No revalidate argument is needed: every source is freshly parsed and required to be valid. See Sequential multi-document parsing for publication and failure contracts; Optional Pydantic and Arrow integrations describes preparation and supported projections.

class pyxsd.integrations.dataset.DatasetResult(destination, status, inputs, succeeded, failed, rows, manifest)[source]

Published dataset summary; partial/failed results are not full successes.

Parameters:
  • destination (Path)

  • status (Literal['complete', 'partial', 'failed'])

  • inputs (int)

  • succeeded (int)

  • failed (int)

  • rows (int)

  • manifest (Path)

class pyxsd.integrations.dataset.DatasetExportError(message, *, stage, source_id=None)[source]

Fatal export failure with stage/source context and a chained cause.

Parameters:
  • message (str)

  • stage (str)

  • source_id (str | None)

Querying a document

Document.xpath(expr) evaluates an XPath expression over the bound tree and returns the original bound nodes (scalars such as count(...) pass through). The context item is the document’s root element, so an absolute path starts /root. Document.find(path) and Document.findall(path) accept ElementTree’s path subset instead. Pass namespaces={"p": uri} for prefixed queries.

Queries run over a projection of the bound tree, so they see what the object model holds: descriptor write-through is reflected, text mirrors _value_, and a child’s tail mirrors the recorded _tail_ — so mixed character data is queryable in document order. Parsing does not preserve comment nodes, and a nil/no-content element projects text=None. The projection is document-faithful, not source-XPath-exact: element-only content’s recorded layout whitespace is part of the projected text. The projection tags nodes with their expanded (Clark-notation) name, so prefixed queries work in both namespace modes. Document.to_dict/ to_json provide structured export (see Data model).

Errors

class pyxsd.exceptions.PyXSDError[source]

Raised when parsing cannot proceed.

class pyxsd.exceptions.ValidationError(message, report)[source]

Raised by require_valid() when a report contains errors.

  • report: the ValidationReport whose errors caused the raise; inspect it instead of parsing the message.

Parameters:
class pyxsd.exceptions.PyXSDWarning[source]

Warning category for recoverable, non-fatal conditions.

PyXSDError signals fatal input problems (unreadable or malformed files, a document whose root is not xs:schema). ValidationError is what require_valid() raises; its report attribute carries the full ValidationReport.

The ValidationReport/ValidationIssue classes are documented in Validation (detailed reference) rather than duplicated here.

Modes and the node protocol

BindingPolicy and the ParseModes presets are documented in Parse modes and binding. The node shape is the XMLNode protocol:

class pyxsd.nodes.XMLNode(*args, **kwargs)[source]

Structural type of one node in a parsed instance tree.

Both the generated schema classes and the synthetic nodes built by Transform.makeElemObj satisfy this protocol:

  • _name_: the element’s tag name.

  • _attribs_: attribute names mapped to their lexical values.

  • _children_: child nodes in document order (a child may be None when an optional element is absent).

  • _value_: the element’s text split on line boundaries, or None when it has none.

Transforms

See The Transform class for the Transform/Displayer class reference, iter_tree, and the built-ins.