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]