class documentation

class Schema:

Constructor: Schema(name, version)

View In Hierarchy

In-memory representation of a DataGraphs domain model schema.

A Schema instance tracks every change applied to it over its lifetime and exposes change_report to emit a deterministic, net-effect, semantically-annotated changelog relative to the state at construction. Tracking is always on and adds negligible overhead for typical schema sizes.

Every public mutating method is atomic (all-or-nothing): a rollback transaction is opened at the outermost call boundary and replayed on any exception, so a method that raises leaves the schema completely unchanged — never a partial write. The transaction is scoped to the operation's footprint (a shallow class-list snapshot plus a property-granular undo journal), so its cost is proportional to what the operation touches, not to the schema size — building an N-class schema is O(N), not O(N²). Compound mutations (e.g. create_subclass, or any apply_to_subclasses cascade) are covered as a single unit: their inner self-calls share the outer transaction and never open a nested one. Because a rolled-back operation records nothing, change_report never surfaces a change for an operation the caller saw raise.

Static Method create_from Create a Schema from a dictionary.
Method __init__ Create a new empty schema.
Method assign_baseclass Set or change the parent (base) class for an existing class.
Method assign_class_description Set or clear the description of a class.
Method assign_label_autogen Set an auto-generation pattern on the label property of a class.
Method assign_label_property Designate an existing property as the label property for a class.
Method assign_property_orders Reorder properties within classes.
Method change_report Return a net-effect changelog of all changes since construction.
Method clone Create a deep copy of the schema.
Method create_class Create a new class in the schema.
Method create_property Create a new property on a class.
Method create_subclass Create a subclass that inherits all properties from the parent class.
Method delete_class Delete a class from the schema.
Method delete_property Remove a property from a class.
Method find_baseclasses Find all direct or recursive base classes of a given class.
Method find_class Find a class definition by name.
Method find_property Find a property by name within a list of property dicts.
Method find_subclasses Find all direct or recursive subclasses of a given class.
Method rename_property Rename a property.
Method to_dict Convert the schema to a plain dictionary.
Method to_json Serialise the schema to a JSON string.
Method update_class Update a class's name, description, or parent class.
Method update_property Update an existing property on a class.
Method update_schema_metadata Update the schema's name, version, and last modified date.
Constant ALL_CLASSES Undocumented
Property classes The list of class definitions in the schema.
Property version The schema version string.
Static Method _descendants Transitive descendants of baseclass in BFS order, off a prebuilt index.
Static Method _is_legacy_format Detect whether a schema dict uses the legacy (old) format.
Method _apply_metadata Mutate the schema's name/version/last-modified — the untracked core.
Method _assign_datatype Undocumented
Method _assign_enum Undocumented
Method _assign_inverse_of Undocumented
Method _assign_is_array Undocumented
Method _assign_is_filterable Undocumented
Method _assign_is_optional Undocumented
Method _assign_is_synonym Undocumented
Method _assign_property_description Undocumented
Method _children_index Build the parent-name -> direct-children-names index in ONE O(C) pass.
Method _class_indices Build the (name -> class_def) and (parent -> children) indices in ONE pass.
Method _create_property_on_class Create one property on one already-resolved class dict.
Method _delete_linked_properties Undocumented
Method _get_description_text Extract plain text from a description (handles both str and dict).
Method _is_valid_inverse_of Validate that inverse_of on datatype points back at class_name.
Method _is_valid_inverse_range Is range_name an acceptable range for a property pointing back at class_name?
Method _make_description Create a description dict in the new format.
Method _set_internal_schema Undocumented
Method _transitive_subclass_names Names of every transitive subclass of baseclass, in BFS order.
Method _update_property_on_class Update one already-resolved property on one already-resolved class.
Method _validate_schema Undocumented
Instance Variable _schema Undocumented
Instance Variable _tracker Undocumented
Instance Variable _version Undocumented
@staticmethod
def create_from(data: dict, version: str = '') -> Self:

Create a Schema from a dictionary.

Automatically detects and converts legacy-format schemas.

Parameters
data:dictSchema dictionary (new or legacy format).
version:strSchema version override.
Returns
SelfA new Schema instance.
Raises
SchemaErrorIf the dict is missing required keys.
def __init__(self, name: str = '', version: str = ''):

Create a new empty schema.

Parameters
name:strModel name. Defaults to 'Domain Model' if empty.
version:strSchema version. Defaults to '1.0' if empty.
Raises
TypeErrorIf a dict is passed instead of keyword arguments.
def assign_baseclass(self, class_name: str, parent_class_name: str):

