Source code for bamengine.core.registry

"""
Registry system for roles, events and relationships.

Provides global lookup for all registered roles, events, and relationships
in the BAM Engine. Registration happens automatically via __init_subclass__
hooks, eliminating manual registration boilerplate.

Design Notes
------------
- Thread-safe read-only access (registries populated at import time)
- No dynamic registration after initialization
- Clear error messages with suggestions for misspellings
- List functions for discovery (list_roles, list_events, list_relationships)

Usage Pattern
-------------
Automatic registration (happens at import):

>>> from dataclasses import dataclass
>>> from bamengine.core import Role
>>> from bamengine import Float
>>>
>>> @dataclass(slots=True)
... class MyRole(Role):
...     field: Float
>>> # MyRole is now automatically registered!

Retrieval:

>>> from bamengine.core.registry import get_role
>>> role_cls = get_role("MyRole")
>>> import numpy as np
>>> instance = role_cls(field=np.array([1.0, 2.0, 3.0]))

Discovery:

>>> from bamengine.core.registry import list_roles
>>> all_roles = list_roles()
>>> print(all_roles)
['Borrower', 'Consumer', 'Employer', 'Lender', 'MyRole', 'Producer', 'Worker']

Error Handling
--------------
If a role/event is not found, registry functions raise KeyError with
helpful message listing all available options:

>>> get_role("Producter")  # Typo in name
Traceback (most recent call last):
    ...
KeyError: Role 'Producter' not found in registry.
Available roles: Borrower, Consumer, Employer, Lender, Producer, Worker

See Also
--------
:class:`~bamengine.core.role.Role` : Base class with __init_subclass__ registration
:class:`~bamengine.core.event.Event` : Base class with __init_subclass__ registration
:class:`~bamengine.core.relationship.Relationship` : Base class with __init_subclass__ registration
"""

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:  # pragma: no cover
    from bamengine.core.event import Event
    from bamengine.core.relationship import Relationship
    from bamengine.core.role import Role

# Global registry storage
_ROLE_REGISTRY: dict[str, type[Role]] = {}
_EVENT_REGISTRY: dict[str, type[Event]] = {}
_RELATIONSHIP_REGISTRY: dict[str, type[Relationship]] = {}


[docs] def get_role(name: str) -> type[Role]: """ Retrieve a role class from the registry by name. Parameters ---------- name : str Name of the role to retrieve (case-sensitive). Returns ------- type[Role] The registered role class. Raises ------ KeyError If the role name is not found in the registry. Error message includes list of all available roles. Examples -------- Retrieve a role class and create instance: >>> from bamengine.core.registry import get_role >>> import numpy as np >>> Producer = get_role("Producer") >>> prod = Producer( ... price=np.array([1.0, 1.2]), ... production=np.array([100.0, 120.0]), ... inventory=np.array([0.0, 10.0]), ... labor_productivity=np.array([2.0, 2.0]), ... ) Use in simulation: >>> import bamengine as bam >>> sim = bam.Simulation.init(n_firms=100, seed=42) >>> Producer = get_role("Producer") >>> assert isinstance(sim.prod, Producer) Handle missing role: >>> try: ... get_role("NonExistent") ... except KeyError as e: ... print(e) Role 'NonExistent' not found in registry. Available roles: ... See Also -------- :func:`list_roles` : Get list of all registered role names :func:`get_event` : Retrieve event class from registry """ if name not in _ROLE_REGISTRY: available = ", ".join(sorted(_ROLE_REGISTRY.keys())) raise KeyError( f"Role '{name}' not found in registry. Available roles: {available}" ) return _ROLE_REGISTRY[name]
[docs] def get_event(name: str) -> type[Event]: """ Retrieve an event class from the registry by name. Parameters ---------- name : str Name of the event to retrieve (snake_case, case-sensitive). Returns ------- type[Event] The registered event class. Raises ------ KeyError If the event name is not found in the registry. Error message includes list of all available events. Examples -------- Retrieve and execute an event: >>> from bamengine.core.registry import get_event >>> import bamengine as bam >>> sim = bam.Simulation.init(n_firms=100, seed=42) >>> FirmsAdjustPrice = get_event("firms_adjust_price") >>> event_instance = FirmsAdjustPrice() >>> event_instance.execute(sim) Check event availability: >>> from bamengine.core.registry import list_events >>> "firms_adjust_price" in list_events() True See Also -------- :func:`list_events` : Get list of all registered event names :func:`get_role` : Retrieve role class from registry """ if name not in _EVENT_REGISTRY: available = ", ".join(sorted(_EVENT_REGISTRY.keys())) raise KeyError( f"Event '{name}' not found in registry. Available events: {available}" ) return _EVENT_REGISTRY[name]
[docs] def get_relationship(name: str) -> type[Relationship]: """ Retrieve a relationship class from the registry by name. Parameters ---------- name : str Name of the relationship to retrieve. Returns ------- type[Relationship] The registered relationship class Raises ------ KeyError If the relationship name is not found in the registry. Examples -------- Retrieve a relationship class and create instance: >>> from bamengine import get_relationship >>> import numpy as np >>> LoanBook = get_relationship("LoanBook") >>> loans = LoanBook() >>> loans.append_loans_for_lender( ... lender_idx=0, ... borrower_indices=np.array([1, 2]), ... amount=np.array([100.0, 200.0]), ... rate=np.array([0.05, 0.05]), ... ) >>> loans.size 2 """ if name not in _RELATIONSHIP_REGISTRY: available = ", ".join(sorted(_RELATIONSHIP_REGISTRY.keys())) raise KeyError( f"Relationship '{name}' not found in registry. " f"Available relationships: {available}" ) return _RELATIONSHIP_REGISTRY[name]
[docs] def list_roles() -> list[str]: """ Return sorted list of all registered role names. Returns ------- list[str] Sorted list of role names. Examples -------- >>> from bamengine.core.registry import list_roles >>> roles = list_roles() >>> "Producer" in roles True >>> "Worker" in roles True """ return sorted(_ROLE_REGISTRY.keys())
[docs] def list_events() -> list[str]: """ Return sorted list of all registered event names. Returns ------- list[str] Sorted list of event names in snake_case. Examples -------- >>> from bamengine.core.registry import list_events >>> events = list_events() >>> len(events) # Should be 39 BAM events + any custom 39 >>> "firms_adjust_price" in events True """ return sorted(_EVENT_REGISTRY.keys())
[docs] def list_relationships() -> list[str]: """ Return sorted list of all registered relationship names. Returns ------- list[str] Sorted list of relationship names. Examples -------- >>> from bamengine.core.registry import list_relationships >>> rels = list_relationships() >>> "LoanBook" in rels True """ return sorted(_RELATIONSHIP_REGISTRY.keys())
[docs] def clear_registry() -> None: """ Clear all registrations (useful for testing). WARNING: This is a destructive operation. Only use in test teardown. Clears roles, events, and relationships. """ _ROLE_REGISTRY.clear() _EVENT_REGISTRY.clear() _RELATIONSHIP_REGISTRY.clear()