diff --git a/.gitignore b/.gitignore index 4f723d5..8546a71 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Editors +.vscode + # Python *.py[co] diff --git a/AUTHORS b/AUTHORS index 3089202..35e7c5f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -12,3 +12,4 @@ Patches and Suggestions - Eric Smith - Evan Klitzke - Thomas Hanssen Nornes +- Michel Betancourt diff --git a/README.rst b/README.rst index 7af1549..9aff85b 100644 --- a/README.rst +++ b/README.rst @@ -51,6 +51,75 @@ You can also predefine events for a single Events instance by passing an iterato # this will raise an EventsException as `on_change` is unknown to events: >>> events.on_change += something_changed +You can define default arguments for events + +.. code-block:: pycon + + >>> from events import Events + >>> class MyClass(object): + ... def __init__(self): + ... self.events = Events(default=[str(self) + ": "]) + ... self.events.on_change += print + ... def __str__(self): + ... return self.__class__.__name__ + >>> inst = MyClass() + >>> inst.events.on_change("Hello world!") + >>> inst.events.on_change("Bye world!") + +You can also declare a global wrap function that allows to insert all the events a +use is at the time of debugging or avoiding the execution of an event under certain +circumstances + +.. code-block:: pycon + + >>> from events import Events + >>> def debug(func, *args, **kwargs): + ... logging.debug("Trigger event: " + func.__name__) + ... func(*args, **kwargs) + >>> class MyClass(object): + ... def __init__(self): + ... self.events = Events(wrapper=debug) + ... self.events.on_change += print + ... def __str__(self): + ... return self.__class__.__name__ + >>> inst = MyClass() + >>> inst.events.on_change("Hello world!") + >>> inst.events.on_change("Bye world!") + +Do not worry about sending exact parameters or fill your functions with * args, * +kwargs the functions are only calls with the parameters they need + +.. code-block:: pycon + + >>> from events import Events + >>> class MyClass(object): + ... def __init__(self): + ... self.events = Events(default=[self]) + ... self.events.on_change += self.destroy + ... self.events.on_change += self.paint + ... def destroy(self): + ... pass + ... def paint(self): + ... pass + ... def __str__(self): + ... return self.__class__.__name__ + >>> def need1(arg): + ... print(f"need1 {arg}") + >>> def need2(arg, arg2): + ... print(f"need2 {arg} {arg2}") + >>> def need3(arg, arg3, named): + ... print(f"need3 {arg} {arg3} {named}") + >>> def function(sender): + ... print(sender) + ... sender.paint() + >>> my_class = MyClass() + >>> my_class.events.on_change() + >>> my_class.events.on_change += function + >>> my_class.events.on_change() + >>> my_class.events.on_key += need1 + >>> my_class.events.on_key += need2 + >>> my_class.events.on_key += need3 + >>> my_class.events.on_key('arg', 'arg2', arg3='arg3', named='named') Documentation ------------- diff --git a/events/__init__.py b/events/__init__.py index b2cd88f..a38beda 100644 --- a/events/__init__.py +++ b/events/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from .events import Events, EventsException -__version__ = '0.3' # test +__version__ = '0.4.1' # test __all__ = [ Events.__name__, diff --git a/events/events.py b/events/events.py index 29eff02..022e1ee 100644 --- a/events/events.py +++ b/events/events.py @@ -12,7 +12,7 @@ :copyright: (c) 2014-2017 by Nicola Iarocci. :license: BSD, see LICENSE for more details. """ - +from inspect import signature class EventsException(Exception): pass @@ -35,8 +35,13 @@ class Events: xxx.OnChange = event('OnChange') """ - def __init__(self, events=None): - + def __init__(self, events=None, default=None, wrapper=None): + if not isinstance(default, (list, tuple)) and default is not None: + raise AttributeError("type object %s is not list" % (type(default))) + elif not callable(wrapper) and wrapper is not None: + raise AttributeError("type object %s is not function" % (type(wrapper))) + self._wrapper = wrapper + self._default = default if events is not None: try: @@ -61,7 +66,7 @@ def __getattr__(self, name): if name not in self.__class__.__events__: raise EventsException("Event '%s' is not declared" % name) - self.__dict__[name] = ev = _EventSlot(name) + self.__dict__[name] = ev = _EventSlot(name, self._default, self._wrapper) return ev def __repr__(self): @@ -72,35 +77,46 @@ def __repr__(self): __str__ = __repr__ def __len__(self): - return len(self.__dict__.items()) + return len([x for x in self.__dict__ if not x.startswith('_')]) def __iter__(self): def gen(dictitems=self.__dict__.items()): - for attr, val in dictitems: + for _, val in dictitems: if isinstance(val, _EventSlot): yield val return gen() class _EventSlot: - def __init__(self, name): + def __init__(self, name, default=None, wrapper=None): + self._default = default or [] + self._wrapper = wrapper or (lambda func, *args, **kwargs: func(*args, **kwargs)) self.targets = [] self.__name__ = name def __repr__(self): return "event '%s'" % self.__name__ - def __call__(self, *a, **kw): - for f in tuple(self.targets): - f(*a, **kw) - - def __iadd__(self, f): - self.targets.append(f) + def __call__(self, *args, **kwargs): + for function in tuple(self.targets): + ckwargs = kwargs.copy() + cargs = self._default + list(args) + params = signature(function).parameters + if not any(map(lambda x: x.kind == x.VAR_KEYWORD, params.values())): + for key in kwargs: + if not key in params: + del ckwargs[key] + if not any(map(lambda x: x.kind == x.VAR_POSITIONAL, params.values())): + cargs = cargs[:len(params)-len(ckwargs)] + self._wrapper(function, *cargs, **ckwargs) + + def __iadd__(self, function): + self.targets.append(function) return self - def __isub__(self, f): - while f in self.targets: - self.targets.remove(f) + def __isub__(self, function): + while function in self.targets: + self.targets.remove(function) return self def __len__(self): diff --git a/events/tests/tests.py b/events/tests/tests.py index b68486f..a40241f 100644 --- a/events/tests/tests.py +++ b/events/tests/tests.py @@ -18,6 +18,9 @@ def callback2(self): def callback3(self): pass + def callback4(self, arg1, arg2): + self.assertEqual(arg1, "Default_1") + self.assertEqual(arg2, "Default_2") class TestEvents(TestBase): def test_getattr(self): @@ -101,6 +104,10 @@ def test_isub(self): class TestInstanceEvents(TestBase): + def test_instance_default(self): + events = Events(default=["Default_1", "Default_2"]) + events.on_event += self.callback4 + events.on_event() def test_getattr(self):