registry - python object management

You can use these classes to manage collections of python objects or classes. Registries provide a unified interface to validate, store, search and filter python objects and classes.

For class registry in general you need to specify a list of accepted base classes by defining get_base_classes() which is required. You can also want to change default registry key function get_key() which uses class __name__ by default.

import abc
from typing import Type

class BaseConverter(abc.ABC):
  data_type: str

  @abc.abstractmethod
  def convert(self, data):
    ...

class ConvertersRegistry(ClassRegistry[str, Type[BaseConverter]]):

  @classmethod
  def get_base_classes(cls):
    return (BaseConverter,)

  def get_key(self, obj):
    return obj.data_type

CONVERTERS = ConvertersRegistry()

Then you can register new classes anywhere in your code.

class XMLConverter(BaseConverter):
  data_type = 'xml'

  def convert(self, data):
    ...

CONVERTERS.register(XMLConverter)

You can now dynamically load your classes in your code.

def convert_data(data, data_type):
  if data_type in CONVERTERS:
    conv = CONVERTERS[data_type]()
    return conv.convert(data)
  else:
    raise NotFound('Unsupported data type.')
class ClassRegistry[source]

Bases: Registry, Generic[_Key, _Obj], ABC

A default registry for classes.

It can be used to register a set of classes for dynamic object initialization based on class names or other parameters.

To create your own registry you need to define get_base_classes() method returning a tuple of base classesd. All newly registered classes then must be a subclass of these bases. Additionaly you may want to configure at which keys your classes will be stored by changing get_key() method. It uses __name__ of a class by default.

It’s also a nice idea to provide a generic type hint for your registry key and item types in square brackets.

>>> class BaseC(abc.ABC):
...     name: str
...     value: int
>>> class Classes(ClassRegistry[str, type[BaseC]]):
...     @classmethod
...     def get_base_classes(cls):
...         return (BaseC,)
...
...     def get_key(self, obj: type[BaseC]) -> str:
...         return obj.name  # not required, the registry uses `__name__` by default

When you have your registry configured, you can initialize a registry object and start adding your classes! There are a few methods such as register(), register_from_module() and register_from_namespace() to help you with registering classes.

>>> reg = Classes()
>>> class C(BaseC):
...     name = 'my_class'
...     value = 1
>>> reg.register(C)
'my_class'
>>> reg.register_from_namespace({'other_class': C}, use_key_names=True)
frozenset({'other_class'})

You can access registered classes by their keys or search for them using find() and find_all() methods.

>>> reg['my_class'].value
1
>>> reg.find(condition=lambda o: o.value > 0).__name__
'C'

You can also find subclasses of a spacific class(es) using find_subclass() and find_subclasses() special methods.

>>> reg.find_subclass(BaseC).__name__
'C'
find_subclasses(*bases: Collection[_Obj] | _Obj) Generator[_Obj, None, None][source]

Find all subclasses matching bases. A shortcut to find_all method.

find_subclass(*bases: Collection[_Obj] | _Obj) _Obj[source]

Find a first subclass matching bases. A shortcut to find method.

get_key(obj: _Obj) _Key[source]

Get a class name.

__init__(objects: dict = <factory>, raise_if_exists: bool = False) None
can_register(obj, /) bool

Check if an object can be registered.

clear() None

Unlink all registered objects. Use it with caution.

find(condition: Callable[[Any], bool]) _Obj

Find an object matching a condition.

find_all(condition: Callable[[Any], bool]) Generator[_Obj, None, None]

Find all objects matching a condition.

get(k[, d]) D[k] if k in D, else d.  d defaults to None.
items() a set-like object providing a view on D's items
keys() a set-like object providing a view on D's keys
register(obj: _Obj, /, name: _Key = None) _Key

Register an object in the registry and return a key under which it has been registered.

Parameters:
  • obj – object to register

  • name – provide a custom name (not recommended)

Raises:

RegistrationFailed – if an object can’t be registered

Returns:

object key in the registry

register_from_module(module: object, *, use_key_names: bool = False) frozenset[_Key]

Register classes from current object.

Parameters:
  • module – any object with __dict__

  • use_key_names – use namespace key names instead of a registry name function

Returns:

a set of registered keys

register_from_namespace(namespace: Mapping[_Key, _Obj], *, use_key_names: bool = False) frozenset[_Key]

Register all supported objects from an arbitrary mapping.

Incompatible objects will be ignored. Returns a set of registered keys.

Parameters:
  • namespace – any mapping

  • use_key_names – use namespace key names instead of a registry name function

Returns:

a set of registered keys

register_many(obj: Collection[_Obj], /) tuple[_Key, ...]

Register multiple objects at once.

Parameters:

obj – objects

Raises:

RegistrationFailed – if any of the objects can’t be registered

Returns:

a tuple of object keys

values() an object providing a view on D's values
class ObjectRegistry[source]

Bases: Registry, Generic[_Key, _Obj], ABC

Python objects registry.

It can be used to store specific objects in a single mapping (for example for storing application services in a single service registry).

>>> class BaseC(abc.ABC):
...     name: str
...
...     @staticmethod
...     def get_value(): return 1
...
...     def __repr__(self): return f'<[{self.name}]>'

The initialization process and overall workflow is similar to ClassRegistry with the only difference being that it stores objects instead of classes.

