class GraphMetadata(ConfigBaseModel):
"""Schema metadata and versioning information.
Holds metadata about the schema, including its name, version, and
description. Used for schema identification and versioning.
Suitable for LLM-generated schema constituents.
"""
name: str = PydanticField(
...,
description=(
"Label of the schema. Free-form, not an identifier: merges fold it "
"into ``left+right``. The database / graph / space it deploys into "
"is ``db_profile.target_namespace`` when set, else this label "
"sanitized per flavor (see ``Schema.effective_namespace``)."
),
)
version: str | None = PydanticField(
default=None,
description="Semantic version of the schema (e.g. '1.0.0', '2.1.3-beta+build.42').",
)
description: str | None = PydanticField(
default=None,
description="Optional human-readable description of the schema.",
)
semantics: Semantics | None = PydanticField(
default=None,
description="Optional external-vocabulary anchors for the schema as a whole.",
)
base_iri: str | None = PydanticField(
default=None,
description=(
"Namespace this schema mints IRIs in: a vertex ``Person`` is "
"``{base_iri}Person``. Ends in ``/`` or ``#``. Not "
"``db_profile.target_namespace``, which names a database."
),
)
namespaces: dict[str, str] = PydanticField(
default_factory=dict,
description=(
"Prefix -> namespace IRI for the vocabularies the schema's IRIs come "
"from, e.g. ``{schema: https://schema.org/}``."
),
)
provenance: Provenance | None = PydanticField(
default=None,
description=(
"Content address and lineage of this schema. Written at commit "
"points, never by `apply_evolution`, and excluded from every "
"content hash -- identity has to be path-independent."
),
)
naming: NamingConvention | None = PydanticField(
default=None,
description=(
"Optional declaration of the naming style this schema's invented "
"identifiers follow. Purely descriptive: nothing consults it at "
"runtime. Its audience is the next author — human or agent — "
"extending this schema, who would otherwise have to infer the "
"convention from the names and usually infers it wrong."
),
)
@field_validator("base_iri")
@classmethod
def _validate_base_iri(cls, v: str | None) -> str | None:
if v is not None and not (is_absolute_iri(v) and v[-1] in "/#"):
raise ValueError(
f"base_iri {v!r} must be an absolute IRI ending in '/' or '#'"
)
return v
@field_validator("namespaces")
@classmethod
def _validate_namespaces(cls, v: dict[str, str]) -> dict[str, str]:
for prefix, iri in v.items():
if not _PREFIX_RE.match(prefix):
raise ValueError(f"namespace prefix {prefix!r} is not a valid prefix")
if not is_absolute_iri(iri):
raise ValueError(
f"namespace {prefix!r} -> {iri!r} is not an absolute IRI"
)
return v
@field_validator("version")
@classmethod
def _validate_semver(cls, v: str | None) -> str | None:
if v is not None and not _SEMVER_RE.match(v):
raise ValueError(
f"version '{v}' is not a valid semantic version "
f"(expected MAJOR.MINOR.PATCH[-prerelease][+build])"
)
return v