Migrating from pyxsd 0.1 to 1.0¶
pyxsd 1.0 is the first release in twenty years, ported to modern Python and
rebuilt for the ecosystem of 2026. It is a new generation of the same
idea: schema-compiled Python classes, a validated instance tree, a
transform pipeline, and a pure-Python runtime with one dependency
(elementpath, for XSD regular expressions). The node shape, the CLI
flags, and the XSD subset all survive; almost everything else around them
was modernized.
This document covers what changed, why, and how to move your code.
Why the architecture changed¶
pyxsd 0.1 generated classes with an imperative type() factory and kept
class metadata (_elementNames_, _attributeNames_) in manually managed
class dictionaries. 1.0 keeps the same generated-class idea but uses the
modern machinery:
types.new_classreplaces thetype()factory, so generated classes behave like ordinary classes underinspect,copy, and pickle probes.__set_name__on theElement/Attributedescriptors wires each descriptor to its owner and attribute name at class creation — no manual bookkeeping lists to keep in sync.__init_subclass__onSchemaBasecollects the element/attribute name lists automatically through the MRO.Descriptive
__getattr__on generated classes turns misspelled attribute access into an error message that names your class and lists its declared elements and attributes.ValidationReportreplaces scatteredprintstatements: all non-fatal issues are structured objects with severity, stable code, message, and optional element name. A schema reports atSchema.report; a bound document reports the merged run atDocument.report, andrequire_valid()turns errors into a raisedValidationErrorcarrying that report.loggingreplaces prints for progress/trace output. The library never configures handlers; the CLI maps-v/-qto DEBUG/CRITICAL.pathlib.Path,argparse,abc.ABC, context managers, and lazyimportlibloading replace the Python-2 era equivalents (imp,optparse, ad-hoc file handling,sys.pathhacks).Schema composition (
include/import/redefine) is implemented by splicing the composed documents into the main schema tree before class generation, and identity constraints (key/unique/keyref) run as a post-parse validation pass with an XPath subset.
Breaking changes¶
Python and packaging¶
0.1 |
1.0 |
|---|---|
Python 2.3+, separate ElementTree/cElementTree required |
Python 3.11+ only; one pure-Python runtime dependency ( |
|
|
|
|
Modules and imports¶
0.1 |
1.0 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
moved to |
the |
|
|
|
|
|
All internal module names are now snake_case and importable as such; every
layout change was a git mv, so history follows the files.
Crystallography transforms¶
The eight application transforms (CellSizer, SphereCutter,
ExpandCell, BravaisLattice, CoordViewer, FormatForVisit, plus the
Atom/Vector helper libraries) moved from the package to
examples/legacy/. Importing pyxsd.transforms.cellSizer (or any
snake_case variant) now raises ImportError. The installed package keeps
the framework (Transform, Displayer, iter_tree) and the generic
built-ins (PrintData and ToDict). See
examples/legacy/README.md for how to run them.
The PyXSD pipeline → Schema and Document¶
The biggest change: the monolithic PyXSD class is gone. In 0.1,
constructing PyXSD(xmlFileInput=..., xsdFile=..., ...) ran the whole
pipeline — parse the schema, bind the instance, write outputs, run
transforms — inside __init__. 1.0 splits that into explicit objects
with one job each:
# 0.1
from pyxsd.pyXSD import PyXSD
parser = PyXSD(
xmlFileInput="inventory.xml",
xsdFile="inventory.xsd",
xmlFileOutput=False,
)
root = parser.schemaRootInstance
for issue in parser.report.issues:
print(issue.format())
# 1.0
import pyxsd
schema = pyxsd.Schema.compile("inventory.xsd")
schema.require_valid() # raises pyxsd.ValidationError if the schema is bad
document = schema.parse("inventory.xml")
document.require_valid()
root = document.root
for issue in document.report.issues:
print(issue.format())
The mapping, piece by piece:
0.1 |
1.0 |
|---|---|
|
|
|
|
|
|
checking |
|
|
|
|
|
|
write the returned document yourself: |
walking the tree by hand to read values |
|
XSD 1.0-only schemas |
|
Each parse returns a fresh Document, so one compiled schema can serve
many documents (and many parses of the same document) without their
reports mixing.
Transforms: strings → callables¶
In 0.1, library users passed transform call strings
(transforms=["PrintData()"]) that pyxsd resolved by class name at run
time. In 1.0, Document.transform takes the callable itself:
def normalize_units(root):
... # mutate the tree or return a new root
return root
updated = document.transform(normalize_units)
updated.write("normalized.xml")
A transform that returns a tree root comes back as a new Document
against the same schema; any other return value, including None, is
returned unchanged. The original document remains available when a
callable mutates it in place. A returned document shares the pre-transform
report — after a structural change, call updated.revalidate() for a
report that reflects the new shape (that is also the replacement for 0.1’s
SendTreeToPyXSD transform, which re-fed the written tree back through
the pipeline).
The shipped Transform classes still work — __init__ takes the root,
__call__ does the work — they are just invoked through a one-line
wrapper (see Using transforms).
On the command line, nothing changes: -t 'PrintData()',
>-chained calls, and -T transform files are exactly the syntax they
were; the CLI resolves the strings to callables for you.
CLI¶
Same flag set (
-i,-s,-p,-k,-o,-d,-t,-T,-c,-v,-q), plus--strict,--mode strict|lax,--namespaces strict|legacy, and--xsd-version 1.0|1.1.Transform calls must be calls with parentheses (
-t 'PrintData()'); a bare class name is now a usage error. Calls are parsed withast.literal_eval— only literal arguments are accepted (0.1 usedeval). This call-string syntax is unchanged by the library-side move to callable transforms: the CLI resolves the strings to callables itself.-tand-Tare mutually exclusive;-vand-qare mutually exclusive (both exit 2).stdin input is read directly (no
stdin.xmltemp file).Exit codes are meaningful:
0success,1fatal error or--strictwith errors,2usage error. Validation issues go to stderr as a rendered report.Default output filenames changed where 0.1 was buggy: the transformed output default is now derived from the input name (0.1 silently reused the input file name); nameless/file-like inputs write
output.xmlin the current directory.
Data types¶
Class names use the true XSD spelling: the class for
xs:stringisString, but its.nameattribute is"string"(0.1 used"String");Doubleno longer reports itself as"Float", and the misspelled"NonPostive"is now"NonPositiveInteger".Lexical validation happens in
__new__and raisesTypeErroron invalid input — previously many invalid values slipped through or crashed with exotic errors.Booleanhas avalattribute,str()→"true"/"false",repr()→"True"/"False"(0.1 raisedNameErroron repr).Empty simple content (
<x/>) yields""— not"True"(a latent 0.1 bug wheredataTypeVal = Truewas stringified).
Instances and descriptors¶
Typed primitive values are stored as
_value_ = [<one string>]— a list contract, consistently (0.1 sometimes stored a bare string, which the writers then char-split).Element-level assignment now works and validates:
item.quantity = Integer(4)stores the value; in 0.1 the descriptor silently discarded it.Attributes remain lexical strings in
_attribs_(so round-trip output is faithful); typed attribute values fromdefault/fixedapplication land in the instance__dict__.Class-level access to element descriptors returns the descriptor (0.1 crashed); instance-level assignment is type-checked.
The quirk where an element named
nameshadows the generated class’s metadatanameis preserved; prefer__name__for the class name.
Errors and reporting¶
printstatements →logging(library default WARNING) plus theValidationReport. Codes are stable strings — see Validation.Schema problems raise or record
PyXSDError(0.1 mixedValueErrorand raw stringraisestatements). Invalid schemas and documents do not raise by default:Schema.require_valid()andDocument.require_valid()raisepyxsd.ValidationError(carrying the failing report) when you want errors to be fatal.Instance validation is more complete and more correct: attribute defaults/prohibitions/fixed, element defaults/fixed/nil, abstract checks, substitution groups,
xsi:type, and identity constraints all report through codes that did not exist in 0.1.The double-processing bug is fixed: 0.1 constructed every grandchild element twice (each child processed by both its parent’s loop and the factory), causing spurious duplicate children and bogus “Order Error”s on inline
complexTypeschemas.
Upgrade guide¶
Command-line users¶
Your existing invocations mostly work as-is. Check:
Transform calls need parentheses:
PrintData()notPrintData.Crystallography transforms must be run from
examples/legacy/(or copied next to your data).Add
--strictwhere a CI pipeline needs a failure signal.Move your log parsing from “grep stdout” to the rendered report on stderr (or the issue objects on
document.reportwhen scripting the library).
Library users¶
# 0.1
from pyxsd.pyXSD import PyXSD
parser = PyXSD(
xmlFileInput="data.xml",
xsdFile="data.xsd",
xmlFileOutput="_No_Output_",
transformOutputName="_No_Output_",
)
# ... hope the prints were useful
# 1.0
import pyxsd
schema = pyxsd.Schema.compile("data.xsd")
schema.require_valid() # raise on a bad schema
document = schema.parse("data.xml")
if document.report.has_errors:
for issue in document.report.issues:
print(issue.format())
root = document.root
Notes:
Compilation and binding are separate steps:
Schema.compilebuilds the schema (and its classes) once; everyschema.parse(xml)returns a freshDocumentfor one instance.pyxsd.parse(xml)is the shortcut that also resolves the schema from the instance’s own schema hints.Nothing is written unless you ask:
document.write(path)/document.to_string()replace thexmlFileOutput/transformOutputNameoptions.Transforms are callables passed to
document.transform(fn)— call strings are a CLI-only syntax now.require_valid()raisespyxsd.ValidationErrorwhose.reportattribute holds every issue;document.is_validis the boolean form.Import paths follow the module renames table above; writer APIs (
XmlTreeWriter,XmlTagWriter) are unchanged in behavior.
History¶
The 0.1 codebase’s story — ORNL 2006, the mentors, the design intent — is
preserved in Program origins. Every structural move in the 1.0
modernization was done with git mv so that history remains traceable
through the repository itself.