diff --git a/sample_lego.py b/sample_lego.py index 5cefdab..ed18ab5 100644 --- a/sample_lego.py +++ b/sample_lego.py @@ -1,6 +1,7 @@ import asyncio from thingsdb.client import Client from thingsdb.model import Collection, Thing, ThingStrict, Enum +from thingsdb.util import event class Color(Enum): @@ -21,6 +22,11 @@ def on_init(self, *args, **kwars): color value: {self.color.value} ''') + @event('new-color') + def on_new_color(self, color): + print(f'brick with id {self.id()} as a new color: {color}') + + class Lego(Collection): bricks = '[Brick]', Brick @@ -41,7 +47,15 @@ async def example(): await lego.load(client) # ... now the collection will be watched for 100 seconds - await asyncio.sleep(100) + while True: + await asyncio.sleep(3) + if lego and lego.bricks: + brick = lego.bricks[0] + await brick.emit('new-color', 'RED') + break + await lego.query('.bricks.push(Brick{});') + + await asyncio.sleep(300) finally: client.close() diff --git a/thingsdb/model/thing.py b/thingsdb/model/thing.py index bbd7473..5fbce30 100644 --- a/thingsdb/model/thing.py +++ b/thingsdb/model/thing.py @@ -43,6 +43,7 @@ class Thing(ThingHash): # will be created. A Collection instance will have `False` as default. __AS_TYPE__ = True + _ev_handlers = dict() _props = dict() _type_name = None # Only set when __AS_TYPE__ is True _visited = 0 # For build, 0=not visited, 1=new_type, 2=set_type, 3=build @@ -54,6 +55,7 @@ def __init__(self, collection, id: int): collection._register(self) def __init_subclass__(cls): + cls._ev_handlers = {} cls._props = {} items = { k: v for k, v in cls.__dict__.items() if not k.startswith('__')} @@ -63,6 +65,9 @@ def __init_subclass__(cls): if isinstance(val, tuple): prop = cls._props[key] = Prop(*val) delattr(cls, key) + elif callable(val) and hasattr(val, '_ev'): + cls._ev_handlers[val._ev] = val + if cls.__AS_TYPE__: cls._type_name = getattr(cls, '__TYPE_NAME__', cls.__name__) @@ -89,6 +94,16 @@ def unwatch(self): collection = self._collection return collection._client.unwatch(self._id, scope=collection._scope) + def emit(self, event, *args): + data = {f'd{i}': v for i, v in enumerate(args)} + dstr = "".join((f", {k}" for k in data.keys())) + + return self._collection.query( + f'thing(id).emit(event{dstr});', + id=self._id, + event=event, + **data) + @checkevent def on_init(self, event, data): self._job_set(data) @@ -105,6 +120,14 @@ def on_update(self, event, jobs): def on_delete(self): self._collection._things.pop(self.id()) + def on_event(self, ev, *args): + cls = self.__class__ + fun = cls._ev_handlers.get(ev) + if fun is None: + logging.debug(f'no event handler for {ev} on {cls.__name__}') + return + fun(self, *args) + def on_stop(self): logging.warning(f'stopped watching thing {self}') @@ -148,6 +171,9 @@ def _job_del(self, k): except AttributeError: pass + def _job_event(self, data): + self.on_event(*data) + def _job_remove(self, pair): cls = self.__class__ (k, v), = pair.items() @@ -270,6 +296,7 @@ def _job_set_type(self, data): # Thing jobs 'add': _job_add, 'del': _job_del, + 'event': _job_event, 'remove': _job_remove, 'set': _job_set, 'splice': _job_splice, diff --git a/thingsdb/util/__init__.py b/thingsdb/util/__init__.py index 242bd77..bd83d21 100644 --- a/thingsdb/util/__init__.py +++ b/thingsdb/util/__init__.py @@ -1,2 +1,3 @@ from .convert import convert from .fmt import fmt +from .event import event \ No newline at end of file diff --git a/thingsdb/util/event.py b/thingsdb/util/event.py new file mode 100644 index 0000000..b870027 --- /dev/null +++ b/thingsdb/util/event.py @@ -0,0 +1,12 @@ +"""Decorator for handleing events.""" + +def event(ev): + + def _event(fun): + def wrapper(self, *args): + fun(self, *args) + + wrapper._ev = ev + return wrapper + + return _event diff --git a/thingsdb/version.py b/thingsdb/version.py index a68d2bd..02f8497 100644 --- a/thingsdb/version.py +++ b/thingsdb/version.py @@ -1 +1 @@ -__version__ = '0.6.3' +__version__ = '0.6.4'