Skip to content

struct

struct #

Struct — named fields. Subclass Struct; the annotations are the fields.

class Session(cg.Struct):
    photo: cg.Image
    location: cg.Location

A field named with a leading _ is hidden on disk (_metadata.metadata).

Struct #

Bases: CGType

A type with named fields; each subclass is a dataclass over its annotations.

Source code in capturegraph-lib/capturegraph/types/containers/struct.py
@dataclass_transform()
class Struct(CGType):
    """A type with named fields; each subclass is a dataclass over its annotations."""

    _fields: ClassVar[dict[str, type[CGType]]] = {}

    def __init_subclass__(cls, **kwargs: object) -> None:
        """Turn ``cls`` into a dataclass over its annotated CGType fields."""
        super().__init_subclass__(**kwargs)
        annotations = inspect.get_annotations(cls, eval_str=True)
        if not annotations:
            return
        for name, field_type in annotations.items():
            check_element(field_type, f"Struct field {name!r}")
        cls._fields = annotations
        dataclass(cls)

    def __setattr__(self, name: str, value: object) -> None:
        """Assign a field; a public field write means the loaded tree no longer matches."""
        if not name.startswith("_"):
            forget_base(self)
        object.__setattr__(self, name, value)

    @classmethod
    def schema(cls) -> dict[str, JSONValue]:
        """This struct's wire form: each field's schema, tagged ``"struct"``."""
        return {
            "type": "struct",
            "fields": {n: t.schema() for n, t in cls._fields.items()},
        }

    @classmethod
    def _children(cls) -> list[tuple[str, type[CGType]]]:
        """Every declared field, labelled ``.name``."""
        return [(f".{name}", field) for name, field in cls._fields.items()]

    @classmethod
    def _descend(cls, component: str) -> type[CGType]:
        """The field stored at on-disk entry ``component``.

        Raises:
            SchemaPathError: If no field carries that on-disk name.
        """
        for name, field in cls._fields.items():
            if on_disk(name) == component:
                return field
        raise SchemaPathError(
            f"{cls.__name__} has no field at {component!r}; "
            f"expected one of {[on_disk(name) for name in cls._fields]}"
        )

    @classmethod
    def load(cls, base: Path) -> Self:
        """The struct stored at ``base``, one entry per field; an absent field is ``Missing``.

        Raises:
            FileNotFoundError: If ``base`` is not a directory.
        """
        if not base.is_dir():
            raise FileNotFoundError(f"no {cls.__name__} directory at {base}")
        values: dict[str, object] = {}
        for name, field in cls._fields.items():
            try:
                values[name] = field.load(base / on_disk(name))
            except FileNotFoundError as error:
                values[name] = _Missing(error)
        value = cls(**values)
        remember_base(value, base)
        return value

    @classmethod
    def store(cls, base: Path, value: object) -> None:
        """Write each present field of ``value`` under ``base``; a ``Missing`` field is left off.

        Raises:
            TypeError: If ``value`` is not an instance of this struct.
        """
        if not isinstance(value, cls):
            raise TypeError(f"{cls.__name__}.store needs a {cls.__name__}, got {value!r}")
        base.mkdir(parents=True, exist_ok=True)
        for name, field in cls._fields.items():
            child = getattr(value, name)
            if not is_missing(child):
                field.store(base / on_disk(name), child)

__init_subclass__(**kwargs) #

Turn cls into a dataclass over its annotated CGType fields.

Source code in capturegraph-lib/capturegraph/types/containers/struct.py
def __init_subclass__(cls, **kwargs: object) -> None:
    """Turn ``cls`` into a dataclass over its annotated CGType fields."""
    super().__init_subclass__(**kwargs)
    annotations = inspect.get_annotations(cls, eval_str=True)
    if not annotations:
        return
    for name, field_type in annotations.items():
        check_element(field_type, f"Struct field {name!r}")
    cls._fields = annotations
    dataclass(cls)

__setattr__(name, value) #

Assign a field; a public field write means the loaded tree no longer matches.

Source code in capturegraph-lib/capturegraph/types/containers/struct.py
def __setattr__(self, name: str, value: object) -> None:
    """Assign a field; a public field write means the loaded tree no longer matches."""
    if not name.startswith("_"):
        forget_base(self)
    object.__setattr__(self, name, value)

load(base) classmethod #

The struct stored at base, one entry per field; an absent field is Missing.

Raises:

Type Description
FileNotFoundError

If base is not a directory.

Source code in capturegraph-lib/capturegraph/types/containers/struct.py
@classmethod
def load(cls, base: Path) -> Self:
    """The struct stored at ``base``, one entry per field; an absent field is ``Missing``.

    Raises:
        FileNotFoundError: If ``base`` is not a directory.
    """
    if not base.is_dir():
        raise FileNotFoundError(f"no {cls.__name__} directory at {base}")
    values: dict[str, object] = {}
    for name, field in cls._fields.items():
        try:
            values[name] = field.load(base / on_disk(name))
        except FileNotFoundError as error:
            values[name] = _Missing(error)
    value = cls(**values)
    remember_base(value, base)
    return value

schema() classmethod #

This struct's wire form: each field's schema, tagged "struct".

Source code in capturegraph-lib/capturegraph/types/containers/struct.py
@classmethod
def schema(cls) -> dict[str, JSONValue]:
    """This struct's wire form: each field's schema, tagged ``"struct"``."""
    return {
        "type": "struct",
        "fields": {n: t.schema() for n, t in cls._fields.items()},
    }

store(base, value) classmethod #

Write each present field of value under base; a Missing field is left off.

Raises:

Type Description
TypeError

If value is not an instance of this struct.

Source code in capturegraph-lib/capturegraph/types/containers/struct.py
@classmethod
def store(cls, base: Path, value: object) -> None:
    """Write each present field of ``value`` under ``base``; a ``Missing`` field is left off.

    Raises:
        TypeError: If ``value`` is not an instance of this struct.
    """
    if not isinstance(value, cls):
        raise TypeError(f"{cls.__name__}.store needs a {cls.__name__}, got {value!r}")
    base.mkdir(parents=True, exist_ok=True)
    for name, field in cls._fields.items():
        child = getattr(value, name)
        if not is_missing(child):
            field.store(base / on_disk(name), child)

on_disk(field_name) #

The on-disk entry name for a struct field (_metadata.metadata).

Source code in capturegraph-lib/capturegraph/types/containers/struct.py
def on_disk(field_name: str) -> str:
    """The on-disk entry name for a struct field (``_metadata`` → ``.metadata``)."""
    return f".{field_name[1:]}" if field_name.startswith("_") else field_name

struct_fields(struct_or_type) #

The declared fields of a Struct value or type, name → type, as a fresh dict.

Raises:

Type Description
TypeError

If the argument is not a Struct value or subclass.

Source code in capturegraph-lib/capturegraph/types/containers/struct.py
def struct_fields(struct_or_type: Struct | type[Struct]) -> dict[str, type[CGType]]:
    """The declared fields of a ``Struct`` value or type, name → type, as a fresh dict.

    Raises:
        TypeError: If the argument is not a ``Struct`` value or subclass.
    """
    cls = struct_or_type if isinstance(struct_or_type, type) else type(struct_or_type)
    if not (isinstance(cls, type) and issubclass(cls, Struct)):
        raise TypeError(f"struct_fields expects a Struct, got {cls!r}")
    return dict(cls._fields)