Validation¶
pyxsd reports recoverable schema and instance findings in a
ValidationReport rather than raising on the first
one. Each finding is a ValidationIssue. A compiled schema reports its own
findings at Schema.report; a bound document reports the merged run —
schema-phase issues followed by instance-phase issues — at
Document.report. A run can produce a bound object tree and multiple
validation findings at the same time.
Reporting is non-fatal only when parsing can proceed. An unreadable or
malformed input, or a document whose root is not xs:schema, raises
PyXSDError instead of returning a partial report.
An undeclared instance root is recoverable: it produces an unknown-root
issue and a Document whose root is None.
Note
Recoverable reporting is separate from the parse mode
(Schema.compile(..., mode=...), --mode), which controls whether invalid
values and unrecognized content are dropped or retained in a best-effort
bound tree. A bound tree is not necessarily complete or lossless, even in
lax mode. See Parse modes and binding.
The report collects the checks the processor supports; it is not a promise
that every possible violation or a complete, source-faithful tree is
returned. With facet enforcement enabled (the default), a parse mode does
not relax facet validation merely to retain an invalid value. Setting
facets="off" skips those checks, so the resulting report is not a full
validity verdict for facet-constrained values. The known schema-legality
limitations below also apply.
import pyxsd
schema = pyxsd.Schema.compile("inventory.xsd")
schema.require_valid() # raises ValidationError if the schema itself is bad
document = schema.parse("inventory.xml")
if document.report.has_errors:
for issue in document.report.issues:
print(issue.format())
Both Schema.require_valid() and Document.require_valid() raise
ValidationError, whose report attribute is
the report that failed the check.
Resource and parser policies¶
pyxsd.compile(..., resource_policy=policy) and
pyxsd.parse(..., resource_policy=policy) accept an optional
pyxsd.resource_policy.ResourcePolicy. The compiled schema retains this
policy for subsequent Schema.parse() and Document.revalidate() calls.
Without a policy, existing loading and parsing behavior is unchanged.
A policy supplies two methods:
read(source, *, base, purpose)authorizes and loads a named resource, returningResourceData(source_id, content)with its original bytes.baseidentifies the including document, not its directory;purposeis"schema"(an explicit or mapped schema, or an ordinary dependency),"schema-hint"(a schema the instance selected through its location hint), or"instance". Canonical source IDs can name virtual filesystem layouts. They also determine nested dependency bases and composition-cycle detection.parse(data, context)parses admitted bytes and records namespace bindings in the suppliedNamespaceContext.
Includes, imports, redefines, overrides, namespace mappings and instance
schema hints use the same policy. Raise ResourcePolicyError to refuse
access: unlike unavailable-resource errors, a denial is never downgraded
to an ignored import or schema-hint warning. A policy’s parse() owns XML
parsing; xml_policy and xml_limits cannot be combined with it.
Streams pass through the policy parser too; byte streams preserve original
encoding, whereas text streams are encoded as UTF-8. Anonymous schema
streams have no implicit filesystem base: relative dependencies require a
canonical stream .name. Already-parsed Element inputs are rejected
when a policy is active, so they cannot bypass its parser.
Schema.compile and Schema.parse also accept opt-in xml_policy and
xml_limits arguments without a custom resource policy. xml_policy=None
keeps the stdlib parser and its current DTD/entity behavior;
XmlParserPolicy.protected() or .strict() selects a defusedxml-backed
profile, and XmlLimits bounds bytes, depth, and start-tag count. A
compile-time parser policy applies to the schema and its dependencies but is
not inherited by later instance parses, so pass it per parse. These hooks are
not a sandbox or a secure parser implementation. A policy is trusted
application code responsible for access checks, parser hardening, limits and
namespace evidence. Python overlays and extensions remain executable
application code, outside this XML-loading contract.
pyxsd.RemoteResourcePolicy is the built-in opt-in remote schema loader.
Selecting it authorizes a remote main schema, whether the caller supplied it
explicitly or the instance selected it through a location hint; a hint-selected
remote main also requires allow_remote_hints=True, and as the root read it
needs no allowed_origins entry. An additional remote hint resolved from a
local schema is not a root read and needs both allow_remote_hints=True and
allow_local_remote=True. An ordinary remote include/import from a local schema
needs allow_local_remote=True; once admitted, cross-origin dependencies of a
remote document need allowed_origins. Every request is HTTPS, address-checked,
budgeted, and subject to RemoteLimits; there is no persistent cache. Remote
instance documents, remote-to-local pivots, file: URLs, and remote Python
overlays are refused. Once selected, transport/resolver failures abort the
compile even for a document-authored include/import. Full rules, defaults, and
the DNS-rebinding caveat are in XML security and untrusted input.
Selecting the policy also governs instance parsing: Schema.parse() sends the
instance through the policy, which parses it with XmlParserPolicy.strict() and
caps it at limits.response_bytes. Local instances are affected too, so a
DOCTYPE-bearing or oversized instance is refused while the policy is attached.
Report API¶
- class pyxsd.validation.ValidationReport(*, max_issues=None, max_message_bytes=None)[source]¶
An ordered collection of validation issues.
A schema owns the report of its compilation; each document parsed from it gets a fresh instance report, and the document’s merged report presents both phases in order. The binding machinery fills the reports in as it walks the schema and instance documents.
- phase: str¶
The phase new issues are attributed to unless one is passed explicitly. The parser flips this between schema compilation and instance binding.
- add_error(message, *, code, element=None, phase=None, location=None, constraint=None, details=None, location_precision='unknown')[source]¶
Record a fatal-severity issue.
- add_warning(message, *, code, element=None, phase=None, location=None, constraint=None, details=None, location_precision='unknown')[source]¶
Record a recoverable-severity issue.
- for_phase(phase)[source]¶
Only the issues attributed to phase (
"schema"/"instance").- Parameters:
phase (str)
- Return type:
- extend(other)[source]¶
Appends every issue from other to this report.
Used to merge the phase reports of one run into a single collection:
pyxsd.schema.Schema.parse()merges the schema-phase and instance-phase issues into the document’s report, and a caller adopting the findings of apyxsd.document.Document.revalidate()run merges that document’s report the same way. The issue objects are shared, not copied; the other report is left unchanged.- Parameters:
other (ValidationReport)
- Return type:
None
- property suppressed_count: int¶
Findings whose details were not retained (including merged reports).
- to_dict()[source]¶
Return JSON-safe details and complete counts; decimals remain strings.
Unsupported detail values raise TypeError rather than falling back to a potentially lossy or executable object’s string representation.
- property issues: list[ValidationIssue]¶
All recorded issues, in the order they were found.
- property errors: list[ValidationIssue]¶
Only the error-severity issues.
- property warnings: list[ValidationIssue]¶
Only the warning-severity issues.
- class pyxsd.validation.ValidationIssue(severity, code, message, element=None, phase='instance', *, location=None, constraint=None, details=None, location_precision='unknown')[source]¶
A single problem found while validating a document.
severity: theIssueSeverityof the issue.code: a short, stable, machine-readable tag describing the kind of problem (for example"missing-attribute"or"order"). Codes are documented in the validation guide and are safe to match on in tooling.message: a human-readable, single-line description.element: the name of the element or type the issue was found in, when known.phase: the pipeline stage that produced the issue, either"schema"(reading the XSD, composing included schemas, building the generated classes) or"instance"(binding the XML document). Tooling and the conformance runner use this to check each stage independently.
- Parameters:
- severity: IssueSeverity¶
Issue codes¶
Codes are stable strings. ERROR-level codes fail under --strict or
report.has_errors; WARNING-level codes never affect exit status.
Code |
Severity |
Meaning |
|---|---|---|
|
ERROR |
Child order violates the sequence content model. |
|
ERROR |
A child element is not allowed by the declaration’s child grammar. Also reported for a |
|
ERROR |
A child that may appear at most once is repeated (including a wildcard’s second |
|
ERROR |
Children are out of order, or an exclusive content kind excludes later children. |
|
ERROR |
A declaration is missing its required name. |
|
ERROR |
A declaration’s XML attribute is illegal for its XSD representation: |
|
ERROR |
An occurrence attribute ( |
|
ERROR |
A wildcard’s XML representation is illegal: an unknown or misplaced namespace-constraint token (only |
|
ERROR |
|
|
ERROR |
A |
|
ERROR |
A complex type derived from a complex base violates a particle-derivation rule (XSD 1.0 §3.9.6 / 1.1 §3.4.6.2; the message names the deciding constraint, such as |
|
ERROR |
Attribute-use derivation on a complex type: a restriction that redeclares a required base attribute use as optional, or that redeclares a base use whose |
|
ERROR |
Unique Particle Attribution (cos-nonambig) over a complex type’s effective content model — the base type’s tree composed with an extension’s suffix. Two element/wildcard particles with overlapping names are ambiguous when no deterministic separation exists between their positions: a repeated (non-univocal) sequence member followed by an overlapping member or wildcard, a choice or |
|
ERROR |
A declaration sits in a container whose construction-time context cannot hold it: an |
|
ERROR |
A constraining facet is not applicable to its base type, or its declared value is not legal for that base (bad lexical form, outside the base’s value space, or a digit facet that violates the fixed value on an integer-derived type). Also reported when a restriction widens the base’s value space: a |
|
ERROR |
Two constraining facets in one restriction step cannot hold together: mutually exclusive bounds ( |
|
ERROR |
Element is not declared in the content model and no wildcard allows it. A wildcard that matches by namespace still refuses an element its XSD 1.1 exclusions bar — |
|
ERROR |
A complex-typed element whose content model is element-only has character content other than whitespace (XSD 1.1 §3.4.3.2, Element Locally Valid (Complex Type)). Mixed content and simple content are not checked — their text is legal or is the value, and a mixed content type includes one inherited through an extension with empty explicit content — and neither are elements whose content model could not be compiled. |
|
ERROR |
|
|
ERROR |
An undeclared instance attribute was not admitted by the governing type’s effective attribute wildcard — the type’s own |
|
ERROR |
XSD 1.1 dynamic tighter EDC (Element Locally Valid (Complex Type) clause 5): an element admitted by a |
|
ERROR |
Fewer occurrences than |
|
ERROR |
More occurrences than |
|
ERROR |
A required attribute is absent. |
|
ERROR |
Attribute not declared in the schema and not admitted by any effective attribute wildcard. The check skips the XML Schema instance and XML namespaces and |
|
ERROR |
Attribute value fails its declared type; or, in the schema phase, an |
|
ERROR |
A direct attribute declaration with |
|
ERROR |
Attribute present with a value differing from |
|
ERROR |
Referenced type could not be resolved: a namespaced reference into a namespace no composed schema declares, a reference into the XML Schema namespace that names no built-in, or an unqualified reference that resolves into no namespace when a same-named type is declared in another loaded namespace. An unqualified no-namespace reference with no candidate stays tolerated. |
|
WARNING |
An |
|
WARNING |
A schema component lookup is missing or ambiguous. The warning is recorded when a report is active. |
|
ERROR |
A |
|
ERROR |
A simple-type |
|
ERROR |
|
|
ERROR |
An |
|
ERROR |
A prefixed name uses a namespace prefix that is not bound in scope (namespaced mode). |
|
ERROR |
Text content failed lexical validation for its type. |
|
ERROR |
Element default value is not valid for the element’s type. |
|
ERROR |
Element content differs from its |
|
ERROR |
Any |
|
ERROR |
Instance of an abstract element declaration. |
|
ERROR |
Direct instance of an abstract type (allowed only via |
|
ERROR |
Substitution-group member blocked by the head’s |
|
ERROR |
|
|
ERROR |
Document root matches no global element declaration. |
|
ERROR |
More than one global element matches the document root. |
|
ERROR |
Key field missing or duplicate key value. |
|
ERROR |
Duplicate value under a |
|
ERROR |
Keyref value has no matching key/unique value. |
|
ERROR |
A keyref’s |
|
ERROR |
An identity-constraint |
|
ERROR |
An |
|
ERROR |
An |
|
ERROR |
An |
|
ERROR |
An |
|
ERROR |
An XSD 1.1-only element, attribute, or built-in type appears while compiling in XSD 1.0 mode. |
|
ERROR |
A |
|
ERROR |
The type alternative conditionally assigned to an instance element is |
|
ERROR |
Two attribute or element values of type |
|
ERROR |
A value of type |
|
WARNING |
Identity-constraint XPath uses an unsupported construct. |
|
WARNING |
An inline union member could not be built and was skipped; other members remain available. |
|
ERROR |
Referenced |
|
ERROR |
A type derives from itself directly or through a derivation cycle; the cyclic base is skipped. |
|
ERROR |
A simple type combines |
|
ERROR |
Group reference cycle. |
|
ERROR |
An attribute |
|
ERROR |
Referenced |
|
ERROR |
An attribute use is contributed twice to one complex type with the same expanded name: the schema document’s |
|
ERROR |
Substitution group references a missing head. |
|
ERROR |
A substitution-group member’s declared type is not validly derived from its head’s type (XSD 1.1 §3.3.5.2). Reported for a member with more than one head and for the clear ur-type category mismatches (a complex member under an |
|
ERROR |
Substitution-group membership is cyclic ( |
|
ERROR |
Element |
|
ERROR |
Derivation violates the base type’s effective |
|
ERROR |
The schema file itself is malformed or unreadable. |
|
WARNING |
Malformed schemaLocation hint in the instance document. |
|
ERROR / WARNING |
A composition directive is invalid or its referenced schema cannot be used. ERROR for a missing |
|
ERROR / WARNING |
An |
|
WARNING |
A repeated include/redefine was deduplicated. |
|
ERROR |
Include target namespace mismatch. |
|
ERROR |
A composition rule violation beyond a missing resource: an |
|
ERROR |
An |
|
ERROR |
A mixin’s |
|
ERROR |
A mixin’s |
|
WARNING |
Parser internal inconsistency — please report. |
Strict mode¶
pyxsd --strict exits with status 1 when the report contains any
ERROR-severity issue. Warnings never affect the exit code.
Reports and transformed documents¶
A Document’s report describes the tree as it was bound, not subsequent
mutations. Document.transform() may return a bound root, None after an
in-place mutation, or an arbitrary result. When it returns a Document,
that document shares the historical report; require_valid() therefore
checks the old report rather than proving that the changed tree is valid.
Call revalidate() to serialize and parse the tree again, and check the
fresh document’s report. An unreadable or malformed serialized result still
raises PyXSDError; it does not become an ordinary validation finding.
Structured and bounded reports¶
report.to_dict() and report.to_json() expose issue severity, code, message,
phase and optional location/constraint details. Decimal evidence is encoded as
strings; integers outside JavaScript’s exact range are strings too. Nonfinite
floating-point evidence uses the canonical XSD strings NaN, INF, and -INF.
Unsupported detail objects raise TypeError instead of silently becoming arbitrary text.
ValidationReport(max_issues=N) retains at most the first N issue details.
The default is unlimited. error_count, warning_count and has_errors include
suppressed findings, and suppressed_count reports how many details are absent.
issues, errors, warnings, iteration and for_phase() expose only retained
details. A report can therefore be truthy with zero retained details; len(report)
still counts retained details. Merging a report preserves its aggregate counts
even when the receiving report has a different detail budget.
ValidationReport(max_message_bytes=N) visibly truncates retained messages to at
most N UTF-8 bytes without splitting a character. Truncation does not change
issue counts or suppressed-detail accounting. The default is unlimited. Resource
policies may optionally provide new_report() to apply these budgets consistently
to schema, instance, and merged reports; policies without it retain core defaults.
Location and constraint fields are optional: an absent location does not imply that an issue refers to the first element with that name. Facet violations expose typed constraint evidence at their source. Existing issue formatting and positional constructors remain unchanged.
Finding the offending occurrence¶
Instance diagnostics carry an IssueLocation when the parser can identify the
source occurrence. Its path is a tuple of PathStep(name, index) records:
expanded XML names and one-based sibling indexes among elements with that exact
name. Namespace URIs can contain slashes; do not split expanded names on /.
Attribute findings can additionally name an expanded attribute.
issue.location_precision distinguishes exact, container, component and
unknown evidence. A missing required child points to its existing container,
not an invented child. Schema findings and diagnostics outside parsing may have
unknown locations. No source line or column is fabricated.
document.locate(issue.location) returns a NodeLookup with a status:
bound:boundis the original bound object for that occurrence.unbound: the source element existed, but binding discarded it.attribute:boundis the original lexical attribute value.missing: the address or attribute did not exist in this parse.
Guard against issue.location is None before calling locate. These are
parse-specific addresses, not durable identifiers: modifying the tree does not
update them. revalidate() returns a freshly parsed Document with fresh
locations. Another parse on the same Schema does not retarget the first
Document’s lookups. A manually constructed or transformed Document has no
provenance until freshly parsed. Duplicate identity findings can include
details["related_locations"] for the earlier occurrences.
Known limitations (schema legality)¶
A few schema-phase checks are intentionally shallow, so some invalid schemas are accepted:
Substitution-derivation under-approximation. The
declaration-attributesubstitution-member check reports only a member type whose immediate derivation method the head’sfinalexcludes. It does not walk a blocked step earlier in the derivation chain, and it accepts a member type that is wholly unrelated to the head’s type.Duplicate names and
xs:IDuniqueness are main-document only. Duplicate component names and duplicateidvalues contributed by separate included/imported documents are not reported; both checks are scoped to the main schema document.vc:*conditional inclusion. In a document that uses XSD 1.1vc:*conditional inclusion, the ancestor walk used to evaluate it suppresses same-document duplicate-name detection.Shared-include conflicting redefine. A conflicting
xs:redefinereached through a shared include (the same base document reached by more than one path) is suppressed rather than reported.Inline list/union members.
atomic-requiredunder-reports for inline anonymous list/union members; atomicity is checked for named andmemberTypes-referenced members.One known false reject. The substitution-derivation check rejects one valid schema shape; the fix would regress a cyclic
xs:redefinecase whose expected verdicts the W3C suite itself contradicts.Composition residuals. Group-redefine content-model restriction validation and per-schema import visibility are not implemented (the latter is risky under pyxsd’s global composition); one import-related instance-binding gap also remains.
XSD 1.1 built-in types. 41 XSD 1.1 built-in types remain unimplemented; schemas that use them are not validated.
Caller-supplied schemas. A missing caller-supplied resource (
namespace_schemas, or an instancexsi:schemaLocation) is still anERROR, unlike a schema’s own missing include/import/redefine, which warns.