# pylint: disable=redefined-builtin
import functools
import caspia.meadow
[docs]class ActiveNamespace:
def __init__(self, lookup, path):
self.lookup = lookup
self.path = path
@property
def full_path(self):
if self.parent is None:
return self.path
else:
return self.parent.full_path + '.' + self.path
def __enter__(self):
self.parent = self.lookup.active_namespace
self.lookup.active_namespace = self
return self
def __exit__(self, *args):
self.lookup.active_namespace = self.parent
self.parent = None
[docs]class Lookup:
""" This class works as a collection of known services. """
def __init__(self, create=None, prepare=None):
self.active_namespace = None
self.services = dict()
self.create_hooks = [create] if create else []
self.prepare_hooks = [prepare] if prepare else []
def _create_service(self, name):
for hook in self.create_hooks:
instance = hook(name)
if instance:
return instance
from caspia.meadow.client import get_consumer_class
stype, _ = caspia.meadow.name.split(name)
return get_consumer_class(stype)(name)
def _prepare_service(self, service):
for hook in self.prepare_hooks:
hook(service)
[docs] def add(self, service):
""" Register given service within this lookup instance. """
self.services[service.name] = service
self._prepare_service(service)
[docs] def add_prepare_hook(self, func):
""" Register a hook (function) to be called with every new service instance
added to this lookup.
:param func: The function hook with signature func(new_service).
"""
self.prepare_hooks.append(func)
[docs] def add_create_hook(self, func):
""" Register a new service creator.
When user requests a service from this lookup and the service is not known,
the lookup tries to create it registered creators. Creator is a callable
creator(service_type, service_name) -> None|Service.
"""
self.create_hooks.append(func)
[docs] def find(self, name, type=None): # pylint: disable=redefined-builtin
""" Find service with given name (and optionally type). """
if type:
name = caspia.meadow.name.update(name, type=type)
full_name = self.resolve_name(name)
if full_name in self.services:
return self.services[full_name]
return None
[docs] def get(self, name, type=None, create=True):
""" Get service of a given type and name.
Try to create it, if ``create`` is True
"""
if type:
name = caspia.meadow.name.update(name, type=type)
full_name = self.resolve_name(name)
instance = self.find(name)
if instance is not None:
return instance
elif create:
instance = self._create_service(full_name)
self.services[full_name] = instance
self._prepare_service(instance)
return instance
else:
return None
[docs] def namespace(self, path):
""" Return object, which if used as a context manager enables
use of relative names.
:Example:
with lookup.namespace('basement'):
lookup.get('light', '.light') # gets service named basement.light
"""
return ActiveNamespace(self, path)
[docs] def resolve_name(self, name):
""" Resolve name with respect to active namespaces. """
if self.active_namespace:
type, path = caspia.meadow.name.split(name, strict=False)
path = caspia.meadow.namespace.resolve_relative(path, self.active_namespace.full_path)
return caspia.meadow.name.update(path, type=type)
else:
return name
def __getitem__(self, key):
return functools.partial(self.get, type=key)
def __getattr__(self, attr):
return functools.partial(self.get, type=attr)