| # Copyright 2015 The ChromiumOS Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Abstract ObjectFactory class used for injection of external dependencies.""" |
| |
| |
| class ObjectFactoryIllegalOperation(Exception): |
| """Raised when attempting an illegal ObjectFactory operation.""" |
| |
| |
| _NO_SINGLETON_INSTANCE = object() |
| |
| |
| class ObjectFactory: |
| """Abstract object factory, used for injection of external dependencies. |
| |
| A call to Setup(...) is necessary before a call to GetInstance(). |
| """ |
| |
| _object_name = "" |
| _is_setup = False |
| _setup_type = None |
| _setup_instance = None |
| _types = {} |
| |
| def __init__( |
| self, object_name, setup_types, allowed_transitions=None |
| ) -> None: |
| """ObjectFactory constructor. |
| |
| Args: |
| object_name: Human readable name for the type of object that this |
| factory generates. |
| setup_types: A (set up type name -> generator function) dictionary, |
| which teaches ObjectFactory how to construct instances after |
| setup has been called. For set up types where a singleton |
| instance is specified at setup(...) time, generator function |
| should be None. |
| allowed_transitions: Optional function, where |
| allowed_transitions(from_type, to_type) specifies whether |
| transition from |from_type| to |to_type| is allowed. If |
| unspecified, no transitions are allowed. |
| """ |
| |
| self._object_name = object_name |
| self._types = setup_types |
| self._allowed_transitions = allowed_transitions |
| |
| def Setup( |
| self, setup_type, singleton_instance=_NO_SINGLETON_INSTANCE |
| ) -> None: |
| # Prevent set up to unknown types. |
| if setup_type not in self._types: |
| raise ObjectFactoryIllegalOperation( |
| "Unknown %s setup_type %s" % (self._object_name, setup_type) |
| ) |
| |
| # Prevent illegal setup transitions. |
| if self._is_setup: |
| if self._allowed_transitions: |
| if not self._allowed_transitions(self._setup_type, setup_type): |
| raise ObjectFactoryIllegalOperation( |
| "Illegal set up transition from %s to %s." |
| % (self._setup_type, setup_type) |
| ) |
| else: |
| raise ObjectFactoryIllegalOperation( |
| "%s already set up." % self._object_name |
| ) |
| |
| # Allow singleton_instance if and only if factory method for this type |
| # is None. |
| instance_supplied = singleton_instance != _NO_SINGLETON_INSTANCE |
| factory_is_none = self._types[setup_type] is None |
| if instance_supplied != factory_is_none: |
| raise ObjectFactoryIllegalOperation( |
| "singleton_instance should be supplied if and only if " |
| "setup_type has a factory that is None." |
| ) |
| |
| self._setup_type = setup_type |
| self._setup_instance = singleton_instance |
| self._is_setup = True |
| |
| @property |
| def is_setup(self): |
| """Returns True iff a call to get_instance is expected to succeed.""" |
| return self._is_setup |
| |
| @property |
| def setup_type(self): |
| """Returns the setup_type.""" |
| return self._setup_type |
| |
| def GetInstance(self): |
| """Returns an object instance iff setup has been called. |
| |
| Raises: |
| ObjectFactoryIllegalOperation: if setup has not yet been called. |
| """ |
| if not self.is_setup: |
| raise ObjectFactoryIllegalOperation( |
| "%s is not set up." % self._object_name |
| ) |
| if self._setup_instance != _NO_SINGLETON_INSTANCE: |
| return self._setup_instance |
| return self._types[self.setup_type]() |
| |
| def _clear_setup(self) -> None: |
| """Clear setup, for testing purposes only.""" |
| self._setup_type = None |
| self._is_setup = False |
| self._setup_instance = _NO_SINGLETON_INSTANCE |