Skip to content

Type Discovery

TypeRegistry

crategraph.core.types.TypeRegistry

Dynamically exposes entity or relationship types as attributes.

Used as graph.types and graph.relationship_types. Supports attribute access for autocomplete (graph.types.Person) and fuzzy string validation with helpful error messages.

Instances are immutable snapshots — call _with_types() to produce a new registry scoped to a subset.

Source code in crategraph/core/types.py
class TypeRegistry:
    """Dynamically exposes entity or relationship types as attributes.

    Used as ``graph.types`` and ``graph.relationship_types``.  Supports
    attribute access for autocomplete (``graph.types.Person``) and fuzzy
    string validation with helpful error messages.

    Instances are immutable snapshots — call ``_with_types()`` to produce
    a new registry scoped to a subset.
    """

    def __init__(self, names: frozenset[str], *, label: str = "type") -> None:
        self._names = names
        self._label = label  # "entity type" or "relationship type", used in errors

    # --- Public API ---

    def __contains__(self, name: str) -> bool:
        return name in self._names

    def __iter__(self):
        return iter(sorted(self._names))

    def __len__(self) -> int:
        return len(self._names)

    def __repr__(self) -> str:
        sorted_names = sorted(self._names)
        if len(sorted_names) <= 10:
            listing = ", ".join(sorted_names)
        else:
            listing = ", ".join(sorted_names[:10]) + f", ... ({len(sorted_names)} total)"
        return f"TypeRegistry([{listing}])"

    def __getattr__(self, name: str) -> str:
        """Allow ``registry.Person`` → ``"Person"`` with fuzzy fallback."""
        if name.startswith("_"):
            raise AttributeError(name)
        if name in self._names:
            return name
        self._raise_not_found(name)

    def validate(self, name: str) -> str:
        """Validate *name* against known types, returning it if valid.

        Raises ``ValueError`` with fuzzy suggestions if not found.
        """
        if name in self._names:
            return name
        self._raise_not_found(name)

    def _with_types(self, names: frozenset[str]) -> TypeRegistry:
        """Return a new registry scoped to *names*."""
        return TypeRegistry(names, label=self._label)

    # --- Error helpers ---

    def _raise_not_found(self, name: str) -> None:
        available = sorted(self._names)
        if not available:
            msg = f'"{name}" is not a recognised {self._label} (no types loaded yet).'
            raise ValueError(msg)

        matches = process.extract(name, available, limit=3, score_cutoff=50)
        suggestions = [m[0] for m in matches]

        if suggestions:
            suggestion_str = ", ".join(f'"{s}"' for s in suggestions)
            msg = f'"{name}" isn\'t a recognised {self._label}. Did you mean {suggestion_str}?'
        else:
            if len(available) <= 10:
                available_str = ", ".join(available)
            else:
                available_str = ", ".join(available[:10]) + f", ... ({len(available)} total)"
            msg = f'"{name}" isn\'t a recognised {self._label}. Available: {available_str}'
        raise ValueError(msg)

__getattr__(name)

Allow registry.Person → "Person" with fuzzy fallback.

Source code in crategraph/core/types.py
def __getattr__(self, name: str) -> str:
    """Allow ``registry.Person`` → ``"Person"`` with fuzzy fallback."""
    if name.startswith("_"):
        raise AttributeError(name)
    if name in self._names:
        return name
    self._raise_not_found(name)

validate(name)

Validate name against known types, returning it if valid.

Raises ValueError with fuzzy suggestions if not found.

Source code in crategraph/core/types.py
def validate(self, name: str) -> str:
    """Validate *name* against known types, returning it if valid.

    Raises ``ValueError`` with fuzzy suggestions if not found.
    """
    if name in self._names:
        return name
    self._raise_not_found(name)