diff --git a/app/sghi-commons/docs/source/_templates/class.rst b/app/sghi-commons/docs/source/_templates/class.rst index d638e60..ca179e6 100644 --- a/app/sghi-commons/docs/source/_templates/class.rst +++ b/app/sghi-commons/docs/source/_templates/class.rst @@ -4,7 +4,7 @@ .. autoclass:: {{ objname }} :members: - :special-members: __enter__, __exit__, __call__, __getattr__, __setattr__ + :special-members: __contains__, __delitem__, __enter__, __exit__, __call__, __getattr__, __setattr__, __getitem__, __setitem__ :show-inheritance: :inherited-members: :member-order: groupwise diff --git a/app/sghi-commons/docs/source/api/sghi.app.registry.rst b/app/sghi-commons/docs/source/api/sghi.app.registry.rst new file mode 100644 index 0000000..dcf7720 --- /dev/null +++ b/app/sghi-commons/docs/source/api/sghi.app.registry.rst @@ -0,0 +1,6 @@ +sghi.app.registry +================= + +.. currentmodule:: sghi.app + +.. autodata:: registry diff --git a/app/sghi-commons/docs/source/api/sghi.app.rst b/app/sghi-commons/docs/source/api/sghi.app.rst index 31778a9..9b40def 100644 --- a/app/sghi-commons/docs/source/api/sghi.app.rst +++ b/app/sghi-commons/docs/source/api/sghi.app.rst @@ -11,6 +11,7 @@ :toctree: dispatcher + registry conf diff --git a/app/sghi-commons/docs/source/api/sghi.config.config.Config.rst b/app/sghi-commons/docs/source/api/sghi.config.config.Config.rst index ff2aa8f..9eb6ef8 100644 --- a/app/sghi-commons/docs/source/api/sghi.config.config.Config.rst +++ b/app/sghi-commons/docs/source/api/sghi.config.config.Config.rst @@ -1,11 +1,11 @@ -sghi.config.config.Config +sghi.config.config.Config ========================= .. currentmodule:: sghi.config.config .. autoclass:: Config :members: - :special-members: __enter__, __exit__, __call__, __getattr__, __setattr__ + :special-members: __contains__, __delitem__, __enter__, __exit__, __call__, __getattr__, __setattr__, __getitem__, __setitem__ :show-inheritance: :inherited-members: :member-order: groupwise diff --git a/app/sghi-commons/docs/source/api/sghi.config.setting_initializer.SettingInitializer.rst b/app/sghi-commons/docs/source/api/sghi.config.setting_initializer.SettingInitializer.rst index c5e7276..873895a 100644 --- a/app/sghi-commons/docs/source/api/sghi.config.setting_initializer.SettingInitializer.rst +++ b/app/sghi-commons/docs/source/api/sghi.config.setting_initializer.SettingInitializer.rst @@ -1,11 +1,11 @@ -sghi.config.setting\_initializer.SettingInitializer +sghi.config.setting\_initializer.SettingInitializer =================================================== .. currentmodule:: sghi.config.setting_initializer .. autoclass:: SettingInitializer :members: - :special-members: __enter__, __exit__, __call__, __getattr__, __setattr__ + :special-members: __contains__, __delitem__, __enter__, __exit__, __call__, __getattr__, __setattr__, __getitem__, __setitem__ :show-inheritance: :inherited-members: :member-order: groupwise diff --git a/app/sghi-commons/docs/source/api/sghi.dispatch.Dispatcher.rst b/app/sghi-commons/docs/source/api/sghi.dispatch.Dispatcher.rst index 6ba3fbe..015b58d 100644 --- a/app/sghi-commons/docs/source/api/sghi.dispatch.Dispatcher.rst +++ b/app/sghi-commons/docs/source/api/sghi.dispatch.Dispatcher.rst @@ -1,11 +1,11 @@ -sghi.dispatch.Dispatcher +sghi.dispatch.Dispatcher ======================== .. currentmodule:: sghi.dispatch .. autoclass:: Dispatcher :members: - :special-members: __enter__, __exit__, __call__, __getattr__, __setattr__ + :special-members: __contains__, __delitem__, __enter__, __exit__, __call__, __getattr__, __setattr__, __getitem__, __setitem__ :show-inheritance: :inherited-members: :member-order: groupwise diff --git a/app/sghi-commons/docs/source/api/sghi.dispatch.Receiver.rst b/app/sghi-commons/docs/source/api/sghi.dispatch.Receiver.rst index 4f7701c..30614a9 100644 --- a/app/sghi-commons/docs/source/api/sghi.dispatch.Receiver.rst +++ b/app/sghi-commons/docs/source/api/sghi.dispatch.Receiver.rst @@ -1,11 +1,11 @@ -sghi.dispatch.Receiver +sghi.dispatch.Receiver ====================== .. currentmodule:: sghi.dispatch .. autoclass:: Receiver :members: - :special-members: __enter__, __exit__, __call__, __getattr__, __setattr__ + :special-members: __contains__, __delitem__, __enter__, __exit__, __call__, __getattr__, __setattr__, __getitem__, __setitem__ :show-inheritance: :inherited-members: :member-order: groupwise diff --git a/app/sghi-commons/docs/source/api/sghi.dispatch.Signal.rst b/app/sghi-commons/docs/source/api/sghi.dispatch.Signal.rst index 5ad64c8..dddf731 100644 --- a/app/sghi-commons/docs/source/api/sghi.dispatch.Signal.rst +++ b/app/sghi-commons/docs/source/api/sghi.dispatch.Signal.rst @@ -1,11 +1,11 @@ -sghi.dispatch.Signal +sghi.dispatch.Signal ==================== .. currentmodule:: sghi.dispatch .. autoclass:: Signal :members: - :special-members: __enter__, __exit__, __call__, __getattr__, __setattr__ + :special-members: __contains__, __delitem__, __enter__, __exit__, __call__, __getattr__, __setattr__, __getitem__, __setitem__ :show-inheritance: :inherited-members: :member-order: groupwise diff --git a/app/sghi-commons/docs/source/api/sghi.dispatch.connect.rst b/app/sghi-commons/docs/source/api/sghi.dispatch.connect.rst index 4664778..8423691 100644 --- a/app/sghi-commons/docs/source/api/sghi.dispatch.connect.rst +++ b/app/sghi-commons/docs/source/api/sghi.dispatch.connect.rst @@ -1,11 +1,11 @@ -sghi.dispatch.connect +sghi.dispatch.connect ===================== .. currentmodule:: sghi.dispatch .. autoclass:: connect :members: - :special-members: __enter__, __exit__, __call__, __getattr__, __setattr__ + :special-members: __contains__, __delitem__, __enter__, __exit__, __call__, __getattr__, __setattr__, __getitem__, __setitem__ :show-inheritance: :inherited-members: :member-order: groupwise diff --git a/app/sghi-commons/docs/source/api/sghi.disposable.decorators.not_disposed.rst b/app/sghi-commons/docs/source/api/sghi.disposable.decorators.not_disposed.rst index 569f98c..4b4fd80 100644 --- a/app/sghi-commons/docs/source/api/sghi.disposable.decorators.not_disposed.rst +++ b/app/sghi-commons/docs/source/api/sghi.disposable.decorators.not_disposed.rst @@ -1,11 +1,11 @@ -sghi.disposable.decorators.not\_disposed +sghi.disposable.decorators.not\_disposed ======================================== .. currentmodule:: sghi.disposable.decorators .. autoclass:: not_disposed :members: - :special-members: __enter__, __exit__, __call__, __getattr__, __setattr__ + :special-members: __contains__, __delitem__, __enter__, __exit__, __call__, __getattr__, __setattr__, __getitem__, __setitem__ :show-inheritance: :inherited-members: :member-order: groupwise diff --git a/app/sghi-commons/docs/source/api/sghi.disposable.disposable.Disposable.rst b/app/sghi-commons/docs/source/api/sghi.disposable.disposable.Disposable.rst index 1ccb5f6..82f7644 100644 --- a/app/sghi-commons/docs/source/api/sghi.disposable.disposable.Disposable.rst +++ b/app/sghi-commons/docs/source/api/sghi.disposable.disposable.Disposable.rst @@ -1,11 +1,11 @@ -sghi.disposable.disposable.Disposable +sghi.disposable.disposable.Disposable ===================================== .. currentmodule:: sghi.disposable.disposable .. autoclass:: Disposable :members: - :special-members: __enter__, __exit__, __call__, __getattr__, __setattr__ + :special-members: __contains__, __delitem__, __enter__, __exit__, __call__, __getattr__, __setattr__, __getitem__, __setitem__ :show-inheritance: :inherited-members: :member-order: groupwise diff --git a/app/sghi-commons/docs/source/api/sghi.registry.NoSuchRegistryItemError.rst b/app/sghi-commons/docs/source/api/sghi.registry.NoSuchRegistryItemError.rst new file mode 100644 index 0000000..ead9560 --- /dev/null +++ b/app/sghi-commons/docs/source/api/sghi.registry.NoSuchRegistryItemError.rst @@ -0,0 +1,25 @@ +sghi.registry.NoSuchRegistryItemError +===================================== + +.. currentmodule:: sghi.registry + +.. autoexception:: NoSuchRegistryItemError + :members: + :show-inheritance: + :inherited-members: + :member-order: groupwise + + + + + + + + + + + + + + + .. automethod:: __init__ diff --git a/app/sghi-commons/docs/source/api/sghi.registry.Registry.rst b/app/sghi-commons/docs/source/api/sghi.registry.Registry.rst new file mode 100644 index 0000000..82d79ae --- /dev/null +++ b/app/sghi-commons/docs/source/api/sghi.registry.Registry.rst @@ -0,0 +1,47 @@ +sghi.registry.Registry +====================== + +.. currentmodule:: sghi.registry + +.. autoclass:: Registry + :members: + :special-members: __contains__, __delitem__, __enter__, __exit__, __call__, __getattr__, __setattr__, __getitem__, __setitem__ + :show-inheritance: + :inherited-members: + :member-order: groupwise + + + ---- + + + + + .. rubric:: Methods + + .. autosummary:: + + ~Registry.__init__ + ~Registry.get + ~Registry.of + ~Registry.pop + ~Registry.setdefault + + + + + + + .. rubric:: Attributes + + .. autosummary:: + + ~Registry.dispatcher + + + + + ---- + + + + .. automethod:: __init__ diff --git a/app/sghi-commons/docs/source/api/sghi.registry.RemoveRegistryItemSignal.rst b/app/sghi-commons/docs/source/api/sghi.registry.RemoveRegistryItemSignal.rst new file mode 100644 index 0000000..ecaacbf --- /dev/null +++ b/app/sghi-commons/docs/source/api/sghi.registry.RemoveRegistryItemSignal.rst @@ -0,0 +1,43 @@ +sghi.registry.RemoveRegistryItemSignal +====================================== + +.. currentmodule:: sghi.registry + +.. autoclass:: RemoveRegistryItemSignal + :members: + :special-members: __contains__, __delitem__, __enter__, __exit__, __call__, __getattr__, __setattr__, __getitem__, __setitem__ + :show-inheritance: + :inherited-members: + :member-order: groupwise + + + ---- + + + + + .. rubric:: Methods + + .. autosummary:: + + ~RemoveRegistryItemSignal.__init__ + + + + + + + .. rubric:: Attributes + + .. autosummary:: + + ~RemoveRegistryItemSignal.item_key + + + + + ---- + + + + .. automethod:: __init__ diff --git a/app/sghi-commons/docs/source/api/sghi.registry.SetRegistryItemSignal.rst b/app/sghi-commons/docs/source/api/sghi.registry.SetRegistryItemSignal.rst new file mode 100644 index 0000000..ae33146 --- /dev/null +++ b/app/sghi-commons/docs/source/api/sghi.registry.SetRegistryItemSignal.rst @@ -0,0 +1,44 @@ +sghi.registry.SetRegistryItemSignal +=================================== + +.. currentmodule:: sghi.registry + +.. autoclass:: SetRegistryItemSignal + :members: + :special-members: __contains__, __delitem__, __enter__, __exit__, __call__, __getattr__, __setattr__, __getitem__, __setitem__ + :show-inheritance: + :inherited-members: + :member-order: groupwise + + + ---- + + + + + .. rubric:: Methods + + .. autosummary:: + + ~SetRegistryItemSignal.__init__ + + + + + + + .. rubric:: Attributes + + .. autosummary:: + + ~SetRegistryItemSignal.item_key + ~SetRegistryItemSignal.item_value + + + + + ---- + + + + .. automethod:: __init__ diff --git a/app/sghi-commons/docs/source/api/sghi.registry.rst b/app/sghi-commons/docs/source/api/sghi.registry.rst index a7c19a5..3019024 100644 --- a/app/sghi-commons/docs/source/api/sghi.registry.rst +++ b/app/sghi-commons/docs/source/api/sghi.registry.rst @@ -2,3 +2,35 @@ ============= .. automodule:: sghi.registry + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + :toctree: + :template: class.rst + + Registry + RemoveRegistryItemSignal + SetRegistryItemSignal + + + + + + .. rubric:: Exceptions + + .. autosummary:: + :toctree: + :template: exception.rst + + NoSuchRegistryItemError diff --git a/app/sghi-commons/docs/source/api/sghi.task.common.Chainable.rst b/app/sghi-commons/docs/source/api/sghi.task.common.Chainable.rst index fbc3728..25f5abd 100644 --- a/app/sghi-commons/docs/source/api/sghi.task.common.Chainable.rst +++ b/app/sghi-commons/docs/source/api/sghi.task.common.Chainable.rst @@ -1,11 +1,11 @@ -sghi.task.common.Chainable +sghi.task.common.Chainable ========================== .. currentmodule:: sghi.task.common .. autoclass:: Chainable :members: - :special-members: __enter__, __exit__, __call__, __getattr__, __setattr__ + :special-members: __contains__, __delitem__, __enter__, __exit__, __call__, __getattr__, __setattr__, __getitem__, __setitem__ :show-inheritance: :inherited-members: :member-order: groupwise diff --git a/app/sghi-commons/docs/source/api/sghi.task.common.Consumer.rst b/app/sghi-commons/docs/source/api/sghi.task.common.Consumer.rst index 37e3d54..fe83775 100644 --- a/app/sghi-commons/docs/source/api/sghi.task.common.Consumer.rst +++ b/app/sghi-commons/docs/source/api/sghi.task.common.Consumer.rst @@ -1,11 +1,11 @@ -sghi.task.common.Consumer +sghi.task.common.Consumer ========================= .. currentmodule:: sghi.task.common .. autoclass:: Consumer :members: - :special-members: __enter__, __exit__, __call__, __getattr__, __setattr__ + :special-members: __contains__, __delitem__, __enter__, __exit__, __call__, __getattr__, __setattr__, __getitem__, __setitem__ :show-inheritance: :inherited-members: :member-order: groupwise diff --git a/app/sghi-commons/docs/source/api/sghi.task.common.Pipeline.rst b/app/sghi-commons/docs/source/api/sghi.task.common.Pipeline.rst index 20cb3d2..e6e020a 100644 --- a/app/sghi-commons/docs/source/api/sghi.task.common.Pipeline.rst +++ b/app/sghi-commons/docs/source/api/sghi.task.common.Pipeline.rst @@ -1,11 +1,11 @@ -sghi.task.common.Pipeline +sghi.task.common.Pipeline ========================= .. currentmodule:: sghi.task.common .. autoclass:: Pipeline :members: - :special-members: __enter__, __exit__, __call__, __getattr__, __setattr__ + :special-members: __contains__, __delitem__, __enter__, __exit__, __call__, __getattr__, __setattr__, __getitem__, __setitem__ :show-inheritance: :inherited-members: :member-order: groupwise diff --git a/app/sghi-commons/docs/source/api/sghi.task.concurrent.ConcurrentExecutor.rst b/app/sghi-commons/docs/source/api/sghi.task.concurrent.ConcurrentExecutor.rst index 2769e8a..8e2bda8 100644 --- a/app/sghi-commons/docs/source/api/sghi.task.concurrent.ConcurrentExecutor.rst +++ b/app/sghi-commons/docs/source/api/sghi.task.concurrent.ConcurrentExecutor.rst @@ -1,11 +1,11 @@ -sghi.task.concurrent.ConcurrentExecutor +sghi.task.concurrent.ConcurrentExecutor ======================================= .. currentmodule:: sghi.task.concurrent .. autoclass:: ConcurrentExecutor :members: - :special-members: __enter__, __exit__, __call__, __getattr__, __setattr__ + :special-members: __contains__, __delitem__, __enter__, __exit__, __call__, __getattr__, __setattr__, __getitem__, __setitem__ :show-inheritance: :inherited-members: :member-order: groupwise diff --git a/app/sghi-commons/docs/source/api/sghi.task.task.Task.rst b/app/sghi-commons/docs/source/api/sghi.task.task.Task.rst index 98779df..7c82f30 100644 --- a/app/sghi-commons/docs/source/api/sghi.task.task.Task.rst +++ b/app/sghi-commons/docs/source/api/sghi.task.task.Task.rst @@ -1,11 +1,11 @@ -sghi.task.task.Task +sghi.task.task.Task =================== .. currentmodule:: sghi.task.task .. autoclass:: Task :members: - :special-members: __enter__, __exit__, __call__, __getattr__, __setattr__ + :special-members: __contains__, __delitem__, __enter__, __exit__, __call__, __getattr__, __setattr__, __getitem__, __setitem__ :show-inheritance: :inherited-members: :member-order: groupwise diff --git a/app/sghi-commons/docs/source/api/sghi.task.task.TaskWithOptionalInput.rst b/app/sghi-commons/docs/source/api/sghi.task.task.TaskWithOptionalInput.rst index b4c3308..61a11d8 100644 --- a/app/sghi-commons/docs/source/api/sghi.task.task.TaskWithOptionalInput.rst +++ b/app/sghi-commons/docs/source/api/sghi.task.task.TaskWithOptionalInput.rst @@ -1,11 +1,11 @@ -sghi.task.task.TaskWithOptionalInput +sghi.task.task.TaskWithOptionalInput ==================================== .. currentmodule:: sghi.task.task .. autoclass:: TaskWithOptionalInput :members: - :special-members: __enter__, __exit__, __call__, __getattr__, __setattr__ + :special-members: __contains__, __delitem__, __enter__, __exit__, __call__, __getattr__, __setattr__, __getitem__, __setitem__ :show-inheritance: :inherited-members: :member-order: groupwise diff --git a/app/sghi-commons/src/sghi/app.py b/app/sghi-commons/src/sghi/app.py index 4427639..3e9dd7a 100644 --- a/app/sghi-commons/src/sghi/app.py +++ b/app/sghi-commons/src/sghi/app.py @@ -20,13 +20,17 @@ from .config import Config, SettingInitializer from .dispatch import Dispatcher +from .registry import Registry # ============================================================================= # GLOBAL APPLICATION/TOOL CONSTANTS # ============================================================================= dispatcher: Final[Dispatcher] = Dispatcher.of() -"""The application dispatcher.""" +"""The main application dispatcher.""" + +registry: Final[Registry] = Registry.of() +"""The main application registry.""" conf: Final[Config] = Config.of_awaiting_setup() """The application configurations. diff --git a/app/sghi-commons/src/sghi/registry/__init__.py b/app/sghi-commons/src/sghi/registry/__init__.py index e69de29..762ac40 100644 --- a/app/sghi-commons/src/sghi/registry/__init__.py +++ b/app/sghi-commons/src/sghi/registry/__init__.py @@ -0,0 +1,325 @@ +from __future__ import annotations + +from abc import ABCMeta, abstractmethod +from dataclasses import dataclass, field +from typing import Any + +from sghi.dispatch import Dispatcher, Signal +from sghi.exceptions import SGHIError +from sghi.utils import ensure_not_none + +# ============================================================================= +# EXCEPTIONS +# ============================================================================= + + +class NoSuchRegistryItemError(SGHIError, LookupError): + """Non-existent :class:`Registry` item access error. + + This is raised when trying to access or delete an item that does not exist + in a ``Registry``. + """ + + def __init__(self, item_key: str, message: str | None = None) -> None: + """Initialize a ``NoSuchRegistryItemError`` with the given properties. + + :param item_key: The key of the missing item. + :param message: An optional message for the resulting exception. + If none is provided, then a generic one is automatically generated. + """ + self._item_key: str = ensure_not_none( + item_key, "'item_key' MUST not be None.", + ) + super().__init__( + message=message or ( + "Item with key '%s' does not exist in the registry." % + self._item_key + ), + ) + + @property + def item_key(self) -> str: + """Return the missing item's key whose attempted access resulted in + this exception being raised. + + :return: The missing item's key. + """ + return self._item_key + + +# ============================================================================= +# SIGNALS +# ============================================================================= + +@dataclass(frozen=True, slots=True) +class SetRegistryItemSignal(Signal): + """Signal indicating the setting of an item in the :class:`Registry`. + + This signal is emitted when a new item is added or updated to the + ``Registry``. + """ + item_key: str = field() + item_value: Any = field(repr=False) + + +@dataclass(frozen=True, slots=True) +class RemoveRegistryItemSignal(Signal): + """Signal indicating the removal of an item from the :class:`Registry`. + + This signal is emitted when an item is removed from the ``Registry``. + """ + item_key: str = field() + +# ============================================================================= +# REGISTRY INTERFACE +# ============================================================================= + + +class Registry(metaclass=ABCMeta): + """An interface representing a registry for storing key-value pairs. + + A ``Registry`` allows for storage and retrieval of values using unique + keys. It supports basic dictionary-like operations and provides an + interface for interacting with registered items. + + A ``Registry`` also comes bundled with a :class:`~sghi.dispatch.Dispatcher` + whose responsibility is to emit :class:`signals` whenever changes + to the registry are made. It allows other components to subscribe to these + signals and react accordingly. + + For a list of supported signals, see the :attr:`dispatcher` property. + """ + + __slots__ = () + + @abstractmethod + def __contains__(self, key: str) -> bool: + """Check if the registry contains an item with the specified key. + + :param key: The key to check for. + + :return: ``True`` if the key exists in the registry, ``False`` + otherwise. + """ + ... + + @abstractmethod + def __delitem__(self, key: str) -> None: + """Remove an item from the registry using the specified key. + + If successful, this will result in a :class:`RemoveRegistryItemSignal` + being emitted. + + :param key: The key of the item to remove. + + :return: None. + + :raises NoSuchRegistryItemError: If the key does not exist in the + registry. + """ + ... + + @abstractmethod + def __getitem__(self, key: str) -> Any: # noqa: ANN401 + """ + Retrieve the value associated with the specified key from the registry. + + :param key: The key of the item to retrieve. + + :return: The value associated with the key. + + :raises NoSuchRegistryItemError: If the key does not exist in the + registry. + """ + ... + + @abstractmethod + def __setitem__(self, key: str, value: Any) -> None: # noqa: ANN401 + """Set the value associated with the specified key in the registry. + + If successful, this will result in a :class:`SetRegistryItemSignal` + being emitted. + + :param key: The key of the item to set. + :param value: The value to associate with the key. + + :return: None. + """ + ... + + @property + @abstractmethod + def dispatcher(self) -> Dispatcher: + """Get the :class:`Dispatcher` associated with the ``Registry``. + + The dispatcher is responsible for emitting signals when items are added + or removed from the registry. It allows other components to subscribe + to these signals and react accordingly. + + ---- + + **Supported Signals** + + Each ``Registry`` implementation should at the very least support the + following signals: + + - :class:`SetRegistryItemSignal` -> This signal is emitted when either + a new item is added to the ``Registry``, or an existing item updated. + It includes information about the item's key and value. + - :class:`RemoveRegistryItemSignal` -> This signal is emitted when an + item is removed from the registry. It includes information about the + item's key. + + These signals provide a way for other parts of the application to react + to changes in the registry, making the system more dynamic and + responsive. + + + :return: The dispatcher instance associated with the registry. + """ + ... + + @abstractmethod + def get(self, key: str, default: Any = None) -> Any: # noqa: ANN401 + """ + Retrieve the value associated with the specified key from the + ``Registry``, with an optional default value if the key does not exist. + + :param key: The key of the item to retrieve. + + :param default: The default value to return if the key does not exist. + Defaults to ``None`` when not specified. + + :return: The value associated with the key, or the default value. + """ + ... + + @abstractmethod + def pop(self, key: str, default: Any = None) -> Any: # noqa: ANN401 + """ + Remove and return the value associated with the specified key from the + ``Registry``, or the specified default if the key does not exist. + + .. note:: + + A :class:`RemoveRegistryItemSignal` will only be emitted + `if and only if` an item with the specified key existed in the + ``Registry`` and was thus removed. + + :param key: The key of the item to remove. + :param default: The default value to return if the key does not exist. + Defaults to ``None`` when not specified. + + :return: The value associated with the key, or the default value. + """ + ... + + @abstractmethod + def setdefault(self, key: str, value: Any) -> Any: # noqa: ANN401 + """ + Retrieve the value associated with the specified key from the + ``Registry``, or set it if the key does not exist. + + .. note:: + + A :class:`SetRegistryItemSignal` will only be emitted + `if and only if` an item with the specified key does not exist in + the ``Registry`` and thus the new default value was set. + + :param key: The key of the item to retrieve or set. + :param value: The value to associate with the key if it does not exist. + + :return: The value associated with the key, or the newly set value. + """ + ... + + @staticmethod + def of(dispatcher: Dispatcher | None = None) -> Registry: + """Factory method to create ``Registry`` instances. + + :param dispatcher: An optional ``Dispatcher`` instance to associate + with the registry. A new ``Dispatcher`` instance will be created if + not specified. + + :return: A ``Registry`` instance. + """ + return _RegistryImp(dispatcher=dispatcher or Dispatcher.of()) + + +# ============================================================================= +# REGISTRY IMPLEMENTATIONS +# ============================================================================= + +class _RegistryImp(Registry): + """An implementation of the Registry interface.""" + + __slots__ = ("_dispatcher", "_items") + + def __init__(self, dispatcher: Dispatcher) -> None: + super().__init__() + self._dispatcher: Dispatcher = ensure_not_none( + dispatcher, "'dispatcher' MUST not be None.", + ) + self._items: dict[str, Any] = {} + + def __contains__(self, key: str) -> bool: + return key in self._items + + def __delitem__(self, key: str) -> None: + ensure_not_none(key, "'key' MUST not be None.") + try: + del self._items[key] + self.dispatcher.send(RemoveRegistryItemSignal(item_key=key)) + except KeyError: + raise NoSuchRegistryItemError(item_key=key) from None + + def __getitem__(self, key: str) -> Any: # noqa: ANN401 + ensure_not_none(key, "'key' MUST not be None.") + try: + return self._items[key] + except KeyError: + raise NoSuchRegistryItemError(item_key=key) from None + + def __setitem__(self, key: str, value: Any) -> None: # noqa: ANN401 + ensure_not_none(key, "'key' MUST not be None.") + self._items[key] = value + self.dispatcher.send( + SetRegistryItemSignal(item_key=key, item_value=value), + ) + + @property + def dispatcher(self) -> Dispatcher: + return self._dispatcher + + def get(self, key: str, default: Any = None) -> Any: # noqa: ANN401 + ensure_not_none(key, "'key' MUST not be None.") + return self._items.get(key, default) + + def pop(self, key: str, default: Any = None) -> Any: # noqa: ANN401 + ensure_not_none(key, "'key' MUST not be None.") + try: + value = self._items.pop(key) + self._dispatcher.send(RemoveRegistryItemSignal(item_key=key)) + return value + except KeyError: + return default + + def setdefault(self, key: str, value: Any) -> Any: # noqa: ANN401 + ensure_not_none(key, "'key' MUST not be None.") + try: + return self[key] + except LookupError: + self[key] = value + return value + +# ============================================================================= +# MODULE EXPORTS +# ============================================================================= + + +__all__ = ( + "NoSuchRegistryItemError", + "Registry", + "RemoveRegistryItemSignal", + "SetRegistryItemSignal", +)