from .common import log, get_fqdn
[docs]class Context(object):
'''
An IoC container for :func:`interface` s, :func:`service` s and :func:`component` s
:param parent: a parent context
:type parent: :class:`Context`
'''
def __init__(self, parent=None):
self.parent = parent
self.service_instances = {}
self.component_instances = {}
def __repr__(self):
if self.parent:
return '[Context %s (child of %s)]' % (id(self), id(self.parent))
else:
return '[Context %s]' % id(self)
[docs] def get_service(self, cls):
fqdn = get_fqdn(cls)
if fqdn not in self.service_instances:
self.service_instances[fqdn] = cls(self)
return self.service_instances[fqdn]
[docs] def get_component(self, cls):
fqdn = get_fqdn(cls)
if fqdn not in self.component_instances:
self.component_instances[fqdn] = cls(self)
return self.component_instances[fqdn]
[docs] def get_components(self, cls, ignore_exceptions=False):
for comp in cls.implementations:
try:
instance = self.get_component(comp)
yield instance
except Exception as e:
if ignore_exceptions:
log.error('Could not instantiate %s: %s', cls, e)
else:
raise
[docs]def service(cls):
'''
Marks the decorated class as a singleton ``service``.
Injects following classmethods:
.. py:method:: .get(context)
Returns a singleton instance of the class for given ``context``
:param context: context to look in
:type context: :class:`Context`
:returns: ``cls``
'''
if not cls:
return None
# Inject methods
def get(cls, context):
return context.get_service(cls)
cls.get = get.__get__(cls)
log.debug('Registering [%s] (service)', get_fqdn(cls))
return cls
[docs]class NoImplementationError(Exception):
def __init__(self, cls):
self.cls = cls
def __str__(self):
return u'No implementation for [%s] is available' % self.cls.__name__
def __repr__(self):
return unicode(self)
[docs]def interface(cls):
'''
Marks the decorated class as an abstract interface.
Injects following classmethods:
.. py:method:: .all(context)
Returns a list of instances of each component in the ``context`` implementing this ``@interface``
:param context: context to look in
:type context: :class:`Context`
:returns: list(``cls``)
.. py:method:: .any(context)
Returns the first suitable instance implementing this ``@interface`` or raises :exc:`NoImplementationError` if none is available.
:param context: context to look in
:type context: :class:`Context`
:returns: ``cls``
.. py:method:: .classes()
Returns a list of classes implementing this ``@interface``
:returns: list(class)
'''
if not cls:
return None
cls.implementations = []
# Inject methods
def _all(cls, context, ignore_exceptions=False):
return list(context.get_components(cls, ignore_exceptions=ignore_exceptions))
cls.all = _all.__get__(cls)
def _any(cls, context):
instances = cls.all(context)
if instances:
return instances[0]
raise NoImplementationError(cls)
cls.any = _any.__get__(cls)
def _classes(cls):
return list(cls.implementations)
cls.classes = _classes.__get__(cls)
log.debug('Registering [%s] (interface)', get_fqdn(cls))
return cls
[docs]def component(iface):
'''
Marks the decorated class as a component implementing the given ``iface``
:param iface: the interface to implement
:type iface: :func:`interface`
'''
def decorator(cls):
if not cls:
return None
# Run custom verificator if any
if hasattr(cls, '__verify__'):
if not cls.__verify__():
return None
if not hasattr(iface, 'implementations'):
log.error('%s is not an @interface', iface)
log.debug(
'Registering [%s] (implementation of [%s])' % (
get_fqdn(cls),
get_fqdn(iface)
)
)
iface.implementations.append(cls)
def get(cls, context):
return context.get_component(cls)
cls.get = get.__get__(cls)
return cls
return decorator