Source code for runtimepy.registry
"""
A generic registry interface for keeping track of objects by either string or
integer identifier.
"""
# built-in
from abc import abstractmethod as _abstractmethod
from contextlib import suppress
from typing import Generic as _Generic
from typing import Iterator
from typing import Optional as _Optional
from typing import TypeVar as _TypeVar
from typing import cast as _cast
# third-party
from vcorelib.io.types import JsonObject as _JsonObject
from vcorelib.io.types import JsonValue as _JsonValue
# internal
from runtimepy.registry.item import RegistryItem as _RegistryItem
from runtimepy.registry.name import NameRegistry as _NameRegistry
from runtimepy.registry.name import RegistryKey as _RegistryKey
from runtimepy.registry.name import is_registry_key as _is_registry_key
from runtimepy.schemas import RuntimepyDictCodec as _RuntimepyDictCodec
T = _TypeVar("T", bound=_RegistryItem)
[docs]
class Registry(_RuntimepyDictCodec, _Generic[T]):
"""A base class for a generic registry."""
name_registry: type[_NameRegistry] = _NameRegistry
@property
@_abstractmethod
def kind(self) -> type[T]:
"""Determine what kind of registry this is."""
[docs]
def init(self, data: _JsonObject) -> None:
"""Perform implementation-specific initialization."""
# Create the registry items and name mapping.
self.items: dict[str, T] = {
name: self.kind.create(_cast(_JsonObject, data))
for name, data in data.items()
}
# Create the name registry.
self.names = self.name_registry(
reverse={name: item.id for name, item in self.items.items()}
)
[docs]
def search(
self, pattern: str, exact: bool = False
) -> Iterator[tuple[str, T]]:
"""Search for items in the registry by name."""
for name in self.names.search(pattern, exact=exact):
with suppress(KeyError):
yield name, self.items[name]
[docs]
def asdict(self) -> _JsonObject:
"""Get this registry as a dictionary."""
return {
name: _cast(_JsonValue, item.asdict())
for name, item in self.items.items()
}
[docs]
def register_from_other(self, other: "Registry[T]") -> None:
"""Register missing elements from another registry."""
for name, instance in other.items.items():
if name in self.items:
assert self.items[name].id == instance.id, (name, instance)
else:
assert self.register(name, instance), (name, instance)
[docs]
def register(self, name: str, item: T) -> bool:
"""Attempt to register a new item."""
identifier = self.names.register_name(name, identifier=item.id)
added = False
if identifier is not None and name not in self.items:
self.items[name] = item
added = True
return added
[docs]
def register_dict(self, name: str, data: _JsonObject) -> _Optional[T]:
"""Register a new item from dictionary data."""
# Inject an identifier into the data if one's not present.
if "id" not in data:
identifier = self.names.register_name(name)
if identifier is None:
return None
data["id"] = identifier
item = self.kind(data)
result = self.register(name, item)
return item if result else None
[docs]
def get(self, key: _RegistryKey) -> _Optional[T]:
"""Attempt to get an item from a registry key."""
result = None
name = self.names.name(key)
if name is not None and name in self.items:
result = self.items[name]
return result
[docs]
def registry_normalize(self, key: _RegistryKey | T) -> T:
"""Attempt to get an item from a registry key."""
if _is_registry_key(key):
result: T = self[_cast(str, key)]
else:
result = _cast(T, key)
return result
def __getitem__(self, key: _RegistryKey) -> T:
"""Get a registry item."""
item = self.get(key)
if item is None:
raise KeyError(f"No item '{key}'!")
return item