Architecture¶
oold-python is organized into four cooperating layers: the model core, the code generator, the backend system, and an optional UI layer. This page explains how they fit together.
Component overview¶
graph TD
subgraph Schema["Schema Layer"]
JS["JSON Schema / OO-LD"]
end
subgraph Gen["Code Generator"]
G["Generator\n(generator.py)"]
DCG["datamodel-code-generator"]
G --> DCG
JS --> G
DCG -->|"writes .py"| PM["Generated Pydantic models\n(model/model.py)"]
end
subgraph Core["Model Core"]
GLBM["GenericLinkedBaseModel\n(static.py)"]
LBM["LinkedBaseModel\n(model/__init__.py)"]
BC["BaseController\n(model/__init__.py)"]
PM --> LBM
LBM -.->|inherits| GLBM
BC -.->|mixin| LBM
end
subgraph Backends["Backend Layer"]
BI["Backend interface\n(backend/interface.py)"]
DS["SimpleDictDocumentStore\n/ SqliteDocumentStore"]
SP["LocalSparqlBackend\n(backend/sparql.py)"]
EXT["Custom backends"]
BI --> DS
BI --> SP
BI --> EXT
end
subgraph Serialization["Serialization"]
JSONLD["to_jsonld()\nJSON-LD document"]
RDFLIB["RDFLib Graph\n(rdflib)"]
JSONLD --> RDFLIB
end
subgraph UI["UI Layer (optional)"]
PANEL["Panel widgets"]
NICEGUI["NiceGUI components"]
WIDGET["anywidget (Jupyter)"]
end
LBM -->|"resolve IRI"| BI
LBM -->|"store_jsonld()"| BI
LBM --> JSONLD
LBM --> UI
Layer descriptions¶
Schema Layer¶
OO-LD schemas are standard JSON Schema documents extended with a range keyword that marks string fields as IRI references to other schemas. Schemas can compose via allOf and reference each other via $ref.
Code Generator (generator.py)¶
Generator.generate() accepts a list of schema dicts and writes a .py file containing Pydantic classes. It delegates to datamodel-code-generator for the actual Python source generation, then post-processes the output to wire up oold-python's IRI-resolution machinery.
Model Core¶
GenericLinkedBaseModel (static.py) is the base class. It adds:
- JSON-LD context injection via
json_schema_extra to_jsonld()/to_json()serialization that replaces Python object references with IRIs- A
_typesregistry mapping IRI type strings to Python classes
LinkedBaseModel (model/__init__.py) extends the generic base with:
- IRI-transparent field validation: fields annotated with
rangeaccept both objects and IRI strings - Lazy resolution via
__get__descriptors - IRIs are resolved on first attribute access - Class-level
[]subscript operator for direct IRI lookup cast()for cross-model conversion
BaseController is a mixin for adding runtime state. See BaseController.
Backend Layer¶
All backends implement the Backend interface from backend/interface.py:
| Backend | Storage | SPARQL |
|---|---|---|
SimpleDictDocumentStore |
In-memory dict, optional JSON file | No |
SqliteDocumentStore |
SQLite database | No |
LocalSparqlBackend |
In-memory RDFLib graph | Yes |
Backends are registered per IRI prefix via set_resolver / set_backend, so multiple backends can coexist in one application.
Serialization¶
to_jsonld() produces a self-describing JSON-LD document. Object references are serialized as IRI strings, not as embedded objects - keeping the graph flat and enabling partial loading. The output can be fed directly into RDFLib or any JSON-LD aware triple store.
UI Layer (optional)¶
oold.ui contains optional integrations for Panel, NiceGUI, and Jupyter anywidget. These are not installed by default.
Data flow¶
sequenceDiagram
participant Dev as Developer
participant Gen as Generator
participant Model as LinkedBaseModel
participant Backend as Backend
participant RDF as RDFLib
Dev->>Gen: provide JSON schemas
Gen-->>Dev: generated model.py
Dev->>Model: instantiate(id="ex:foo", ...)
Dev->>Backend: store(nodes={"ex:foo": foo})
Dev->>Model: foo.bar (IRI reference)
Model->>Backend: resolve_iris(["ex:bar"])
Backend-->>Model: Bar instance
Dev->>Model: foo.to_jsonld()
Model-->>RDF: JSON-LD document
RDF-->>Dev: SPARQL results
- Schema → model:
Generatorconverts JSON schemas into typed Pydantic classes - Instantiation: models are created like any Pydantic class; IRI-valued fields are stored as strings
- Persistence:
store()serializes instances and writes to the backend - Resolution: accessing an IRI-valued attribute triggers a backend lookup; the result is cached on the instance
- Serialization:
to_jsonld()replaces all resolved objects with their IRIs, producing a flat JSON-LD graph
Key design decisions¶
IRI transparency - the same field can hold either an object or an IRI. This means you can work with partial graphs (load only what you need) and still produce correct JSON-LD.
Schema-first - all semantic meaning lives in the JSON schema, not in Python class annotations. This makes schemas portable across languages and tools.
Pluggable backends - no single storage technology is assumed. Swapping backends requires only re-registering the prefix; model code is unchanged.
Controller separation - runtime behavior is kept out of data models entirely, so serialization is always deterministic and backend-independent.