Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Default params, wrapper functions and better call function #13

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Editors
.vscode

# Python
*.py[co]

Expand Down
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ Patches and Suggestions
- Eric Smith
- Evan Klitzke
- Thomas Hanssen Nornes
- Michel Betancourt
69 changes: 69 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------------
Expand Down
2 changes: 1 addition & 1 deletion events/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from .events import Events, EventsException

__version__ = '0.3' # test
__version__ = '0.4.1' # test

__all__ = [
Events.__name__,
Expand Down
48 changes: 32 additions & 16 deletions events/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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):
Expand All @@ -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):
Expand Down
7 changes: 7 additions & 0 deletions events/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):

Expand Down