Source code for orrery.observable
"""Base class for objects implementing the observer pattern"""
import weakref
from collections import namedtuple
[docs]
class Event(str):
"""Identifies an event that can be raised by an Observable object"""
[docs]
class Observable:
"""Base class for objects which can send notifications to observers.
Maintains a dictionary of event types to ObserverList objects
"""
[docs]
class ObserverList:
"""Class for managing observers and sending notifications for events
Makes special use of WeakKeyDictionary and WeakMethod so that the
Observable won't prevent observers from being destroyed, and if an
observer is destroyed it will automatically be removed from the
observers
"""
CallbackArgs = namedtuple('CallbackArgs', ['callback', 'kwargs'])
def __init__(self):
# Create dictionary of observing objects to their callback.
# The WeakKeyDictionary ensures that if the observer object is
# destroyed, it is automatically removed from the observer
# dictionary
self._observers = weakref.WeakKeyDictionary()
[docs]
def add_observer(self, callback, **kwargs):
"""Add an observer to receive notification events
Args:
callback: bound method which will receive the callbacks
kwargs: additional arguments which will be passed to the
callback
"""
# Wrap the callback (which is a bound method) as a WeakMethod,
# otherwise the bound method would prevent the observer object from
# being destroyed
callback_id = callback.__self__
weak_callback = weakref.WeakMethod(callback)
self._observers[callback_id] = self.CallbackArgs(
callback=weak_callback, kwargs=kwargs)
return callback_id
[docs]
def remove_observer(self, callback_id):
"""Remove an observer previously added with add_observer()
Args:
callback_id: the callback id returned by the add_observer() call
"""
if callback_id not in self._observers:
raise RuntimeError('Unknown callback ID')
del self._observers[callback_id]
[docs]
def notify(self, **event_args):
"""Trigger a callback on all observers which are still in
existence"""
for callback_args in self._observers.values():
callback_args.callback()(**event_args, **callback_args.kwargs)
def __init__(self):
# Each event has its own unique ObserverList.
self._observer_lists: dict[Event, Observable.ObserverList] = {}
[docs]
def add_observer(self, event: Event, callback, **kwargs):
"""Add an observer to receive notifications for the specified event
Args:
event: describes a unique event generated by this object
callback: bound method which will receive the callbacks
kwargs: additional arguments to be passed to the callbacks - these
are added to the arguments passed in through the call to
notify()
Returns:
An id which can be used to remove the callback if necessary
"""
return self._get_observer_list(event).add_observer(callback, **kwargs)
[docs]
def remove_observer(self, event: Event, callback_id):
"""Remove existing observer for the specified event
Args:
event: describes a unique event generated by this object
callback_id: the identifier passed when the observer was added
"""
self._get_observer_list(event).remove_observer(callback_id)
[docs]
def notify(self, event: Event, **event_args):
"""Trigger a callback on all observers for this event
Args:
event: describes a unique event generated by this object
event_args: named arguments to be passed to pass to callbacks
"""
self._get_observer_list(event).notify(**event_args)
def _get_observer_list(self, event: Event) -> ObserverList:
"""Return ObserverList for the specified event, creating if
necessary"""
if event not in self._observer_lists:
self._observer_lists[event] = Observable.ObserverList()
return self._observer_lists[event]