| |
| import inspect |
| import threading |
| import warnings |
| from collections import OrderedDict |
| from typing import Type, TypeVar |
|
|
| _lock = threading.RLock() |
| T = TypeVar('T') |
|
|
|
|
| def _accquire_lock() -> None: |
| """Acquire the module-level lock for serializing access to shared data. |
| |
| This should be released with _release_lock(). |
| """ |
| if _lock: |
| _lock.acquire() |
|
|
|
|
| def _release_lock() -> None: |
| """Release the module-level lock acquired by calling _accquire_lock().""" |
| if _lock: |
| _lock.release() |
|
|
|
|
| class ManagerMeta(type): |
| """The metaclass for global accessible class. |
| |
| The subclasses inheriting from ``ManagerMeta`` will manage their |
| own ``_instance_dict`` and root instances. The constructors of subclasses |
| must contain the ``name`` argument. |
| |
| Examples: |
| >>> class SubClass1(metaclass=ManagerMeta): |
| >>> def __init__(self, *args, **kwargs): |
| >>> pass |
| AssertionError: <class '__main__.SubClass1'>.__init__ must have the |
| name argument. |
| >>> class SubClass2(metaclass=ManagerMeta): |
| >>> def __init__(self, name): |
| >>> pass |
| >>> # valid format. |
| """ |
|
|
| def __init__(cls, *args): |
| cls._instance_dict = OrderedDict() |
| params = inspect.getfullargspec(cls) |
| params_names = params[0] if params[0] else [] |
| assert 'name' in params_names, f'{cls} must have the `name` argument' |
| super().__init__(*args) |
|
|
|
|
| class ManagerMixin(metaclass=ManagerMeta): |
| """``ManagerMixin`` is the base class for classes that have global access |
| requirements. |
| |
| The subclasses inheriting from ``ManagerMixin`` can get their |
| global instances. |
| |
| Examples: |
| >>> class GlobalAccessible(ManagerMixin): |
| >>> def __init__(self, name=''): |
| >>> super().__init__(name) |
| >>> |
| >>> GlobalAccessible.get_instance('name') |
| >>> instance_1 = GlobalAccessible.get_instance('name') |
| >>> instance_2 = GlobalAccessible.get_instance('name') |
| >>> assert id(instance_1) == id(instance_2) |
| |
| Args: |
| name (str): Name of the instance. Defaults to ''. |
| """ |
|
|
| def __init__(self, name: str = '', **kwargs): |
| assert isinstance(name, str) and name, \ |
| 'name argument must be an non-empty string.' |
| self._instance_name = name |
|
|
| @classmethod |
| def get_instance(cls: Type[T], name: str, **kwargs) -> T: |
| """Get subclass instance by name if the name exists. |
| |
| If corresponding name instance has not been created, ``get_instance`` |
| will create an instance, otherwise ``get_instance`` will return the |
| corresponding instance. |
| |
| Examples |
| >>> instance1 = GlobalAccessible.get_instance('name1') |
| >>> # Create name1 instance. |
| >>> instance.instance_name |
| name1 |
| >>> instance2 = GlobalAccessible.get_instance('name1') |
| >>> # Get name1 instance. |
| >>> assert id(instance1) == id(instance2) |
| |
| Args: |
| name (str): Name of instance. Defaults to ''. |
| |
| Returns: |
| object: Corresponding name instance, the latest instance, or root |
| instance. |
| """ |
| _accquire_lock() |
| assert isinstance(name, str), \ |
| f'type of name should be str, but got {type(cls)}' |
| instance_dict = cls._instance_dict |
| |
| if name not in instance_dict: |
| instance = cls(name=name, **kwargs) |
| instance_dict[name] = instance |
| elif kwargs: |
| warnings.warn( |
| f'{cls} instance named of {name} has been created, ' |
| 'the method `get_instance` should not accept any other ' |
| 'arguments') |
| |
| _release_lock() |
| return instance_dict[name] |
|
|
| @classmethod |
| def get_current_instance(cls): |
| """Get latest created instance. |
| |
| Before calling ``get_current_instance``, The subclass must have called |
| ``get_instance(xxx)`` at least once. |
| |
| Examples |
| >>> instance = GlobalAccessible.get_current_instance() |
| AssertionError: At least one of name and current needs to be set |
| >>> instance = GlobalAccessible.get_instance('name1') |
| >>> instance.instance_name |
| name1 |
| >>> instance = GlobalAccessible.get_current_instance() |
| >>> instance.instance_name |
| name1 |
| |
| Returns: |
| object: Latest created instance. |
| """ |
| _accquire_lock() |
| if not cls._instance_dict: |
| raise RuntimeError( |
| f'Before calling {cls.__name__}.get_current_instance(), you ' |
| 'should call get_instance(name=xxx) at least once.') |
| name = next(iter(reversed(cls._instance_dict))) |
| _release_lock() |
| return cls._instance_dict[name] |
|
|
| @classmethod |
| def check_instance_created(cls, name: str) -> bool: |
| """Check whether the name corresponding instance exists. |
| |
| Args: |
| name (str): Name of instance. |
| |
| Returns: |
| bool: Whether the name corresponding instance exists. |
| """ |
| return name in cls._instance_dict |
|
|
| @property |
| def instance_name(self) -> str: |
| """Get the name of instance. |
| |
| Returns: |
| str: Name of instance. |
| """ |
| return self._instance_name |
|
|