pytilities.event.dispatcher_

Package pytilities :: Package event :: Module dispatcher_ |
[hide private]
[frames] | no frames]

# Source Code for Module pytilities.event.dispatcher_

      1  # Copyright (C) 2010 Tim Diels <limyreth@users.sourceforge.net>
      2  #
      3  # This file is part of pytilities.
      4  #
      5  # pytilities is free software: you can redistribute it and/or modify
      6  # it under the terms of the GNU General Public License as published by
      7  # the Free Software Foundation, either version 3 of the License, or
      8  # (at your option) any later version.
      9  #
     10  # pytilities is distributed in the hope that it will be useful,
     11  # but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  # GNU General Public License for more details.
     14  #
     15  # You should have received a copy of the GNU General Public License
     16  # along with pytilities.  If not, see <http://www.gnu.org/licenses/>.
     17  #
     18
     19  # Note: this file name isn't standard as epydoc and our testing couldn't
     20  # understand it if we named it dispatcher.py; it would clash in __init__.py
     21  # with the dispatcher decorator
     22
     23  __docformat__ = 'reStructuredText'
     24
     25  from pytilities.delegation import (
     26      delegator_factory, delegated)
     27
     28 -class UnsupportedEventError(Exception):
     29      """Tried to access an unregistered event"""
     30
     31 -    def __init__(self, event_name):
     32          Exception.__init__(self, "Unsupported event: %s" % event_name)
     33
     34  @delegator_factory()
     35 -class Dispatcher(object):
     36
     37      """
     38      Utility class for dispatching events to handlers.
     39
     40      Events have to be registered before they can be dispatched or have handlers
     41      added to them.
     42
     43      Handlers can have an owner associated with them, usually you'll use the
     44      reference of the listener. This allows you to remove all the handlers of a
     45      specific owner, which should save you some work.
     46
     47      Instance methods:
     48
     49          - `add_handler`: Add handler for an event
     50          - `remove_handlers`: Remove all or some handlers
     51          - `remove_handler`: Remove a handler
     52          - `dispatch`: Dispatch an event
     53          - `register_events`: Register events
     54          - `has_event`: Check whether event is supported
     55
     56      Instance properties:
     57
     58          - `events`: Read-only, set of all supported events
     59
     60      Instance decorators:
     61
     62          - `event`: Register decorated as a handler
     63
     64      Class invariants:
     65
     66          - For every (owner, event), there can be only 0 or 1 handlers
     67      """
     68
     69      # inspired by pyglet.event.EventDispatcher, yay pyglet
     70
     71      @staticmethod
     72 -    def __init_delegation_profiles(profiles):
     73          profiles['default'] |= profiles['public']
     74
     75 -    def __init__(self):
     76          # dict of handlers: event_name -> set((handler, owner))
     77          self.__handlers = {}
     78          self.__registered_events = set()
     79
     80      @delegated("public")
     81 -    def add_handler(self, event_name, handler, owner = None):
     82          """
     83          Add handler for an event, optionally with an owner.
     84
     85          Parameters:
     86
     87              event_name :: string
     88                  name of the event to add the handler to
     89
     90              handler :: callable
     91                  the handler to call when the event is dispatched
     92
     93              owner = None
     94                  owner of the handler. Use this reference to easily remove all
     95                  your handlers from the dispatcher (e.g. remove all handlers
     96                  with the same owner)
     97
     98          Raises:
     99
    100              - `UnsupportedEventError` when `event_name` doesn't exist
    101          """
    102
    103          if not self.has_event(event_name):
    104              raise UnsupportedEventError(event_name)
    105
    106          assert handler, "handler argument musn't be None"
    107
    108          assert (handler, owner) not in self.__handlers, (
    109              "You cannot add the same handler for the same owner and event " +
    110              "more than once.")
    111
    112          self.__handlers.setdefault(event_name, set())
    113          self.__handlers[event_name].add((handler, owner))
    114
    115      @delegated("public")
    116 -    def remove_handlers(self, event_name=None, owner=None):
    117          """
    118          Remove all or some handlers of the dispatcher.
    119
    120          `event_name` and `owner` act as filters of what to remove.
    121
    122          If no handler matched the criterea, the method will return silently.
    123
    124          Parameters:
    125
    126              event_name :: string = None
    127                  the event of which to remove the handlers. `None` means any
    128
    129              owner = None
    130                  the owner of which to remove the handlers. `None` means any
    131          """
    132
    133          if event_name:
    134              self.__remove_handlers(event_name, owner)
    135          else:
    136              for event in self.__registered_events:
    137                  self.__remove_handlers(event, owner)
    138
    139 -    def __remove_handlers(self, event, owner):
    140          """Remove handlers of single event, optionally with particular owner """
    141          handlers = self.__handlers[event]
    142          handlers -= set((handler, o) for (handler, o) in handlers
    143                          if o is owner)
    144
    145      @delegated("public")
    146 -    def remove_handler(self, event_name, handler, owner = None):
    147          """
    148          Remove a handler from an event. It is an error to try to remove a
    149          handler from an event that doesn't have this handler attached to it.
    150
    151          Parameters:
    152
    153              event_name :: string
    154                  name of the event to which the handler belongs
    155
    156              handler :: callable
    157                  the handler that is attached to the event
    158
    159              owner = None
    160                  owner of the handler
    161
    162          Preconditions:
    163              1. `handler` is attached to `event_name`
    164
    165          Raises:
    166              - `UnsupportedEventError` when `event_name` doesn't exist
    167          """
    168
    169          if not self.has_event(event_name):
    170              raise UnsupportedEventError(event_name)
    171
    172          assert handler, "handler argument musn't be None"
    173
    174          self.__handlers[event_name].remove((handler, owner))
    175
    176      @delegated()
    177 -    def dispatch(self, event_name, *args, **keyword_args):
    178          """
    179          Dispatch an event to its handlers.
    180
    181          The handlers are executed in a random order.
    182
    183          Parameters:
    184
    185              event_name :: string
    186                  name of the event to dispatch
    187
    188              args
    189                  arguments to pass to the handlers
    190
    191              keyword_args
    192                  keyword arguments to pass to the handlers
    193
    194          Raises:
    195              - `UnsupportedEventError` when `event_name` doesn't exist
    196          """
    197
    198          if not self.has_event(event_name):
    199              raise UnsupportedEventError(event_name)
    200
    201          for handler in self.__handlers.get(event_name, ()):
    202              handler[0](*args, **keyword_args)
    203
    204      @delegated("public")
    205 -    def event(self, event_name, owner = None):
    206          """
    207          Register the decorated as a handler of `event_name`
    208
    209          Parameters:
    210
    211              event_name :: string
    212                  name of the event
    213
    214              owner = None
    215                  owner of the handler. Use this reference to easily remove all
    216                  your handlers from the dispatcher (e.g. remove all handlers
    217                  with the same owner)
    218          """
    219
    220          def decorator(handler):
    221              self.add_handler(event_name, handler, owner)
    222              return handler
    223
    224          return decorator
    225
    226      @delegated()
    227 -    def register_events(self, *event_names):
    228          """
    229          Register events.
    230
    231          Parameters:
    232
    233              event_names :: (string...)
    234                  names of events to support
    235          """
    236          # this allows us to have methods like has event, which allow for better
    237          # duck typing (if I see something that sends out quack events, then I
    238          # call that a duck)
    239          self.__registered_events.update(event_names)
    240
    241      @delegated("public")
    242 -    def has_event(self, event_name):
    243          """
    244          Checks if `event_name` is supported
    245
    246          Parameters:
    247
    248              event_name :: string
    249                  name of the event
    250
    251          Returns True if the dispatcher has the event
    252          """
    253          return event_name in self.__registered_events
    254
    255      @delegated("public")
    256      @property
    257 -    def events(self):
    258          """
    259          Read-only, set of all supported events
    260
    261          Returns ::frozenset(string...)
    262          """
    263          return frozenset(self.__registered_events)
    264