Source code for pyrokinetics.factory
"""
Defines generic factory object.
Factory objects used throughout this project may inherit this class and add additional
features, such as filetype inference.
"""
from typing import Any, Generator, Tuple, Type
[docs]
class Factory:
"""
A variation on the generic 'factory' pattern, as defined in the classic 'Design
Patterns' by the gang of four.
Creates a mapping of keys to types. New types can be 'registered' to the factory
via a key, and instances of those types can be created by the `create` method by
passing that key. Multiple keys can be registered to the same type. Optionally, the
factory can only return types derived from a given super class.
Parameters
----------
super_class
Registered classes must be derived from this type.
"""
[docs]
def __init__(self, super_class: Type = object):
self._super_class = super_class
self._registered_types = {}
[docs]
def type(self, key: str) -> Type:
"""
Returns type associated with a given key. Raises ``KeyError`` if there is no
type registered with that key.
"""
try:
return self._registered_types[key]
except KeyError as exc:
raise KeyError(
f"'{key}' is not recognised as a type of {self._super_class.__name__}"
) from exc
[docs]
def create(self, key: str, *args, **kwargs) -> Any:
"""Create a new object of type ``key``, forwarding all arguments."""
return self.type(key)(*args, **kwargs)
[docs]
def register(self, key: str, cls: Type) -> None:
"""
Register a new type with the factory. ``cls`` must be derived from
the ``super_class`` that was passed to the ``__init__`` method.
"""
try:
if issubclass(cls, self._super_class):
self._registered_types[key] = cls
else:
raise ValueError(
f"Classes registered must subclass {self._super_class.__name__}"
)
except TypeError as e:
raise TypeError("Only classes may be registered") from e
except ValueError as e:
raise TypeError(str(e))
def __call__(self, key: str, *args, **kwargs) -> Any:
"""Alternative to `create`"""
return self.create(key, *args, **kwargs)
def __getitem__(self, key: str) -> Type:
"""Alternative to `type`"""
return self.type(key)
def __setitem__(self, key: str, cls: Type) -> None:
"""Alternative to `register`"""
self.register(key, cls)
def __contains__(self, key: str) -> None:
"""Check if key is registered by the factory"""
return key in self._registered_types
def __iter__(self) -> Generator[str, None, None]:
"""Iterate over registered keys"""
return iter(self._registered_types)
[docs]
def items(self) -> Generator[Tuple[str, Type], None, None]:
"""Dict-like items iterator"""
return self._registered_types.items()