Set or change the parent (base) class for an existing class.

Parameters
class_name:strThe class to modify.
parent_class_name:strThe new parent class name.
Raises
ClassNotFoundErrorIf class_name does not exist.
def assign_class_description(self, class_name: str, description: str):

Set or clear the description of a class.

Parameters
class_name:strClass name.
description:strNew description. Pass an empty string to remove it.
Raises
ClassNotFoundErrorIf the class does not exist.
def assign_label_autogen(self, class_name: str, pattern: str):

Set an auto-generation pattern on the label property of a class.

Parameters
class_name:strClass name.
pattern:strAuto-generation expression.
Raises
ClassNotFoundErrorIf the class does not exist.
PropertyNotFoundErrorIf the label property does not exist.
def assign_label_property(self, class_name: str, prop_name: str, is_lang_string: bool = True):

Designate an existing property as the label property for a class.

The property is also marked as required (isOptional=False).

Parameters
class_name:strClass name.
prop_name:strProperty name to use as the label.
is_lang_string:boolWhether the label supports multiple languages.
Raises
ClassNotFoundErrorIf the class does not exist.
PropertyNotFoundErrorIf the property does not exist on the class.
def assign_property_orders(self, property_orders: dict):

Reorder properties within classes.

Properties not listed in the order are appended at the end.

Parameters
property_orders:dictA dict mapping class names to ordered lists of property names.
def change_report(self, fmt: REPORT_FORMAT = REPORT_FORMAT.TEXT) -> str | list[dict]:

Return a net-effect changelog of all changes since construction.

Computes the structural delta between the baseline (state at construction) and the current schema, then annotates it with semantic intent from the op-log (renames, reorders, compound ops, label-property assignments). The result is deterministic: identical mutation sequences always yield byte-identical text and equal records regardless of dict insertion order.

This method is strictly read-only: it never mutates _schema, _baseline, or _change_log.

Supported surface / guarantees. fmt="records" is the fully-supported, guaranteed output: deterministic and complete — every structural change since the baseline is present, with its full from/to/fields/detail payload, for programmatic consumption. fmt="text" is a best-effort human-readable rendering of the same change set; it is NOT guaranteed to round-trip user-supplied field content (e.g. a description containing newlines may produce additional or ambiguous lines in the text changelog) — a documented known limitation. Cross-subclass annotation of apply_to_subclasses cascade ops in the report is likewise best-effort. Prefer fmt="records" whenever the output is parsed or relied upon.

Cost. For cascade-heavy edit histories the report is approximately O(L*C) (L cascade ops over a parent of C subclasses): a cascade op genuinely fans out to one record per annotated subclass, so the report size — and therefore its cost — is inherent to annotating C subclasses.

Note

Untracked edits via to_dict — graceful degradation. to_dict returns the live internal dict; mutations applied directly to that dict bypass the op-log entirely. Those changes are still captured by the structural diff and appear in change_report output, but without semantic intent labels: a property rename done through the dict appears as a remove + add rather than a single renamed entry, an unlogged reorder does not become a reordered entry, and so on. Use the public mutating methods to preserve full semantic annotation.

Record shape (fmt="records")

Each dict always carries:

  • "target" (str) — dotted path of the changed entity, using the current name: "ClassName" for class/metadata changes or "ClassName.propName" for property changes.
  • "kind" (str) — "class", "property", or "metadata".
  • "op" (str) — one of "added", "removed", "modified", "renamed", "reordered", "subclass_created".

The following keys are omitted (not None) when they do not apply to the entry:

  • "from" (str) — previous name; present only when op="renamed".
  • "to" (str) — new name; present only when op="renamed".
  • "fields" (list[dict]) — field-level before/after list, each entry {"field": str, "before": Any, "after": Any}; present on op="modified" and on op="renamed" when field-level changes accompany the rename.
  • "detail" (dict) — supplementary annotation dict; present for compound or annotated entries:
    • op="subclass_created": {"parent": str, "inherited": int}
    • op="reordered": {"order": list[str]}
    • op="added" / op="modified" with apply_to_subclasses: {"applied_to_subclasses": list[str]}
    • op="modified" (label-property assignment): {"label_property": str}
Parameters
fmt:REPORT_FORMAT

Output format, a ~datagraphs.enums.REPORT_FORMAT. As that enum is a ~enum.StrEnum, the equivalent string ("text" / "records") is also accepted.

  • ~datagraphs.enums.REPORT_FORMAT.TEXT (default): returns a deterministic plain-text changelog str with a header count line and per-class grouping. Best-effort human rendering — see Supported surface above.
  • ~datagraphs.enums.REPORT_FORMAT.RECORDS: returns a list[dict] of structured change records for programmatic consumption — the supported, guaranteed output. See Record shape below.
