# 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 zero-dependency runtime. 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_class`** replaces the `type()` factory, so generated classes
behave like ordinary classes under `inspect`, `copy`, and pickle probes.
- **`__set_name__`** on the `Element`/`Attribute` descriptors wires each
descriptor to its owner and attribute name at class creation — no manual
bookkeeping lists to keep in sync.
- **`__init_subclass__`** on `SchemaBase` collects 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.
- **`ValidationReport`** replaces scattered `print` statements: all
non-fatal issues are structured objects with severity, stable code,
message, and optional element name, available at `PyXSD.report` and to
any generated class through the parser back-reference.
- **`logging`** replaces prints for progress/trace output. The library
never configures handlers; the CLI maps `-v`/`-q` to DEBUG/CRITICAL.
- **`pathlib.Path`, `argparse`, `abc.ABC`, context managers, and lazy
`importlib` loading** replace the Python-2 era equivalents (`imp`,
`optparse`, ad-hoc file handling, `sys.path` hacks).
- 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, standard library only (zero runtime dependencies) |
| `setup.py` install, Windows exe installer | `pyproject.toml` + hatchling; `pip install pyxsd`; wheel + sdist |
| `pyXSD.py` script | `pyxsd` console script (also `python -m pyxsd`) |
### Modules and imports
| 0.1 | 1.0 |
| --- | --- |
| `pyxsd/pyXSD.py` | `pyxsd/parser.py` |
| `pyxsd/schemaBase.py` | `pyxsd/schema_base.py` |
| `pyxsd/xsdDataTypes.py` | `pyxsd/xsd_data_types.py` |
| `pyxsd/elementRepresentatives/` (camelCase modules) | `pyxsd/element_representatives/` (snake_case modules) |
| `pyxsd/writers/xmlTreeWriter.py` | `pyxsd/writers/xml_tree_writer.py` |
| `pyxsd/writers/xmlTagWriter.py` | `pyxsd/writers/xml_tag_writer.py` |
| `pyxsd/transforms/cellSizer.py` etc. | **moved to `examples/legacy/`** |
| `from pyxsd.pyXSD import PyXSD` | `from pyxsd import PyXSD` |
| `from pyxsd.writers.xmlTreeWriter import XmlTreeWriter` | `from pyxsd.writers.xml_tree_writer import XmlTreeWriter` |
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`, `SendTreeToPyXSD`). See
`examples/legacy/README.md` for how to run them.
### CLI
- Same flag set (`-i`, `-s`, `-p`, `-k`, `-o`, `-d`, `-t`, `-T`, `-c`,
`-v`, `-q`), plus new `--strict`.
- Transform calls must be **calls with parentheses**
(`-t 'PrintData()'`); a bare class name is now a usage error. Calls are
parsed with `ast.literal_eval` — only literal arguments are accepted
(0.1 used `eval`).
- `-t` and `-T` are mutually exclusive; `-v` and `-q` are mutually
exclusive (both exit 2).
- stdin input is read directly (no `stdin.xml` temp file).
- Exit codes are meaningful: `0` success, `1` fatal error or `--strict`
with errors, `2` usage 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.xml` in
the current directory.
### Data types
- Class names use the **true XSD spelling**: the class for `xs:string` is
`String`, but its `.name` attribute is `"string"` (0.1 used
`"String"`); `Double` no longer reports itself as `"Float"`, and the
misspelled `"NonPostive"` is now `"NonPositiveInteger"`.
- Lexical validation happens in `__new__` and raises `TypeError` on
invalid input — previously many invalid values slipped through or
crashed with exotic errors.
- `Boolean` has a `val` attribute, `str()` → `"true"/"false"`,
`repr()` → `"True"/"False"` (0.1 raised `NameError` on repr).
- Empty simple content (``) yields `""` — not `"True"` (a latent 0.1
bug where `dataTypeVal = True` was stringified).
### Instances and descriptors
- Typed primitive values are stored as `_value_ = []` — 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 from `default`/`fixed`
application 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* `name` shadows the generated class's
metadata `name` is preserved; prefer `__name__` for the class name.
### Errors and reporting
- `print` statements → `logging` (library default WARNING) plus the
`ValidationReport`. Codes are stable strings — see {doc}`validation`.
- Schema problems raise or record `PyXSDError` (0.1 mixed `ValueError`
and raw string `raise` statements).
- 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 `complexType` schemas.
## Upgrade guide
### Command-line users
Your existing invocations mostly work as-is. Check:
1. Transform calls need parentheses: `PrintData()` not `PrintData`.
2. Crystallography transforms must be run from
`examples/legacy/` (or copied next to your data).
3. Add `--strict` where a CI pipeline needs a failure signal.
4. Move your log parsing from "grep stdout" to the rendered report on
stderr (or the JSON/tuple API on `PyXSD.report`).
### Library users
```python
# 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
from pyxsd import PyXSD
parser = PyXSD(xmlFileInput="data.xml", xsdFile="data.xsd", xmlFileOutput=False)
if parser.report.has_errors:
for issue in parser.report.issues:
print(issue.format())
root = parser.schemaRootInstance
```
Notes:
- Construction runs the whole pipeline (parse → validate → write →
transforms). `PyXSD.report` holds every issue found.
- `xmlFileOutput=False` (or `"_No_Output_"`) suppresses the parsed-tree
write; a filename or `True` (default name) writes it.
- `transformOutputName` accepts a filename or `"stdout"`; with `transforms`
present, output is always written somewhere.
- Import paths follow the module renames table above; writer APIs
(`XmlTreeWriter`, `XmlTagWriter`) are unchanged in behavior.
### Transform authors
- Inherit from `pyxsd.transforms.Transform` (now an ABC — `__init__` must
be defined, as it always had to be) and import from the package root
module:
```python
from pyxsd.transforms import Transform, Displayer, iter_tree
```
- The visitor/walker API (`walk`, `classCollector`, `tagCollector`,
`tagFinder`, `getElementsByName`, `find`/`findAll`, `makeElemObj`,
`makeCommentElem`) is unchanged. `iter_tree(instance)` is new and
recommended over manual recursion.
- `getInstancesByClassName` no longer crashes (`collection.append` typo
fixed); transforms that write files now flush and close them reliably.
- User transform libraries import siblings with plain `from library import
...` — the loader resolves sibling imports from any directory.
### Schema authors
Your schemas keep working, with more of them validating correctly:
- `xs:all`, groups, attributeGroups, unions, wildcards, substitution
groups, element refs, `xsi:type`, nil/default/fixed, abstract/final —
all new or fixed in 1.0.
- Facet declarations on user simpleTypes are parsed but **not enforced**
(except whitespace collapse on built-ins) — see the gaps table in
{doc}`supported`.
- Schemas must be well-formed XML; composition errors (missing include,
cycles, namespace mismatch) are reported with dedicated codes.
## History
The 0.1 codebase's story — ORNL 2006, the mentors, the design intent — is
preserved in {doc}`history/origins`. Every structural move in the 1.0
modernization was done with `git mv` so that history remains traceable
through the repository itself.