Schema Converter Specifications Developer Guide¶
This guide explains how to author, edit, and maintain specification rules, test cases, error catalogs, and generated reference pages under specs/schema_converters/.
1. Architecture and Directory Layout¶
Each schema converter (jsonschema_to_pyarrow, pydantic_to_pyarrow, xsd_to_pyarrow, pyarrow_to_jsonschema, pyarrow_to_linkml) has a specification directory under specs/schema_converters/<converter>/:
specs/schema_converters/<converter>/
├── rules.yaml # Rule catalogue (sections + rule IDs and statements)
├── spec/
│ ├── 01_<topic>/
│ │ ├── case.yaml # Case manifest (title, schema, rules, runs)
│ │ ├── schema.<ext> # Input schema (.json, .py, or .xsd)
│ │ ├── expected_schema.<ext> # Expected schema (.py, .json, or .yaml)
│ │ ├── data.<ext> / table.py # Input records (.json, .xml, or PyArrow Table .py)
│ │ ├── expected.json # Expected coerced/serialized rows as JSON
│ │ └── expected_table.py # Expected PyArrow Table (EXPECTED_TABLE / TABLE)
│ └── errors/
│ ├── manifest.yaml # Error fixture manifest (file, code, match, description)
│ └── <error_fixture>.<ext> # Invalid/unsupported schemas triggering E1..En
└── examples/
└── <real_world_example>/
└── case.yaml # Real-world domain schema cases (PubMed, UniProt, NCBI, etc.)
The manifests in specs/schema_converters/ drive two outputs:
- Parametrized specification tests:
tests/cdm_data_loaders/converters/facades/test_jsonschema_to_pyarrow_spec.pytests/cdm_data_loaders/converters/facades/test_pydantic_to_pyarrow_spec.pytests/cdm_data_loaders/converters/facades/test_xsd_to_pyarrow_spec.pytests/cdm_data_loaders/converters/facades/test_pyarrow_to_jsonschema_spec.pytests/cdm_data_loaders/converters/facades/test_pyarrow_to_linkml_spec.py- Generated Markdown specification pages:
- JSON Schema to PyArrow Specification
- Pydantic to PyArrow Specification
- XSD to PyArrow Specification
- PyArrow to JSON Schema Specification
- PyArrow to LinkML Specification
2. How Rules Are Created and Structured¶
Rules live in specs/schema_converters/<converter>/rules.yaml and are loaded by load_rules() in tests/converter_spec.py.
title: XSD to PyArrow Schema and Record Conversion Specification
intro: |
Target specification for `cdm_data_loaders.converters.facades.xsd_to_pyarrow`.
sections:
- id: scalars
title: Scalar Type Mapping (`XSD_SCALAR_SPEC` / `SCALAR_ARROW_TYPES`)
intro: |
Built-in XSD primitive and derived atomic types map to PyArrow types as follows:
generated: xsd_scalar_table
rules:
- id: R-SCALAR-BUILTIN
statement: |
Every built-in atomic `xs:*` type maps to the PyArrow `DataType` in the table above.
- Sections: Group related rules under a
titleandid. A section can includegenerated: <key>to embed a table auto-generated from converter code (such asxsd_scalar_table,jsonschema_scalar_table, orpydantic_scalar_tableinscripts/gen_converter_spec.py). - Rule IDs: Use stable, descriptive uppercase identifiers prefixed with
R-(for example,R-SCALAR-BUILTIN,R-NULL-ATTRIBUTE,R-REC-DEPTH). - Rule statements: State one testable conversion contract per rule.
- Bidirectional enforcement:
test_manifests_pass_every_rule_is_exercisedverifies that every rule ID inrules.yamlappears in therules:list of at least onecase.yaml.test_manifests_pass_every_case_rule_existsverifies that every rule ID listed in anycase.yamlis defined inrules.yaml.load_rules()rejects duplicate rule IDs.
3. How Errors Are Created and Structured¶
Converter error codes (E1, E2, ...) are defined as exception subclasses in the converter source code and paired with fixtures in spec/errors/manifest.yaml.
Source Code Error Hierarchy¶
Each converter defines a base error class and a tuple of numbered subclasses:
| Converter | Source Module | Base Class | Error Tuple |
|---|---|---|---|
jsonschema_to_pyarrow |
src/cdm_data_loaders/converters/facades/jsonschema_to_pyarrow.py |
JSONSchemaToPyArrowError |
JSONSCHEMA_TO_PYARROW_ERRORS |
pydantic_to_pyarrow |
src/cdm_data_loaders/converters/facades/pydantic_to_pyarrow.py |
PydanticToPyArrowError |
PYDANTIC_TO_PYARROW_ERRORS |
xsd_to_pyarrow |
src/cdm_data_loaders/converters/facades/xsd_to_pyarrow.py |
XsdToPyArrowError |
XSD_TO_PYARROW_ERRORS |
pyarrow_to_jsonschema |
src/cdm_data_loaders/converters/facades/pyarrow_to_jsonschema.py |
PyArrowToJSONSchemaError |
PYARROW_TO_JSONSCHEMA_ERRORS |
pyarrow_to_linkml |
src/cdm_data_loaders/converters/facades/pyarrow_to_linkml.py |
PyArrowToLinkMLError |
PYARROW_TO_LINKML_ERRORS |
Each error subclass defines:
code: unique identifier ("E1","E2", ...).title: short label rendered in the specification table.- Class docstring (
__doc__): explanation of why the converter rejects the construct.
Error Manifest (spec/errors/manifest.yaml)¶
Each entry in spec/errors/manifest.yaml maps a fixture file in spec/errors/ to an error code and message regex:
errors:
- file: wildcard_any_element.xsd
xml_tag: entry
code: E4
match: xs:any
description: "`<xs:sequence><xs:any processContents=\"lax\"/></xs:sequence>`"
Error Invariants Enforced by Tests¶
- Every class in
*_ERRORShas a uniquecode, a non-emptytitle, and a docstring. - Every
codein*_ERRORShas at least one entry inspec/errors/manifest.yaml. - Every entry in
spec/errors/manifest.yamlreferences a validcodein*_ERRORS. - Every schema file on disk in
spec/errors/is listed inspec/errors/manifest.yaml(test_manifests_pass_every_error_fixture_listed). - Running the converter on each fixture raises the exact subclass
ERRORS_BY_CODE[err.code]with a message matchingerr.match.
4. Common Developer Workflows¶
Adding a New Rule and Test Case¶
- Add the rule to
rules.yaml: - Add an entry with
idandstatementunder the appropriate section inspecs/schema_converters/<converter>/rules.yaml. - Create or update a case in
spec/<case>/(orexamples/<case>/): - Reference the new rule ID under
rules:incase.yaml. - Provide the input schema (
schema.json,schema.py, orschema.xsd),expected_schema.py(EXPECTED_SCHEMA), input data (data.jsonor*.xml),expected.json, andexpected_table.py(EXPECTED_TABLE). - Regenerate the specification page:
- Run the converter spec and documentation tests:
Adding a New Error Code¶
- Define the exception subclass:
- Subclass the converter's base error (
XsdToPyArrowError,JSONSchemaToPyArrowError, orPydanticToPyArrowError) withcode = "E<N>",title = "...", and a docstring. - Append the new class to
XSD_TO_PYARROW_ERRORS,JSONSCHEMA_TO_PYARROW_ERRORS, orPYDANTIC_TO_PYARROW_ERRORS. - Raise the new subclass in the reader or facade when the invalid construct is detected.
- Add an error fixture:
- Place a minimal schema file reproducing the error in
specs/schema_converters/<converter>/spec/errors/. - Add an entry in
specs/schema_converters/<converter>/spec/errors/manifest.yamlwithfile,code,match, anddescription. - Regenerate the specification page and run tests:
Editing or Removing an Existing Rule or Error¶
- Editing a rule statement or error docstring:
- Update the text in
rules.yamlor the error subclass docstring/title. - Run
uv run python -m scripts.gen_converter_specto syncdocs/*_specification.md. - Editing expected schema or table outputs:
- Update
expected_schema.py,expected_table.py, andexpected.jsontogether so schema validation and table roundtrip assertions agree. - Run
uv run python -m scripts.gen_converter_specto update the embedded code blocks indocs/*_specification.md. - Removing a rule or error:
- Remove the rule ID from all
case.yamlfiles before deleting it fromrules.yaml. - Remove the fixture file from
spec/errors/and its entry fromspec/errors/manifest.yamlwhen removing an error subclass from*_ERRORS. - Regenerate docs with
uv run python -m scripts.gen_converter_spec.
Adding a New Converter Specification Suite¶
- Create
specs/schema_converters/<new_converter>/rules.yaml,spec/, andspec/errors/manifest.yaml. - Register a
ConverterSpecConfigentry inCONFIGSinsidescripts/gen_converter_spec.py. - Run
uv run python -m scripts.gen_converter_specto generatedocs/<new_converter>_specification.md. - Register
docs/<new_converter>_specification.mdinzensical.toml([project].nav),docs/index.md, andREADME.md. - Verify with
uv run pytest -m docsanduv run --group docs zensical build --strict.