Returns
str | list[dict]A str for REPORT_FORMAT.TEXT; a list[dict] for REPORT_FORMAT.RECORDS. Returns "" (text) or [] (records) when nothing has changed since construction.
Raises
ValueErrorIf fmt is not a member (or value) of ~datagraphs.enums.REPORT_FORMAT.
def clone(self) -> Self:

Create a deep copy of the schema.

Returns
SelfA new independent Schema instance.
def create_class(self, class_name: str, description: str = '', parent_class_name: str = '', label_prop_name: str = 'label', is_label_prop_lang_string: bool = True, is_abstract: bool = False):

Create a new class in the schema.

Parameters
class_name:strName of the new class.
description:strHuman-readable description.
parent_class_name:strName of the parent class (for inheritance).
label_prop_name:strName of the label property created by default.
is_label_prop_lang_string:boolWhether the label property supports multiple languages.
is_abstract:boolUndocumented
Raises
SchemaErrorIf a class with the same name already exists.
def create_property(self, class_name: str, prop_name: str, datatype: DATATYPE | str, description: str = '', is_optional: bool = True, is_array: bool = False, is_nested: bool = False, is_lang_string: bool = True, inverse_of: str = '', enums: list | None = None, is_synonym: bool = False, is_filterable: bool | None = None, apply_to_subclasses: bool = False):

Create a new property on a class.

Parameters
class_name:strClass to add the property to.
prop_name:strProperty name.
datatype:DATATYPE | strA DATATYPE enum value for primitive types, or a class name string for object (relationship) properties.
description:strHuman-readable description.
is_optional:boolWhether the property is optional.
is_array:boolWhether the property holds multiple values.
is_nested:boolWhether an object property is nested (embedded).
is_lang_string:boolFor text properties, whether to support multiple languages.
inverse_of:strName of the inverse property on the target class (object properties only).
enums:list | NoneAllowed values for DATATYPE.ENUM properties.
is_synonym:boolWhether this property is a label synonym.
is_filterable:bool | NoneWhether the property is available as a facet/filter.
apply_to_subclasses:boolIf True, also creates the property on all existing subclasses.
Raises
ClassNotFoundErrorIf the class (or referenced class) does not exist.
PropertyExistsErrorIf a property with the same name already exists.
InvalidInversePropertyErrorIf the inverse property specification is invalid.
def create_subclass(self, class_name: str, description: str, parent_class_name: str):

Create a subclass that inherits all properties from the parent class.

Parameters
class_name:strName of the new subclass.
description:strDescription for the subclass.
parent_class_name:strName of the parent class to inherit from.
Raises
ClassNotFoundErrorIf the parent class does not exist.
def delete_class(self, class_name: str, include_linked_properties: bool = False, cascade_to_subclasses: bool = True):

Delete a class from the schema.

Parameters
class_name:strName of the class to delete.
include_linked_properties:boolIf True, also removes ObjectProperties on other classes that reference this class.
cascade_to_subclasses:boolIf True, removes subClassOf links from any subclasses of the deleted class.
Raises
ClassNotFoundErrorIf the class does not exist.
def delete_property(self, class_name: str, prop_name: str):

Remove a property from a class.

Parameters
class_name:strClass containing the property.
prop_name:strProperty name to delete.
Raises
ClassNotFoundErrorIf the class does not exist.
PropertyNotFoundErrorIf the property does not exist.
def find_baseclasses(self, subclass: str, recursive: bool = False) -> list[dict]:

Find all direct or recursive base classes of a given class.

Parameters
subclass:strThe child class name.
recursive:boolIf True, find all transitive base classes, not just the direct one.
Returns
list[dict]A list of class dicts that subclass descends from — just its direct parent, or the whole ancestor chain nearest-first when recursive is True. Empty if subclass does not exist, has no subClassOf, or names a parent that does not exist. Each ancestor appears exactly once, and a subClassOf cycle terminates rather than looping forever.
def find_class(self, name: str) -> dict | None:

Find a class definition by name.

Parameters
name:strThe class name to look up.
Returns
dict | NoneThe class dict, or None if not found.
def find_property(self, props: list, name: str) -> dict | None:

Find a property by name within a list of property dicts.

Parameters
props:listList of property dicts to search.
name:strThe property name to look up.
Returns
dict | NoneThe property dict, or None if not found.
def find_subclasses(self, baseclass: str, recursive: bool = False) -> list[dict]:

