From 52f0fdba40b6cc19d7915565b822fbfa965caa12 Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Mon, 13 Jul 2020 17:40:08 +0200 Subject: [PATCH] added Registry class and instances() function, which allow to automatically register/show instances of a class (and optionally its subclasses); for convenience, also added a combined Registry and ABC base class --- slic/utils/metaclasses.py | 35 +++++++++++++++++++++ slic/utils/registry.py | 66 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 slic/utils/metaclasses.py create mode 100644 slic/utils/registry.py diff --git a/slic/utils/metaclasses.py b/slic/utils/metaclasses.py new file mode 100644 index 000000000..98a89e3db --- /dev/null +++ b/slic/utils/metaclasses.py @@ -0,0 +1,35 @@ +from abc import ABCMeta + +from .registry import RegistryMeta + + +def combine_classes(*bases): + """ + Convenience wrapper around type(name, bases, namespace) + for dynamically combining bases into a single class + """ + name = "".join(a.__name__ for a in bases) + namespace = {} + return type(name, bases, namespace) + + + +class RegistryABC(object, metaclass=combine_classes(RegistryMeta, ABCMeta)): + """ + Helper class that allows adding registry and ABC functionalities via inheritance + Does nothing but set a combined RegistryMeta and ABCMeta as metaclass + """ + pass + + + +## The above corresponds to: + +#class RegistryMetaABCMeta(RegistryMeta, ABCMeta): # combine metaclasses +# pass + +#class RegistryABC(object, metaclass=RegistryMetaABCMeta): +# pass + + + diff --git a/slic/utils/registry.py b/slic/utils/registry.py new file mode 100644 index 000000000..79fc33733 --- /dev/null +++ b/slic/utils/registry.py @@ -0,0 +1,66 @@ +import weakref + +from .utils import typename + + +class RegistryMeta(type): + """ + Metaclass containing the registry logic + In most cases, it will be more convenient to inherit from Registry instead of using RegistryMeta directly + """ + + def __init__(cls, name, bases, namespace): + super().__init__(name, bases, namespace) # creates the class + cls.__instances__ = weakref.WeakSet() + + def __call__(cls, *args, **kwargs): + inst = super().__call__(*args, **kwargs) # creates the instance (calls __new__ and __init__ methods) + cls.__instances__.add(inst) + return inst + + + +class Registry(object, metaclass=RegistryMeta): + """ + Helper class that allows adding registry functionality via inheritance + Does nothing but set RegistryMeta as metaclass + """ + pass + + + +def instances(reg, recursive=True): + """ + Return a set of all instances of reg + If recursive=True also include instances of subclasses of reg + """ + res = _collect_instances(reg, recursive=recursive) + return set(res) + + +def _collect_instances(reg, recursive=True): + """ + Return a weakref.WeakSet of all instances of reg + If recursive=True also include instances of subclasses of reg + """ + _check_registry(reg) + instances = reg.__instances__.copy() + if recursive: + for subcls in reg.__subclasses__(): + instances |= _collect_instances(subcls, recursive=recursive) + return instances + + +def _check_registry(reg): + """ + Try whether reg has __instances__ attribute, if missing raise TypeError + """ + try: + reg.__instances__ + except AttributeError as exc: + tname = typename(reg) + cname = reg.__name__ + raise TypeError(f"{tname} object '{cname}' has no registry functionality") from exc + + +