import asyncio import inspect import logging from cbpi.api import * class CBPiEventBus(object): class Node(object): __slots__ = '_children', '_content' def __init__(self): self._children = {} self._content = None class Content(object): def __init__(self, parent, topic, method, once, supports_future=False): self.parent = parent self.method = method self.name = method.__name__ self.once = once self.topic = topic self.supports_future = supports_future class Result: def __init__(self, result, timeout): self.result = result self.timeout = timeout class ResultContainer: def __init__(self, results, timeout=False): self.results = {} self.timeout = timeout self._jobs = set() for key, value in results.items(): if value.done() is True: self.results[key] = CBPiEventBus.Result(value.result(), True) else: self.results[key] = CBPiEventBus.Result(None, False) def get(self, key): r = self.results.get(key) if r is None: raise CBPiException("Event Key %s not found." % key) return (r.result, r.timeout) def register(self, topic, method, once=False): if method in self.registry: raise RuntimeError("Method %s already registerd. Please unregister first!" % method.__name__) self.logger.info("Topic %s", topic) node = self._root for sym in topic.split('/'): node = node._children.setdefault(sym, self.Node()) if not isinstance(node._content, list): node._content = [] sig = inspect.signature(method) if "future" in sig.parameters: supports_future = True else: supports_future = False c = self.Content(node, topic, method, once, supports_future) node._content.append(c) self.registry[method] = c def get_callbacks(self, key): try: node = self._root for sym in key.split('/'): node = node._children[sym] if node._content is None: raise KeyError(key) return node._content except KeyError: raise KeyError(key) def unregister(self, method): self.logger.info("Unregister %s", method.__name__) if method in self.registry: content = self.registry[method] clean_idx = None for idx, content_obj in enumerate(content.parent._content): if method == content_obj.method: clean_idx = idx break if clean_idx is not None: del content.parent._content[clean_idx] def __init__(self, loop, cbpi): self.logger = logging.getLogger(__name__) self.cbpi = cbpi self._root = self.Node() self.registry = {} self.docs = {} if loop is not None: self.loop = loop else: self.loop = asyncio.get_event_loop() def sync_fire(self,topic: str,timeout=1, **kwargs): self.loop.create_task(self.fire(topic=topic, timeout=timeout, **kwargs)) async def fire(self, topic: str, timeout=0.5, **kwargs): futures = {} self.logger.info("FIRE %s %s" % (topic, kwargs)) async def wait(futures): if(len(futures) > 0): await asyncio.wait(futures.values()) for e in self.iter_match(topic): content_array = e keep_idx = [] for idx, content_obj in enumerate(content_array): if inspect.iscoroutinefunction(content_obj.method): if content_obj.supports_future is True: fut = self.loop.create_future() futures["%s.%s" % (content_obj.method.__module__, content_obj.name)] = fut self.loop.create_task(content_obj.method(**kwargs, topic = topic, future=fut)) else: self.loop.create_task(content_obj.method(**kwargs, topic=topic)) else: # only asnyc pass if content_obj.once is False: keep_idx.append(idx) # FILTER only elements with are required if len(keep_idx) < len(e): e[0].parent._content = [e[0].parent._content[i] for i in keep_idx] if timeout is not None: try: await asyncio.wait_for(wait(futures), timeout=timeout) is_timedout = False except asyncio.TimeoutError: is_timedout = True return self.ResultContainer(futures, is_timedout) def dump(self): def rec(node, i=0): result = [] if node._content is not None: for c in node._content: result.append(dict(topic=c.topic, supports_future=c.supports_future, method=c.method.__name__, path=c.method.__module__, once=c.once)) if node._children is not None: for c in node._children: result = result + rec(node._children[c], i + 1) return result result = rec(self._root) return result def iter_match(self, topic): lst = topic.split('/') normal = not topic.startswith('$') def rec(node, i=0): if i == len(lst): if node._content is not None: yield node._content else: part = lst[i] if part in node._children: for content in rec(node._children[part], i + 1): yield content if '+' in node._children and (normal or i > 0): for content in rec(node._children['+'], i + 1): yield content if '#' in node._children and (normal or i > 0): content = node._children['#']._content if content is not None: yield content return rec(self._root) def register_object(self, obj): for method in [getattr(obj, f) for f in dir(obj) if callable(getattr(obj, f)) and hasattr(getattr(obj, f), "eventbus")]: doc = None if method.__doc__ is not None: try: doc = yaml.load(method.__doc__) doc["topic"] = method.__getattribute__("topic") except: pass self.register(method.__getattribute__("topic"), method)