Skip to content

Schema generator

The Schema.org models are generated by tools/schema-generator, a development tool that is not published to PyPI. Its source of truth is the official machine-readable ontology.

Usage

From the repository root:

uv run goatseo-schema generate
Option Effect
(none) Use the newest release vendored in tools/schema-generator/data
--version latest Download the latest release announced by Schema.org, then generate
--version 30.1 Download (or reuse) a specific release
--source FILE Use a local schemaorg-*-https.nt file
--schema-version X Version label when the --source file name has none
--output DIR Write elsewhere than packages/goatseo-schema/src/goatseo/schema
--include-pending Also generate terms from pending.schema.org
--check Write nothing; exit with status 1 if the generated files are out of date
uv run goatseo-schema generate --version latest
uv run goatseo-schema generate --source schemaorg-all-https.nt --output generated/
uv run goatseo-schema generate --check   # used by CI

Downloaded releases are saved as tools/schema-generator/data/schemaorg-<version>-current-https.nt. Commit them: the vendored file makes every regeneration reproducible offline.

Pipeline

schemaorg-current-https.nt
        │  ntriples.py   strict N-Triples reader
        ▼
     triples
        │  parser.py     classes, properties, enumerations, layers
        ▼
 Internal representation (ir.py)
        │  mapper.py     Schema.org types to Python types and names
        ▼
 Python declarations
        │  pyrender.py   source code
        ▼
 Ruff (import sorting, format)
        ▼
 _types.py, _enumerations.py, _version.py

The RDF never reaches Python code directly: everything goes through the intermediate representation.

Internal representation

goatseo_schema_generator.ir holds frozen dataclasses:

  • SchemaType: name, parent types (multiple inheritance), properties, description, kind (CLASS, ENUMERATION, DATATYPE), layer, superseded_by, enumeration members.
  • SchemaProperty: name, domainIncludes, rangeIncludes, description, layer, superseded_by, inverseOf.
  • EnumerationMember: name, the enumerations it belongs to, superseded_by.
  • Ontology: the closed vocabulary, with ancestors, descendants and children queries.

The parser keeps the core layer and the health-lifesci, bib and auto extensions by default. When a parent or range belongs to an excluded layer, it is replaced by its nearest included ancestor or dropped, so the IR never refers to a missing type.

Type mapper

  • Data types map to Python types (Text to str, Date to datetime.date, ...).
  • Classes map to model classes; the unions of ranges become union annotations, always accepting a list of values too.
  • Enumerations with members become StrEnum classes; enumerations without members become models. Large enumeration unions collapse to SchemaEnumeration.
  • Redundant bases are removed and the method resolution order of every class is checked, so a broken hierarchy fails the generation instead of the import.
  • Name collisions (keywords, Pydantic reserved names, two properties mapping to the same snake_case name) are fixed or reported as errors.

Determinism

Every collection is sorted before rendering and the output is formatted by Ruff with the configuration that applies to the target file. Running the generator twice on the same input produces byte-identical files, which is what --check verifies in CI.

Updating Schema.org

uv run goatseo-schema generate --version latest
uv run pytest && uv run pyright
git add tools/schema-generator/data packages/goatseo-schema