Skip to content

Commit

Permalink
ee.event_names (#99)
Browse files Browse the repository at this point in the history
* Class Decorator API (#84)

* Add class decorator API

* Add autofunctions for pyee.cls

* Remove travis file

* docs: Fix a few typos (#91)

* Type Annotations (#97)

* Set up virtualenv, pyright and isort

* Run isort

* Passing type annotations for base.py

* action to run type checks

* Alas!

* Happy type checker for trio

* MOST of the library is type-checking

* working, non-cranky type annotations for uplift laul

* Type check the tests, cause an explosion

* Clean up requirements.txt

* tests type-checking

* py.typed file

* tests and linting happy

* Update build

* obvious action bugfix

* trailing comma

* remove inconsequential and angry type annotation

* Ignore type issues w asyncio import

* messy typecast

* anyway thats when I started blasting

* carnage!

* uplift bugfixes

* update pytest

* bye 3.6

* type annotations for cls

Co-authored-by: Tim Gates <tim.gates@iress.com>

* added function that returns an array listing the events

* ee.event_names tested and passing

Now that there are some asserts for the value of event_names, we can see
what issue @leirons was running into with `new_listener`. It turns out
the issue ran pretty deep.

Internally, pyee used to use a defaultdict to store events. This was
mildly convenient for implementing on and emit, but it also meant that
event names were added after an emit, even if there were no handlers.

OK, so you patch it to use a regular dict and do the bookkeeping
manually. But there's another reason an event might show up even if it
has no handlers: pyee doesn't make an effort to clean up the
OrderedDicts which contain the actual handlers.

To solve this, I removed the defaultdict (so no event after an
emit) and added a step on listener removal to clean up the OrderedDict.

* Make event_names return a set instead of a list

Co-authored-by: Tim Gates <tim.gates@iress.com>
Co-authored-by: Ivan <grecigor11@gmail.com>
  • Loading branch information
3 people committed Jan 12, 2022
1 parent 349f91b commit 43b46b9
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 8 deletions.
18 changes: 13 additions & 5 deletions pyee/base.py
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-

from collections import defaultdict, OrderedDict
from collections import OrderedDict
from threading import Lock
from typing import Any, Callable, Dict, List, Optional, Tuple
from typing import Any, Callable, Dict, List, Optional, Set, Tuple


class PyeeException(Exception):
Expand Down Expand Up @@ -44,7 +44,7 @@ def __init__(self) -> None:
self._events: Dict[
str,
"OrderedDict[Callable, Callable]",
] = defaultdict(OrderedDict)
] = dict()
self._lock: Lock = Lock()

def on(self, event: str, f: Optional[Callable] = None) -> Callable:
Expand Down Expand Up @@ -110,6 +110,8 @@ def _add_event_handler(self, event: str, k: Callable, v: Callable):
# different for `once` handlers, where v is a wrapped version
# of k which removes itself before calling k
with self._lock:
if event not in self._events:
self._events[event] = OrderedDict()
self._events[event][k] = v

def _emit_run(
Expand All @@ -120,6 +122,10 @@ def _emit_run(
) -> None:
f(*args, **kwargs)

def event_names(self) -> Set[str]:
"""Get a list of events that this emitter is listening to."""
return set(self._events.keys())

def _emit_handle_potential_error(self, event: str, error: Any) -> None:
if event == "error":
if isinstance(error, Exception):
Expand All @@ -136,7 +142,7 @@ def _call_handlers(
handled = False

with self._lock:
funcs = list(self._events[event].values())
funcs = list(self._events.get(event, OrderedDict()).values())
for f in funcs:
self._emit_run(f, args, kwargs)
handled = True
Expand Down Expand Up @@ -203,6 +209,8 @@ def g(
def _remove_listener(self, event: str, f: Callable) -> None:
"""Naked unprotected removal."""
self._events[event].pop(f)
if not len(self._events[event]):
del self._events[event]

def remove_listener(self, event: str, f: Callable) -> None:
"""Removes the function ``f`` from ``event``."""
Expand All @@ -217,7 +225,7 @@ def remove_all_listeners(self, event: Optional[str] = None) -> None:
if event is not None:
self._events[event] = OrderedDict()
else:
self._events = defaultdict(OrderedDict)
self._events = dict()

def listeners(self, event: str) -> List[Callable]:
"""Returns a list of all listeners registered to the ``event``."""
Expand Down
26 changes: 23 additions & 3 deletions tests/test_sync.py
Expand Up @@ -22,6 +22,8 @@ def event_handler(data, **kwargs):
call_me()
assert data == "emitter is emitted!"

assert ee.event_names() == {"event"}

# Making sure data is passed propers
ee.emit("event", "emitter is emitted!", error=False)

Expand All @@ -43,6 +45,8 @@ def test_emit_error():
def on_error(exc):
call_me()

assert ee.event_names() == {"error"}

# No longer raises and error instead return True indicating handled
assert ee.emit("error", test_exception) is True
call_me.assert_called_once()
Expand All @@ -56,6 +60,8 @@ def test_emit_return():
call_me = Mock()
ee = EventEmitter()

assert ee.event_names() == set()

# make sure emitting without a callback returns False
assert not ee.emit("data")

Expand All @@ -79,6 +85,8 @@ def test_new_listener_event():
def event_handler(data):
pass

assert ee.event_names() == {"new_listener", "event"}

call_me.assert_called_once_with("event", event_handler)


Expand Down Expand Up @@ -106,6 +114,8 @@ def fourth():

ee.on("event", fourth)

assert ee.event_names() == {"event"}

assert ee._events["event"] == OrderedDict(
[(first, first), (second, second), (third, third), (fourth, fourth)]
)
Expand All @@ -120,7 +130,7 @@ def fourth():
assert ee._events["event"] == OrderedDict([(third, third), (fourth, fourth)])

ee.remove_all_listeners("event")
assert ee._events["event"] == OrderedDict()
assert "event" not in ee._events["event"]


def test_listener_removal_on_emit():
Expand All @@ -137,6 +147,8 @@ def should_remove():
ee.on("remove", should_remove)
ee.on("remove", call_me)

assert ee.event_names() == {"remove"}

ee.emit("remove")

call_me.assert_called_once()
Expand All @@ -148,6 +160,8 @@ def should_remove():
ee.on("remove", call_me)
ee.on("remove", should_remove)

assert ee.event_names() == {"remove"}

ee.emit("remove")

call_me.assert_called_once()
Expand All @@ -169,11 +183,15 @@ def once_handler(data):
# Tests to make sure that after event is emitted that it's gone.
ee.once("event", once_handler)

assert ee.event_names() == {"event"}

ee.emit("event", "emitter is emitted!")

call_me.assert_called_once()

assert ee._events["event"] == OrderedDict()
assert ee.event_names() == set()

assert "event" not in ee._events


def test_once_removal():
Expand All @@ -187,10 +205,12 @@ def once_handler(data):
handle = ee.once("event", once_handler)

assert handle == once_handler
assert ee.event_names() == {"event"}

ee.remove_listener("event", handle)

assert ee._events["event"] == OrderedDict()
assert "event" not in ee._events
assert ee.event_names() == set()


def test_listeners():
Expand Down

0 comments on commit 43b46b9

Please sign in to comment.