>>> class Objects(ObjectRegistry[str, BaseC]):
...
...     @classmethod
...     def get_base_classes(cls):
...         return (BaseC,)
...
...     def get_key(self, obj: BaseC) -> str:
...         return obj.name  # not necessary, the registry uses `__name__` by default
>>> reg = Objects()

You can register objects and dynamically access them by their identifiers.

>>> class C(BaseC):
...     name = 'C_name'
>>> reg.register(C())
'C_name'
>>> reg['C_name'].get_value()
1

You can register objects from a namespace (any dictionary) using either the registry name function or names used in this dictionary (see register_from_namespace).

>>> reg.register_from_namespace({'Other_name': C()}, use_key_names=True)
frozenset({'Other_name'})

Standard mapping methods also work for registry classes.

>>> 'Other_name' in reg
True
>>> del reg['Other_name']

You can also find instances of a class(es) using find_instance() and find_instances() special methods.

>>> reg.find_instance(BaseC)
<[C_name]>
find_instances(*bases: Collection[type[_Obj]] | type[_Obj]) Generator[_Obj, None, None][source]

Find all subclasses matching bases. A shortcut to find_all method.

find_instance(*bases: Collection[type[_Obj]] | type[_Obj]) _Obj | None[source]

Find a first subclass matching bases. A shortcut to find method.

get_key(obj: _Obj) _Key[source]

Get a name by which a registered class will be referenced in the mapping.

__init__(objects: dict = <factory>, raise_if_exists: bool = False) None
can_register(obj, /) bool

Check if an object can be registered.

clear() None

Unlink all registered objects. Use it with caution.

find(condition: Callable[[Any], bool]) _Obj

Find an object matching a condition.

find_all(condition: Callable[[Any], bool]) Generator[_Obj, None, None]

Find all objects matching a condition.

get(k[, d]) D[k] if k in D, else d.  d defaults to None.
items() a set-like object providing a view on D's items
keys() a set-like object providing a view on D's keys
register(obj: _Obj, /, name: _Key = None) _Key

Register an object in the registry and return a key under which it has been registered.

Parameters:
  • obj – object to register

  • name – provide a custom name (not recommended)

Raises:

RegistrationFailed – if an object can’t be registered

Returns:

object key in the registry

register_from_module(module: object, *, use_key_names: bool = False) frozenset[_Key]

Register classes from current object.

Parameters:
  • module – any object with __dict__

  • use_key_names – use namespace key names instead of a registry name function

Returns:

a set of registered keys

register_from_namespace(namespace: Mapping[_Key, _Obj], *, use_key_names: bool = False) frozenset[_Key]

Register all supported objects from an arbitrary mapping.

Incompatible objects will be ignored. Returns a set of registered keys.

Parameters:
  • namespace – any mapping

  • use_key_names – use namespace key names instead of a registry name function

Returns:

a set of registered keys

register_many(obj: Collection[_Obj], /) tuple[_Key, ...]

Register multiple objects at once.

Parameters:

obj – objects

Raises:

RegistrationFailed – if any of the objects can’t be registered

Returns:

a tuple of object keys

values() an object providing a view on D's values
class FunctionRegistry[source]

Bases: Registry[str, Callable]

A very simple function registry.

You can use it to store and dynamically access functions by their identifiers.

>>> reg = FunctionRegistry()
>>> def inc_2(x: int): return x * 2
>>> reg.register(inc_2)
'inc_2'
>>> reg['inc_2'](1)
2
call(name: _Key, *args, **kws)[source]

Call a stored function with arguments.

get_key(obj: Callable) _Key[source]

Get a name by which a registered class will be referenced in the mapping.

__init__(objects: dict = <factory>, raise_if_exists: bool = False) None
can_register(obj, /) bool

Check if an object can be registered.

clear() None

Unlink all registered objects. Use it with caution.

find(condition: Callable[[Any], bool]) _Obj

Find an object matching a condition.

find_all(condition: Callable[[Any], bool]) Generator[_Obj, None, None]

Find all objects matching a condition.

get(k[, d]) D[k] if k in D, else d.  d defaults to None.
items() a set-like object providing a view on D's items
keys() a set-like object providing a view on D's keys
register(obj: _Obj, /, name: _Key = None) _Key

Register an object in the registry and return a key under which it has been registered.

Parameters:
  • obj – object to register

  • name – provide a custom name (not recommended)

Raises:

RegistrationFailed – if an object can’t be registered

Returns:

object key in the registry

register_from_module(module: object, *, use_key_names: bool = False) frozenset[_Key]

Register classes from current object.

Parameters:
  • module – any object with __dict__

  • use_key_names – use namespace key names instead of a registry name function

Returns:

a set of registered keys

register_from_namespace(namespace: Mapping[_Key, _Obj], *, use_key_names: bool = False) frozenset[_Key]

Register all supported objects from an arbitrary mapping.

Incompatible objects will be ignored. Returns a set of registered keys.

Parameters:
  • namespace – any mapping

  • use_key_names – use namespace key names instead of a registry name function

Returns:

a set of registered keys

register_many(obj: Collection[_Obj], /) tuple[_Key, ...]

Register multiple objects at once.

Parameters:

obj – objects

Raises:

RegistrationFailed – if any of the objects can’t be registered

Returns:

a tuple of object keys

values() an object providing a view on D's values
class RegistryError[source]

Bases: Exception

A base class for all registry errors.

class RegistrationFailed[source]

Bases: ValueError, RegistryError

Object cannot be registered in this registry.