1#!/usr/bin/env python
2#coding:utf-8
3# Purpose: observer pattern
4# Created: 22.01.2011
5# Copyright (C) 2011, Manfred Moitzi
6# License: MIT license
7from __future__ import unicode_literals, print_function, division
8__author__ = "mozman <mozman@gmx.at>"
9
10try:
11    from weakref import WeakSet
12except ImportError:
13    from weakrefset import WeakSet
14
15class Observer(object):
16    """ Simple implementation of the observer pattern for broadcasting messages
17    to objects.
18
19    For every event the subscriber object need an event handler called 'on_event_handler'
20    accepting the parameter 'msg'.
21
22    Because of the simple implementation of the algorithm it is necessary to
23    register the objects an not only the listener methods, because the methods of
24    different objects of the same calss have the same 'id' and managing the
25    listeners in a WeakSet is not possible for different objects (you could
26    only manage one table in one document instance).
27
28    Example for event: 'save'
29        # module 'one'
30        import observer
31
32        class Listener:
33            def on_save_handler(self, msg):
34                pass
35            def get_root(self):
36                return None
37
38        listener = Listener()
39        # subscribe to the 'global observer'
40        observer.subscribe('save', listener)
41
42        # module 'two'
43        import observer
44        # calls listener.on_save_handler(msg=None)
45        observer.broadcast('save', msg=None)
46    """
47
48    def __init__(self):
49        self._listeners = dict()
50
51    def subscribe(self, event, listener_object):
52        event_handler_name = "on_%s_handler" % event
53        if not hasattr(listener_object, event_handler_name):
54            raise AttributeError("Listener object has no '%s' event handler." % event)
55        try:
56            event_listeners = self._listeners[event]
57        except KeyError:
58            event_listeners = WeakSet()
59            self._listeners[event] = event_listeners
60        event_listeners.add(listener_object)
61
62    def unsubscribe(self, event, listener_object):
63        # Unsubscribing for objects which will be destroyed is not neccessary,
64        # just unsubscribe objects that should not receive further messages.
65        event_listeners = self._listeners[event]
66        event_listeners.remove(listener_object)
67
68    def broadcast(self, event, msg=None, root=None):
69        """ Broadcast an 'event' and submit 'msg' to the listeners event handler.
70
71        If the 'event' should only reach objects of one document, use the 'root'
72        parameter (get 'root' with the get_xmlroot() method) and only objects with
73        the same 'root' element receives the event.
74        """
75
76        event_handler_name = "on_%s_handler" % event
77        def get_event_handler(listener):
78            return getattr(listener, event_handler_name)
79
80        def send_to_all(listener):
81            handler = get_event_handler(listener)
82            handler(msg=msg)
83
84        def send_to_root(listener):
85            try:
86                listener_root = listener.get_xmlroot()
87            except AttributeError:
88                return
89
90            if listener_root is root:
91                handler = get_event_handler(listener)
92                handler(msg=msg)
93
94        try:
95            event_listeners = self._listeners[event]
96        except KeyError:
97            # ok, because there is just no listener for this event
98            # but mispelling of 'event' is an error trap
99            return
100
101        send = send_to_root if root else send_to_all
102        for listener in event_listeners:
103            send(listener)
104
105    def _has_listener(self, event):
106        # just for testing
107        return self._count_listeners(event) > 0
108
109    def _count_listeners(self, event):
110        # just for testing
111        try:
112            return len(self._listeners[event])
113        except KeyError:
114            return 0
115
116_global_observer = Observer()
117
118subscribe = _global_observer.subscribe
119unsubscripe = _global_observer.unsubscribe
120broadcast = _global_observer.broadcast
121