Files
SciMesh/scimesh/sdk/identity.py
T

209 lines
6.8 KiB
Python

"""Versioned identities used across the SciMesh workload SDK.
The module-level constants ``SDK_API_VERSION``, ``MANIFEST_SCHEMA_VERSION``,
``WORKFLOW_SCHEMA_VERSION``, ``TASK_SCHEMA_VERSION``, and
``OUTPUT_SCHEMA_VERSION`` pin the current wire/schema versions; manifests
declare explicit compatibility ranges against them.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Mapping
from ._validation import (
require_exact_keys,
require_identifier,
require_semver,
require_string,
require_workload_name,
validate_version_range,
version_in_range,
)
SDK_API_VERSION = "1.0.0"
MANIFEST_SCHEMA_VERSION = 1
WORKFLOW_SCHEMA_VERSION = 1
TASK_SCHEMA_VERSION = 1
OUTPUT_SCHEMA_VERSION = 1
@dataclass(frozen=True, slots=True)
class VersionRange:
"""A deliberately small, explicit compatibility range.
The v1 SDK accepts comma-separated comparisons such as ``>=1.0,<2.0``.
Wildcards and an omitted operator are rejected so a missing version can
never be interpreted as "latest".
"""
expression: str
def __post_init__(self) -> None:
object.__setattr__(
self, "expression", validate_version_range(self.expression, "version range")
)
def contains(self, version: str) -> bool:
return version_in_range(version, self.expression)
def to_dict(self) -> str:
return self.expression
@classmethod
def from_dict(cls, value: object) -> "VersionRange":
return cls(value) # type: ignore[arg-type]
@dataclass(frozen=True, slots=True)
class WorkloadId:
"""The exact identity of a workload: canonical hyphenated name and semantic version."""
name: str
version: str
def __post_init__(self) -> None:
object.__setattr__(self, "name", require_workload_name(self.name))
object.__setattr__(
self, "version", require_semver(self.version, "workload.version")
)
def to_dict(self) -> dict[str, str]:
return {"name": self.name, "version": self.version}
@classmethod
def from_dict(cls, value: object) -> "WorkloadId":
if not isinstance(value, Mapping):
raise ValueError("workload identity must be an object")
require_exact_keys(value, {"name", "version"}, "workload identity")
return cls(name=value["name"], version=value["version"]) # type: ignore[arg-type]
@dataclass(frozen=True, slots=True)
class SchemaRef:
"""A versioned artifact schema identity (``name@version``).
Schemas are content contracts: two artifacts share a schema only when
their ``SchemaRef`` values are equal.
"""
name: str
version: int
def __post_init__(self) -> None:
object.__setattr__(self, "name", require_identifier(self.name, "schema.name"))
if (
isinstance(self.version, bool)
or not isinstance(self.version, int)
or self.version < 1
):
raise ValueError("schema.version must be a positive integer")
@property
def canonical(self) -> str:
return f"{self.name}@{self.version}"
def to_dict(self) -> dict[str, object]:
return {"name": self.name, "version": self.version}
@classmethod
def parse(cls, value: object, field: str = "schema") -> "SchemaRef":
text = require_string(value, field, max_length=160)
name, separator, raw_version = text.rpartition("@")
if not separator or not raw_version.isdigit():
raise ValueError(f"{field} must use the name@version form")
return cls(name=name, version=int(raw_version))
@classmethod
def from_dict(cls, value: object) -> "SchemaRef":
if isinstance(value, str):
return cls.parse(value)
if not isinstance(value, Mapping):
raise ValueError("schema reference must be a name@version string or object")
require_exact_keys(value, {"name", "version"}, "schema reference")
return cls(name=value["name"], version=value["version"]) # type: ignore[arg-type]
@dataclass(frozen=True, slots=True)
class ComponentRef:
"""Versioned, package-owned planner/runner/reducer/verifier identity."""
name: str
version: int
def __post_init__(self) -> None:
object.__setattr__(
self, "name", require_identifier(self.name, "component.name")
)
if (
isinstance(self.version, bool)
or not isinstance(self.version, int)
or self.version < 1
):
raise ValueError("component.version must be a positive integer")
@property
def canonical(self) -> str:
return f"{self.name}@{self.version}"
def to_dict(self) -> dict[str, object]:
return {"name": self.name, "version": self.version}
@classmethod
def from_dict(cls, value: object) -> "ComponentRef":
if isinstance(value, str):
parsed = SchemaRef.parse(value, "component")
return cls(parsed.name, parsed.version)
if not isinstance(value, Mapping):
raise ValueError("component reference must be an object")
require_exact_keys(value, {"name", "version"}, "component reference")
return cls(name=value["name"], version=value["version"]) # type: ignore[arg-type]
@dataclass(frozen=True, slots=True)
class FeatureRequirement:
"""A versioned feature a workload requires or optionally selects.
Optional features may declare a fallback that negotiation records when
the runtime does not provide the feature.
"""
name: str
versions: VersionRange
fallback: str | None = None
def __post_init__(self) -> None:
object.__setattr__(self, "name", require_identifier(self.name, "feature.name"))
if not isinstance(self.versions, VersionRange):
raise ValueError("feature.versions must be a VersionRange")
if self.fallback is not None:
object.__setattr__(
self, "fallback", require_identifier(self.fallback, "feature.fallback")
)
def to_dict(self) -> dict[str, object]:
result: dict[str, object] = {
"name": self.name,
"versions": self.versions.expression,
}
if self.fallback is not None:
result["fallback"] = self.fallback
return result
@classmethod
def from_dict(cls, value: object) -> "FeatureRequirement":
if not isinstance(value, Mapping):
raise ValueError("feature requirement must be an object")
require_exact_keys(
value,
{"name", "versions"},
"feature requirement",
optional={"fallback"},
)
return cls(
name=value["name"], # type: ignore[arg-type]
versions=VersionRange.from_dict(value["versions"]),
fallback=value.get("fallback"), # type: ignore[arg-type]
)