Skip to content

Commit

Permalink
Merge pull request #21 from nocarryr/exists-errors
Browse files Browse the repository at this point in the history
Add `DoesNotExistError` and `ExistsError`
  • Loading branch information
nocarryr authored May 30, 2023
2 parents 222591f + c463814 commit 33aafaa
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 8 deletions.
16 changes: 16 additions & 0 deletions doc/source/reference/dispatch.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,19 @@ Event class

.. autoclass:: pydispatch.dispatch.Event
:members:


Exceptions
----------

.. autoclass:: pydispatch.dispatch.DoesNotExistError
:members:

.. autoclass:: pydispatch.dispatch.ExistsError
:members:

.. autoclass:: pydispatch.dispatch.EventExistsError
:members:

.. autoclass:: pydispatch.dispatch.PropertyExistsError
:members:
2 changes: 1 addition & 1 deletion pydispatch/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
'After version 0.1.x, `python-dispatch` will only support Python 3.6 or greater.',
UserWarning)

from pydispatch.dispatch import Dispatcher, Event
from pydispatch.dispatch import *
from pydispatch.properties import *
106 changes: 99 additions & 7 deletions pydispatch/dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,51 @@
import asyncio
from pydispatch.aioutils import AioWeakMethodContainer, AioEventWaiters

__all__ = (
'DoesNotExistError', 'ExistsError', 'EventExistsError',
'PropertyExistsError', 'Event', 'Dispatcher',
)


class DoesNotExistError(KeyError):
"""Raised when binding to an :class:`Event` or :class:`~.properties.Property`
that does not exist
.. versionadded:: 0.2.2
"""
def __init__(self, name):
self.name = name

def __str__(self):
return f'Event "{self.name}" not registered'


class ExistsError(RuntimeError):
"""Raised when registering an event name that already exists
as either a normal :class:`Event` or :class:`~.properies.Property`
.. versionadded:: 0.2.2
"""
def __init__(self, name):
self.name = name

def __str__(self):
return f'"{self.name}" already exists'

class EventExistsError(ExistsError):
"""Raised when registering an event name that already exists
as an :class:`Event`
.. versionadded:: 0.2.2
"""


class PropertyExistsError(ExistsError):
"""Raised when registering an event name that already exists
as a :class:`~.properties.Property`
.. versionadded:: 0.2.2
"""


class Event(object):
Expand Down Expand Up @@ -108,10 +153,20 @@ def register_event(self, *names):
Args:
*names (str): Name or names of the events to register
Raises:
EventExistsError: If an event with the given name already exists
PropertyExistsError: If a property with the given name already exists
.. versionchanged:: 0.2.2
:class:`ExistsError` exceptions are raised when attempting to
register an event or property that already exists
"""
for name in names:
if name in self.__events:
continue
raise EventExistsError(name)
elif name in self.__property_events:
raise PropertyExistsError(name)
self.__events[name] = Event(name)
def bind(self, **kwargs):
"""Subscribes to events or to :class:`~pydispatch.properties.Property` updates
Expand Down Expand Up @@ -164,6 +219,14 @@ class Foo(Dispatcher):
This can also be done using :meth:`bind_async`.
Raises:
DoesNotExistError: If attempting to bind to an event or
property that has not been registered
.. versionchanged:: 0.2.2
:class:`DoesNotExistError` is now raised when binding to
non-existent events or properties
.. versionadded:: 0.1.0
"""
Expand All @@ -174,7 +237,10 @@ class Foo(Dispatcher):
if name in props:
e = props[name]
else:
e = events[name]
try:
e = events[name]
except KeyError:
raise DoesNotExistError(name)
e.add_listener(cb, __aio_loop__=aio_loop)
def unbind(self, *args):
"""Unsubscribes from events or :class:`~pydispatch.properties.Property` updates
Expand Down Expand Up @@ -224,10 +290,21 @@ def emit(self, name, *args, **kwargs):
name (str): The name of the :class:`Event` to dispatch
*args (Optional): Positional arguments to be sent to listeners
**kwargs (Optional): Keyword arguments to be sent to listeners
Raises:
DoesNotExistError: If attempting to emit an event or
property that has not been registered
.. versionchanged:: 0.2.2
:class:`DoesNotExistError` is now raised if the event or property
does not exist
"""
e = self.__property_events.get(name)
if e is None:
e = self.__events[name]
try:
e = self.__events[name]
except KeyError:
raise DoesNotExistError(name)
return e(*args, **kwargs)
def get_dispatcher_event(self, name):
"""Retrieves an Event object by name
Expand All @@ -239,11 +316,21 @@ def get_dispatcher_event(self, name):
Returns:
The :class:`Event` instance for the event or property definition
Raises:
DoesNotExistError: If no event or property with the given name exists
.. versionchanged:: 0.2.2
:class:`DoesNotExistError` is now raised if the event or property
does not exist
.. versionadded:: 0.1.0
"""
e = self.__property_events.get(name)
if e is None:
e = self.__events[name]
try:
e = self.__events[name]
except KeyError:
raise DoesNotExistError(name)
return e
def emission_lock(self, name):
"""Holds emission of events and dispatches the last event on release
Expand Down Expand Up @@ -279,9 +366,14 @@ def emission_lock(self, name):
The context manager is re-entrant, meaning that multiple calls to
this method within nested context scopes are possible.
Raises:
DoesNotExistError: If no event or property with the given name exists
.. versionchanged:: 0.2.2
:class:`DoesNotExistError` is now raised if the event or property
does not exist
.. _PEP 492: https://www.python.org/dev/peps/pep-0492/#asynchronous-context-managers-and-async-with
"""
e = self.__property_events.get(name)
if e is None:
e = self.__events[name]
e = self.get_dispatcher_event(name)
return e.emission_lock
50 changes: 50 additions & 0 deletions tests/test_events.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import pytest

def test_basic(listener, sender):
sender.register_event('on_test_a')
Expand Down Expand Up @@ -179,3 +180,52 @@ def test_emission_lock(listener, sender):
sender.emit('on_test', 'inner')
assert len(listener.received_event_data) == 1
assert listener.received_event_data[0]['args'] == ('inner', )


def test_bind_and_emit_unregistered():
from pydispatch import Dispatcher, DoesNotExistError

class Sender(Dispatcher):
pass

def callback(*args, **kwargs):
pass

sender = Sender()
with pytest.raises(DoesNotExistError) as excinfo:
sender.bind(foo=callback)
assert '"foo"' in str(excinfo.value)

with pytest.raises(DoesNotExistError) as excinfo:
sender.emit('foo')
assert '"foo"' in str(excinfo.value)

with pytest.raises(DoesNotExistError) as excinfo:
e = sender.get_dispatcher_event('foo')
assert '"foo"' in str(excinfo.value)

with pytest.raises(DoesNotExistError) as excinfo:
lock = sender.emission_lock('foo')
assert '"foo"' in str(excinfo.value)

def test_register_existing_event():
from pydispatch import Dispatcher, EventExistsError

class Sender(Dispatcher):
_events_ = ['on_foo']

sender = Sender()
with pytest.raises(EventExistsError) as excinfo:
sender.register_event('on_foo')
assert '"on_foo"' in str(excinfo.value)

def test_register_existing_property():
from pydispatch import Dispatcher, Property, PropertyExistsError

class Sender(Dispatcher):
foo = Property()

sender = Sender()
with pytest.raises(PropertyExistsError) as excinfo:
sender.register_event('foo')
assert '"foo"' in str(excinfo.value)

0 comments on commit 33aafaa

Please sign in to comment.