|  | # -*- coding: utf-8 -*- | 
|  | # Copyright 2015 The Chromium OS Authors. All rights reserved. | 
|  | # 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.""" | 
|  |  | 
|  | from __future__ import print_function | 
|  |  | 
|  | import sys | 
|  |  | 
|  |  | 
|  | assert sys.version_info >= (3, 6), 'This module requires Python 3.6+' | 
|  |  | 
|  |  | 
|  | class ObjectFactoryIllegalOperation(Exception): | 
|  | """Raised when attemping an illegal ObjectFactory operation.""" | 
|  |  | 
|  | _NO_SINGLETON_INSTANCE = object() | 
|  |  | 
|  | class ObjectFactory(object): | 
|  | """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): | 
|  | """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): | 
|  | # 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): | 
|  | """Clear setup, for testing purposes only.""" | 
|  | self._setup_type = None | 
|  | self._is_setup = False | 
|  | self._setup_instance = _NO_SINGLETON_INSTANCE |