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