Find all direct or recursive subclasses of a given class.

Parameters
baseclass:strThe parent class name.
recursive:boolIf True, find all transitive subclasses, not just direct ones.
Returns
list[dict]A list of class dicts descending from baseclass — its direct children, or every transitive descendant when recursive is True. Empty if baseclass has no children or does not exist. Each descendant appears exactly once, and a subClassOf cycle terminates rather than looping forever.
def rename_property(self, class_name: str, old_prop_name: str, new_prop_name: str):

Rename a property.

If the property is the class's label property, the label property reference is updated automatically.

Parameters
class_name:strClass containing the property.
old_prop_name:strCurrent property name.
new_prop_name:strNew property name.
Raises
ClassNotFoundErrorIf the class does not exist.
PropertyNotFoundErrorIf old_prop_name does not exist.
PropertyExistsErrorIf new_prop_name is already in use.
def to_dict(self) -> dict:

Convert the schema to a plain dictionary.

Returns
dictThe schema as a dict.
def to_json(self) -> str:

Serialise the schema to a JSON string.

Returns
strA JSON-formatted string.
def update_class(self, class_name: str, new_name: str = '', new_description: str = '', parent_class_name: str = ''):

Update a class's name, description, or parent class.

Parameters
class_name:strCurrent class name.
new_name:strNew class name, or empty to leave unchanged.
new_description:strNew description, or empty to leave unchanged.
parent_class_name:strNew parent class. Empty string removes the parent.
Raises
ClassNotFoundErrorIf the class does not exist.
def update_property(self, class_name: str, prop_name: str, datatype: DATATYPE | str | None = None, description: str | None = None, is_optional: bool | None = None, is_array: bool | None = None, is_nested: bool | None = None, is_lang_string: bool | None = None, inverse_of: str = '', enums: list | None = None, is_synonym: bool = False, is_filterable: bool | None = None, apply_to_subclasses: bool | None = None):

Update an existing property on a class.

Only parameters that are explicitly provided (non-None) will be changed.

Parameters
class_name:strClass containing the property.
prop_name:strProperty name to update.
datatype:DATATYPE | str | NoneNew data type.
description:str | NoneNew description.
is_optional:bool | NoneWhether the property is optional.
is_array:bool | NoneWhether the property holds multiple values.
is_nested:bool | NoneWhether an object property is nested.
is_lang_string:bool | NoneWhether the property supports multiple languages.
inverse_of:strName of the inverse property on the target class. Validated against datatype when one is given, otherwise against the property's existing range, so an inverse can be added without restating the type.
enums:list | NoneAllowed enumeration values.
is_synonym:boolWhether this property is a label synonym.
is_filterable:bool | NoneWhether the property is available as a filter.
apply_to_subclasses:bool | NoneIf True, also updates the property on all existing subclasses.
Raises
ClassNotFoundErrorIf the class does not exist.
PropertyNotFoundErrorIf the property does not exist.
InvalidInversePropertyErrorIf inverse_of does not name an object property on the target class whose range is class_name or one of its base classes.
def update_schema_metadata(self, name: str = '', version: str = ''):

Update the schema's name, version, and last modified date.

Parameters
name:strNew name for the schema. If empty, the name is unchanged unless it was previously empty, in which case it defaults to 'Domain Model'.
version:strNew version string. If empty, the version is unchanged unless it was previously empty, in which case it defaults to '1.0'.
ALL_CLASSES: str =

Undocumented

Value
'__all_classes__'
@property
classes: list[dict] =

The list of class definitions in the schema.

@property
version: str =

The schema version string.

@staticmethod
def _descendants(baseclass: str, children: dict[str, list[str]]) -> list[str]:

Transitive descendants of baseclass in BFS order, off a prebuilt index.

ITERATIVE (explicit queue), so a subClassOf chain thousands of levels deep cannot exceed Python's recursion limit (FIX round-4 B4). Each class is visited at most once (cycle-safe).

@staticmethod
def _is_legacy_format(schema: dict) -> bool:

Detect whether a schema dict uses the legacy (old) format.

def _apply_metadata(self, name: str = '', version: str = ''):

Mutate the schema's name/version/last-modified — the untracked core.

Shared by update_schema_metadata (which wraps this in the tracking + atomic guards and records the op) and by construction paths (__init__ / _set_internal_schema), which apply metadata before a tracker exists and must not record a change.

Parameters
name:strNew model name (see update_schema_metadata).
version:strNew version string (see update_schema_metadata).
def _assign_datatype(self, prop_def: dict, datatype: DATATYPE | str, is_nested: bool = False, is_lang_string: bool = True):

