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 |
Create a Schema from a dictionary. |
| Method | __init__ |
Create a new empty schema. |
| Method | assign |
Set or change the parent (base) class for an existing class. |
| Method | assign |
Set or clear the description of a class. |
| Method | assign |
Set an auto-generation pattern on the label property of a class. |
| Method | assign |
Designate an existing property as the label property for a class. |
| Method | assign |
Reorder properties within classes. |
| Method | change |
Return a net-effect changelog of all changes since construction. |
| Method | clone |
Create a deep copy of the schema. |
| Method | create |
Create a new class in the schema. |
| Method | create |
Create a new property on a class. |
| Method | create |
Create a subclass that inherits all properties from the parent class. |
| Method | delete |
Delete a class from the schema. |
| Method | delete |
Remove a property from a class. |
| Method | find |
Find all direct or recursive base classes of a given class. |
| Method | find |
Find a class definition by name. |
| Method | find |
Find a property by name within a list of property dicts. |
| Method | find |
Find all direct or recursive subclasses of a given class. |
| Method | rename |
Rename a property. |
| Method | to |
Convert the schema to a plain dictionary. |
| Method | to |
Serialise the schema to a JSON string. |
| Method | update |
Update a class's name, description, or parent class. |
| Method | update |
Update an existing property on a class. |
| Method | update |
Update the schema's name, version, and last modified date. |
| Constant | ALL |
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 |
Detect whether a schema dict uses the legacy (old) format. |
| Method | _apply |
Mutate the schema's name/version/last-modified — the untracked core. |
| Method | _assign |
Undocumented |
| Method | _assign |
Undocumented |
| Method | _assign |
Undocumented |
| Method | _assign |
Undocumented |
| Method | _assign |
Undocumented |
| Method | _assign |
Undocumented |
| Method | _assign |
Undocumented |
| Method | _assign |
Undocumented |
| Method | _children |
Build the parent-name -> direct-children-names index in ONE O(C) pass. |
| Method | _class |
Build the (name -> class_def) and (parent -> children) indices in ONE pass. |
| Method | _create |
Create one property on one already-resolved class dict. |
| Method | _delete |
Undocumented |
| Method | _get |
Extract plain text from a description (handles both str and dict). |
| Method | _is |
Validate that inverse_of on datatype points back at class_name. |
| Method | _is |
Is range_name an acceptable range for a property pointing back at class_name? |
| Method | _make |
Create a description dict in the new format. |
| Method | _set |
Undocumented |
| Method | _transitive |
Names of every transitive subclass of baseclass, in BFS order. |
| Method | _update |
Update one already-resolved property on one already-resolved class. |
| Method | _validate |
Undocumented |
| Instance Variable | _schema |
Undocumented |
| Instance Variable | _tracker |
Undocumented |
| Instance Variable | _version |
Undocumented |
Create a new empty schema.
| Parameters | |
name:str | Model name. Defaults to 'Domain Model' if empty. |
version:str | Schema version. Defaults to '1.0' if empty. |
| Raises | |
TypeError | If a dict is passed instead of keyword arguments. |
Set or change the parent (base) class for an existing class.
| Parameters | |
classstr | The class to modify. |
parentstr | The new parent class name. |
| Raises | |
ClassNotFoundError | If class_name does not exist. |
Set or clear the description of a class.
| Parameters | |
classstr | Class name. |
description:str | New description. Pass an empty string to remove it. |
| Raises | |
ClassNotFoundError | If the class does not exist. |
Set an auto-generation pattern on the label property of a class.
| Parameters | |
classstr | Class name. |
pattern:str | Auto-generation expression. |
| Raises | |
ClassNotFoundError | If the class does not exist. |
PropertyNotFoundError | If the label property does not exist. |
Designate an existing property as the label property for a class.
The property is also marked as required (isOptional=False).
| Parameters | |
classstr | Class name. |
propstr | Property name to use as the label. |
isbool | Whether the label supports multiple languages. |
| Raises | |
ClassNotFoundError | If the class does not exist. |
PropertyNotFoundError | If the property does not exist on the class. |
Reorder properties within classes.
Properties not listed in the order are appended at the end.
| Parameters | |
propertydict | A dict mapping class names to ordered lists of property names. |
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
|
| Returns | |
str | list[ | A str for REPORT_FORMAT.TEXT; a list[dict] for REPORT_FORMAT.RECORDS. Returns "" (text) or [] (records) when nothing has changed since construction. |
| Raises | |
ValueError | If fmt is not a member (or value) of
~datagraphs.enums.REPORT_FORMAT. |
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 | |
classstr | Name of the new class. |
description:str | Human-readable description. |
parentstr | Name of the parent class (for inheritance). |
labelstr | Name of the label property created by default. |
isbool | Whether the label property supports multiple languages. |
isbool | Undocumented |
| Raises | |
SchemaError | If a class with the same name already exists. |
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 | |
classstr | Class to add the property to. |
propstr | Property name. |
datatype:DATATYPE | str | A DATATYPE enum value for primitive types, or a class
name string for object (relationship) properties. |
description:str | Human-readable description. |
isbool | Whether the property is optional. |
isbool | Whether the property holds multiple values. |
isbool | Whether an object property is nested (embedded). |
isbool | For text properties, whether to support multiple languages. |
inversestr | Name of the inverse property on the target class (object properties only). |
enums:list | None | Allowed values for DATATYPE.ENUM properties. |
isbool | Whether this property is a label synonym. |
isbool | None | Whether the property is available as a facet/filter. |
applybool | If True, also creates the property on all existing subclasses. |
| Raises | |
ClassNotFoundError | If the class (or referenced class) does not exist. |
PropertyExistsError | If a property with the same name already exists. |
InvalidInversePropertyError | If the inverse property specification is invalid. |
Create a subclass that inherits all properties from the parent class.
| Parameters | |
classstr | Name of the new subclass. |
description:str | Description for the subclass. |
parentstr | Name of the parent class to inherit from. |
| Raises | |
ClassNotFoundError | If the parent class does not exist. |
str, include_linked_properties: bool = False, cascade_to_subclasses: bool = True):
¶
Delete a class from the schema.
| Parameters | |
classstr | Name of the class to delete. |
includebool | If True, also removes ObjectProperties on other classes that reference this class. |
cascadebool | If True, removes subClassOf links from any subclasses of the deleted class. |
| Raises | |
ClassNotFoundError | If the class does not exist. |
Remove a property from a class.
| Parameters | |
classstr | Class containing the property. |
propstr | Property name to delete. |
| Raises | |
ClassNotFoundError | If the class does not exist. |
PropertyNotFoundError | If the property does not exist. |
Find all direct or recursive base classes of a given class.
| Parameters | |
subclass:str | The child class name. |
recursive:bool | If True, find all transitive base classes, not just the direct one. |
| Returns | |
list[ | 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. |
Find a class definition by name.
| Parameters | |
name:str | The class name to look up. |
| Returns | |
dict | None | The class dict, or None if not found. |
Find a property by name within a list of property dicts.
| Parameters | |
props:list | List of property dicts to search. |
name:str | The property name to look up. |
| Returns | |
dict | None | The property dict, or None if not found. |
Find all direct or recursive subclasses of a given class.
| Parameters | |
baseclass:str | The parent class name. |
recursive:bool | If True, find all transitive subclasses, not just direct ones. |
| Returns | |
list[ | 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. |
Rename a property.
If the property is the class's label property, the label property reference is updated automatically.
| Parameters | |
classstr | Class containing the property. |
oldstr | Current property name. |
newstr | New property name. |
| Raises | |
ClassNotFoundError | If the class does not exist. |
PropertyNotFoundError | If old_prop_name does not exist. |
PropertyExistsError | If new_prop_name is already in use. |
str, new_name: str = '', new_description: str = '', parent_class_name: str = ''):
¶
Update a class's name, description, or parent class.
| Parameters | |
classstr | Current class name. |
newstr | New class name, or empty to leave unchanged. |
newstr | New description, or empty to leave unchanged. |
parentstr | New parent class. Empty string removes the parent. |
| Raises | |
ClassNotFoundError | If the class does not exist. |
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 | |
classstr | Class containing the property. |
propstr | Property name to update. |
datatype:DATATYPE | str | None | New data type. |
description:str | None | New description. |
isbool | None | Whether the property is optional. |
isbool | None | Whether the property holds multiple values. |
isbool | None | Whether an object property is nested. |
isbool | None | Whether the property supports multiple languages. |
inversestr | Name 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 | None | Allowed enumeration values. |
isbool | Whether this property is a label synonym. |
isbool | None | Whether the property is available as a filter. |
applybool | None | If True, also updates the property on all existing subclasses. |
| Raises | |
ClassNotFoundError | If the class does not exist. |
PropertyNotFoundError | If the property does not exist. |
InvalidInversePropertyError | If inverse_of does not name an object property on the target class whose range is class_name or one of its base classes. |
Update the schema's name, version, and last modified date.
| Parameters | |
name:str | New name for the schema. If empty, the name is unchanged unless it was previously empty, in which case it defaults to 'Domain Model'. |
version:str | New version string. If empty, the version is unchanged unless it was previously empty, in which case it defaults to '1.0'. |
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).
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:str | New model name (see update_schema_metadata). |
version:str | New version string (see update_schema_metadata). |
dict, datatype: DATATYPE | str, is_nested: bool = False, is_lang_string: bool = True):
¶
Undocumented
dict, class_name: str, inverse_of: str, datatype: DATATYPE | str):
¶
Undocumented
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).
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).
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).
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 | |
classstr | Name of the class containing the property. |
inversestr | Name of the inverse property on datatype to validate. |
datatype:DATATYPE | str | Datatype of the property. |
| Returns | |
bool | True if the inverse property is valid; never returns False, as every invalid case raises. |
| Raises | |
InvalidInversePropertyError | If 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. |
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.
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.
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).