Source code for soma.factory
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import print_function
import six
from importlib import import_module
from pkgutil import iter_modules
[docs]def find_subclasses_in_module(module_name, parent_class):
'''
Finds all the classes defined in a Python module that derive from a
given class or class name. If the module is a package, it also look
into submodules.
'''
if isinstance(parent_class, six.string_types):
check = lambda item: (isinstance(item, type) and
item.__module__ == module_name and
parent_class in (i.__name__
for i in item.__mro__))
else:
check = lambda item: (isinstance(item, type) and
item.__module__ == module_name and
issubclass(item, parent_class))
for i in find_items_in_module(module_name, check):
yield i
[docs]def find_items_in_module(module_name, check):
'''
Finds all the items defined in a Python module that where *check(cls)* is
*True*. If the module is a package, it also look recursively into
submodules.
'''
try:
module = import_module(module_name)
except ImportError:
return
for i in six.itervalues(module.__dict__):
if check(i):
yield i
path = getattr(module, '__path__', None)
if path:
for importer, submodule_name, ispkg in iter_modules(path):
for j in find_items_in_module('%s.%s' %
(module.__name__, submodule_name), check):
yield j
[docs]class ClassFactory(object):
'''
*ClassFactory* is the base class for creating factories that can look
for classes in Python modules and create instances.
'''
def __init__(self, class_types={}):
# List of Python modules where classes are looked for
self.module_path = []
# Instance-level association between a class_type (which is a string)
# and the corresponding class. There can also be class-level
# class_types (see get_class method).
self.class_types = {}
# Cache of instances returned by get method. This is a dictionary
# whose keys are (class_type, factory_id) and values are instances.
self.instances = {}
[docs] def find_class(self, class_type, factory_id):
'''
Finds a class deriving of the class corresponding to *class_type* and
whose *factory_id* attribute has a given value. Look for all
subclasses of parent class declared in the modules of
:attr:`self.module_path`
and returns the first one having ``cls.factory_id == factory_id``. If
none is found, returns *None*.
'''
base_class = self.get_class(class_type)
for module_name in self.module_path:
for cls in find_subclasses_in_module(module_name, base_class):
if cls.factory_id == factory_id:
return cls
return None
[docs] def get_class(self, class_type):
'''
Returns the class corresponding to a given class type. First look
for a dictionary in self.class_types, then look into parent
classes.
'''
class_types = getattr(self, 'class_types', None)
if class_types:
cls = class_types.get(class_type)
if cls is not None:
return cls
for parent_class in self.__class__.__mro__:
class_types = getattr(parent_class, 'class_types', None)
if class_types:
cls = class_types.get(class_type)
if cls is not None:
return cls
raise ValueError('Unknown class type: %s' % class_type)
[docs] def get(self, class_type, factory_id):
'''
Returns an instance of the class identified by *class_type* and
*factory_id*. There can be only one instance per *class_type* and
*factory_id*. Once created with the first call of this method, it
is stored in :attr:`self.instances` and simply returned in subsequent
calls.
If *get_class* is *True*, returns the class instead of an instance
'''
instance = self.instances.get((class_type, factory_id))
if instance is None:
cls = self.find_class(class_type, factory_id)
if cls is not None:
if hasattr(cls.__init__, '__code__') \
and cls.__init__.__code__.co_argcount != 1:
# The class constructor takes arguments: we cannot
# instantiate it: register the class itself
instance = cls
else:
instance = cls()
self.instances[(class_type, factory_id)] = instance
else:
raise ValueError('Cannot find a class for class type "%s" '
'and factory id "%s"' % (class_type,
factory_id))
return instance