Undocumented

def _assign_enum(self, prop_def: dict, datatype: DATATYPE | str, enums: list):

Undocumented

def _assign_inverse_of(self, prop_def: dict, class_name: str, inverse_of: str, datatype: DATATYPE | str):

Undocumented

def _assign_is_array(self, prop_def: dict, is_array: bool = False):

Undocumented

def _assign_is_filterable(self, prop_def: dict, is_filterable: bool | None = None):

Undocumented

def _assign_is_optional(self, prop_def: dict, is_optional: bool = False):

Undocumented

def _assign_is_synonym(self, prop_def: dict, is_synonym: bool):

Undocumented

def _assign_property_description(self, prop_def: dict, description: str):

Undocumented

def _children_index(self) -> dict[str, list[str]]:

Build the parent-name -> direct-children-names index in ONE O(C) pass.

Built once per outermost cascade so the iterative descendant walk is O(descendants) rather than an O(C) find_subclasses scan per level (FIX round-4 B3 — the relocated op-time quadratic).

def _class_indices(self) -> tuple[dict[str, dict], dict[str, list[str]]]:

Build the (name -> class_def) and (parent -> children) indices in ONE pass.

Both indices back the cascade in O(descendants): the name index makes the atomic pre-validation O(targets) (an O(1) lookup per target rather than an O(C) find_class scan), and the children index drives the iterative descendant walk — together eliminating the O(C^2) cascade (FIX round-4 B3).

def _create_property_on_class(self, class_def: dict, owner_class_name: str, prop_name: str, datatype: DATATYPE | str, description: str, is_optional: bool, is_array: bool, is_nested: bool, is_lang_string: bool, inverse_of: str, enums: list, is_synonym: bool, is_filterable: bool | None):

Create one property on one already-resolved class dict.

The single-class core shared by create_property and its cascade. The caller pre-validates existence/duplicate; this core may still raise mid-apply (_assign_datatype on a missing object range, _assign_inverse_of on an invalid inverse) AFTER appending the half-built dict — the caller's outermost _tracker.atomic guard rolls the model back on any such raise, so the overall create is all-or-nothing. inverse_of is resolved against owner_class_name (each target's own class name, matching the prior per-subclass recursion's inverse validation exactly).

def _delete_linked_properties(self, class_name: str):

Undocumented

def _get_description_text(self, desc: str | dict) -> str:

Extract plain text from a description (handles both str and dict).

def _is_valid_inverse_of(self, class_name: str, inverse_of: str, datatype: DATATYPE | str) -> bool:

Validate that inverse_of on datatype points back at class_name.

The inverse property must be an object property on datatype whose range is class_name or one of its base classes — a range naming a base class already accepts every instance of class_name, so it does point back.

Parameters
class_name:strName of the class containing the property.
inverse_of:strName of the inverse property on datatype to validate.
datatype:DATATYPE | strDatatype of the property.
Returns
boolTrue if the inverse property is valid; never returns False, as every invalid case raises.
Raises
InvalidInversePropertyErrorIf datatype is not a concept, does not exist, or inverse_of is missing, is not an object property, or does not point back at class_name or one of its base classes.
def _is_valid_inverse_range(self, range_name: str | None, class_name: str) -> bool:

Is range_name an acceptable range for a property pointing back at class_name?

class_name itself is valid, and so is any class it descends from: a property whose range is a base class already accepts instances of every subclass, so an inverse pointing at an ancestor does point back at class_name. The exact match is tested first so the common case costs nothing.

def _make_description(self, text: str) -> dict:

Create a description dict in the new format.

def _set_internal_schema(self, data: dict, version: str):

Undocumented

def _transitive_subclass_names(self, baseclass: str) -> list[str]:

Names of every transitive subclass of baseclass, in BFS order.

Mirrors the cascade footprint of apply_to_subclasses=True (direct children, their children, and so on). A class is visited at most once (cycle-safe). Builds the children index once and walks it iteratively.

def _update_property_on_class(self, class_def: dict, prop_def: dict, owner_class_name: str, datatype: DATATYPE | str, description, is_optional, is_array, is_nested, is_lang_string, inverse_of, enums, is_synonym, is_filterable):

Update one already-resolved property on one already-resolved class.

The single-class core shared by update_property and its cascade. Only explicitly-provided (non-None) fields are changed, exactly as the public method. inverse_of is resolved against owner_class_name (each target's own class name, matching the prior per-subclass recursion).

def _validate_schema(self, schema: dict):

Undocumented

_schema =

Undocumented

_tracker =

Undocumented

_version =

Undocumented