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
Vthatvc:*selectors were tested against; the schema’s own vocabulary is not restricted by it (that is the compiler’s job, seeversion_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).
- 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 notxs:schemaraisePyXSDError; schema-legality problems are collected onreportinstead.mode- aBindingPolicy(usually aParseModespreset). Defaults toSTRICT.xsd_version- the XSD processor version the document is compiled as,"1.0"or"1.1"(default"1.1"). It is the declared versionvc:*conditional-inclusion selectors test against: in"1.0"mode a declaration carryingvc:minVersion="1.1"is dropped (XSD 1.1 §4.2.2). The vocabulary itself is checked separately (seepyxsd.version_gates).namespace_schemas- an optional{namespace_uri: path}mapping supplying schemas for namespaces referenced byxs:importwithout aschemaLocation.overlay- an optional overlay class file, loaded after class generation (experimental; seeload_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’sxsi:schemaLocationbeyond the main schema. Advisory hints: consulted in strict namespace mode only, and one that cannot be loaded is aschema-hintwarning (parse()passes the instance’s pairs; a schema-only compile has none).extensions- an optionalExtensionsregistry 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. Itsparse()owns XML parsing, soxml_policyandxml_limitscannot be combined with it.xml_policy- an opt-inXmlParserPolicyapplied to the main schema and every included/imported dependency.Nonekeeps stdlib parsing and its current DTD/entity behavior. The choice is compile- scoped: it never becomes the default for laterparse()calls.xml_limits- optionalXmlLimitsbyte/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:
mode (Any)
xsd_version (Literal['1.0', '1.1'])
namespace_context (NamespaceContext | None)
extensions (Extensions | None)
resource_policy (ResourcePolicy | None)
xml_policy (XmlParserPolicy | None)
xml_limits (XmlLimits | None)
_source_purpose (Literal['schema', 'schema-hint', 'instance'])
- Return type:
- 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
Anywith 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.pyisource:schema.stub() # full .pyi document schema.stub(compact=True) # compact agent-oriented view
The CLI emits the same text:
pyxsd --emit-stubs [PATH] -s SCHEMAwrites the full document to PATH (stdout when PATH is omitted), and adding--stubs-compactselects the compact view.component_idsoptionally selects native declaration IDs, withdepthbounding their outgoing dependencies (None follows all). Unknown IDs raise KeyError and negative depths raise ValueError. Seepyxsd.introspectionfor 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.
- 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:
sources (Iterable[DocumentSource])
errors (Literal['raise', 'report'])
- Return type:
- 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 parsedxml.etree.ElementTree.Elementused as the document root directly. An unopenable file or malformed XML raisesPyXSDError(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 fromcompile();Nonekeeps stdlib parsing and unlimited structure. They cannot be combined with a customresource_policy, whoseparse()is trusted application code. A pre-parsedElementis 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
reportmerges this schema’s (frozen) compilation issues with the document’s own binding issues.Document.rootisNonewhen the document root matched no global element declaration (theunknown-rootissue records why).
- class pyxsd.document.Document(schema, root, source, report, *, provenance=None)[source]¶
A schema-bound instance document: the interaction hub.
- Parameters:
schema (Schema)
root (Any)
source (str | Path | os.PathLike[str] | IO[str] | ET.Element | None)
report (ValidationReport)
provenance (ParseProvenance | None)
- root: Any¶
The bound root instance, or
Nonewhen 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
- 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 indocs/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()andxpath()are the faithful mixed-content views.
- 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 adecimal.Decimal) are encoded as strings.
- transform(fn, *args, **kwargs)[source]¶
Applies fn to the bound tree.
fn(root, *args, **kwargs)may return a new tree root (a boundSchemaBaseinstance), which becomes the root of a newDocumentagainst the same schema — or anything else, includingNone, which is returned unchanged. When a transform mutates the tree in place and returnsNone, 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.
- 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
sourceis carried over.- Return type:
- 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,
textmirrors_value_and a child’stailmirrors its recorded_tail_, so mixed character data is queryable in document order. Parsing does not preserve comment nodes, and an element with_value_ is Noneprojectstext=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. Seedocs/data-model.md.
- 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:
mode (Any)
xsd_version (Literal['1.0', '1.1'])
namespace_context (NamespaceContext | None)
extensions (Extensions | None)
resource_policy (ResourcePolicy | None)
xml_policy (XmlParserPolicy | None)
xml_limits (XmlLimits | None)
_source_purpose (Literal['schema', 'schema-hint', 'instance'])
- Return type:
- 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
xsdwhen given; otherwise the instance’s ownxsi:schemaLocation/xsi:noNamespaceSchemaLocationhints name it, resolved relative to the instance document’s directory (the working directory for stream sources). An instance that names no schema raisesPyXSDError. 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 raisesResourcePolicyError.xml- the instance document: a file path, a file object open for reading, or a parsedxml.etree.ElementTree.Elementused 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 toSchema.compile().resource_policy- a trusted application resource policy for both the instance and schema documents. Itsparse()owns XML parsing, soxml_policy/xml_limitscannot 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 laterSchema.parse()calls.
- Parameters:
mode (Any)
resource_policy (ResourcePolicy | None)
xml_policy (XmlParserPolicy | None)
xml_limits (XmlLimits | None)
- Return type:
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.- classmethod default()[source]¶
Stdlib ElementTree with today’s DTD/entity behavior and no overhead.
- Return type:
- classmethod protected()[source]¶
Defusedxml; forbid entity declarations and external references.
Plain DTD syntax stays allowed, so harmless internal
ATTLISTdefaults still apply.- Return type:
- 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_byteson file/binary-stream bytes or UTF-8 encoded bytes for text-mode streams,max_depthon element nesting (the root is depth 1), andmax_elementson start tags.Noneleaves that budget unset.
- 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 instancexsi:schemaLocation/xsi:noNamespaceSchemaLocationthat names a remote schema needsallow_remote_hints=Trueas well. A local schema (or anonymous stream) may reference a remote dependency only withallow_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 exceedslimits.closure_bytes,limits.resourcesunique URLs, andlimits.depthdependency levels. Redirects are followed one request at a time, with the scheme, origin, and addresses revalidated before each new request, up tolimits.redirectshops. Every downloaded body counts towardclosure_bytes, including redirect and error responses, andlimits.redirectsis 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 needsallow_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. Directreadcalls 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).parsealways usesXmlParserPolicy.strict()and the policy’s XML limits (response_bytesfor bytes,xml_depthandxml_elementsfor 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.resolveris 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).limitsdefaults to a freshRemoteLimits;transportdefaults toHttpClientTransport;resolverdefaults to the system resolver.- Parameters:
allow_remote_hints (bool)
allow_local_remote (bool)
allow_private_addresses (bool)
limits (RemoteLimits)
transport (RemoteTransport | None)
resolver (AddressResolver | None)
- compilation(*, root_read=True)[source]¶
Isolate the closure state of one in-flight compilation.
root_readis 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.
- 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_bytesbounds one downloaded resource andclosure_bytesbounds all resources fetched for one compilation.resourcesanddepthbound the dependency closure,redirectsbounds the hops the caller may authorize, and the three timeouts configure connecting, each socket read, and the total request.xml_depthandxml_elementsare structural limits for the strict parser that consumes a downloaded resource.
- 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:
url (str)
limits (RemoteLimits)
- 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 ofread_timeoutand the time left beforerequest_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-Lengthis 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 expiredrequest_secondsdeadline. DNS, TLS, and socket failures (including a read timeout shorter than the remaining deadline) propagate as the originalOSErrorsubclass. 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.purposeis aResourcePurpose:"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 throughxsi:schemaLocationorxsi:noNamespaceSchemaLocation; and"instance"for an instance document. Parsers must populate the supplied namespace context. Anonymous streams use<memory>and have no implicit filesystem base.
- 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;
spanandextractare cheap dictionary lookups.sourcenames the input in diagnostics.contentis the original encoding; spans index its bytes, so a UTF-16 or BOM-prefixed document is sliced in that encoding rather than re-encoded.policydefaults todefault()(the same declaration behavior as core parsing);limitsbounds 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:
source (str)
content (bytes)
policy (XmlParserPolicy | None)
limits (XmlLimits | None)
- span(path)[source]¶
Return this occurrence’s exact byte range, or
Noneif unknown.- Parameters:
path (tuple[PathStep, ...])
- Return type:
SourceSpan | None
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)). Everyfor_*selector compiles to a predicate over the type ER immediately;for_pathis 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"). Withoutmixin, returns a decorator that registers the decorated class.
- for_namespace(uri, mixin=None)[source]¶
Registers mixin for every type declared in namespace uri.
An empty or
Noneuri selects declarations that have no namespace.
- for_kind(kind, *, derived_from=None, mixin=None)[source]¶
Registers mixin for types of kind (
simpleTypeorcomplexType).With derived_from, only types whose
superClassNameslist the given base (matched by local or expanded spelling) qualify.
- 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.
- 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.
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
.pyidocument: 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 typedIntreads back apyxsd.xsd_data_types.Int.compactselects 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_idsselects native declaration IDs;depthbounds 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
Anywith a comment, and an error-bearing schema still emits. This never callsschema.require_valid().
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 > 1reads aslist[T]; an optional one (minOccurs="0", a branch of a choice, or an optional ancestor group) adds| None— an absent repeated member reads asNone, because the list exists only after the first bind;an attribute with
use="required"reads asT; every other attribute reads asT | None;a member carrying a
defaultorfixedvalue annotatesT | str | Nonewith the value in a comment: a declaration-local elementdefaultreads back the raw lexicalstrwhen its element is absent, a fixed element readsNone, and an attribute’sdefault/fixedis 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:
compile the schema and emit the stub;
write overlay code against the stub — autocomplete and mypy work;
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.
- class pyxsd.batch.ParseOutcome(source, status, document, issues, error)[source]¶
One input’s result with a snapshot of its validation issues.
- Parameters:
source (DocumentSource)
status (Literal['valid', 'invalid', 'input_error'])
document (Document | None)
issues (tuple[ValidationIssue, ...])
error (InputFailure | None)
- class pyxsd.batch.InputFailure(kind, message)[source]¶
Expected input failure, without a live exception or traceback.
- 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:
schema (Schema)
sources (Iterable[DocumentSource])
errors (Literal['raise', 'report'])
- 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.
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.ValidationError(message, report)[source]¶
Raised by
require_valid()when a report contains errors.report: theValidationReportwhose errors caused the raise; inspect it instead of parsing the message.
- Parameters:
message (str)
report (ValidationReport)
- 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.makeElemObjsatisfy 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 beNonewhen an optional element is absent)._value_: the element’s text split on line boundaries, orNonewhen it has none.
Transforms¶
See The Transform class for the Transform/Displayer class
reference, iter_tree, and the built-ins.