Package elisa :: Package core :: Package utils :: Module signal
[hide private]
[frames] | no frames]

Source Code for Module elisa.core.utils.signal

  1  # -*- coding: utf-8 -*- 
  2  # Elisa - Home multimedia server 
  3  # Copyright (C) 2006-2008 Fluendo Embedded S.L. (www.fluendo.com). 
  4  # All rights reserved. 
  5  # 
  6  # This file is available under one of two license agreements. 
  7  # 
  8  # This file is licensed under the GPL version 3. 
  9  # See "LICENSE.GPL" in the root of this distribution including a special 
 10  # exception to use Elisa with Fluendo's plugins. 
 11  # 
 12  # The GPL part of Elisa is also available under a commercial licensing 
 13  # agreement from Fluendo. 
 14  # See "LICENSE.Elisa" in the root directory of this distribution package 
 15  # for details on that license. 
 16   
 17   
 18  __maintainer__ = 'Florian Boucault <florian@fluendo.com>' 
 19  __maintainer2__ = 'Philippe Normand <philippe@fluendo.com>' 
 20   
 21   
22 -class WrongArgument(Exception):
23 """ Exception raised by Signal when the developer tries to emit 24 wrong types values with the Signal. 25 26 @ivar arg: the value of the wrongly typed object 27 @type arg: object 28 @ivar expected_type: the expcected type for the argument, given at 29 Signal creation 30 @type expected_args: type 31 @ivar position: position of the argument in the arguments list, if it's 32 not a keyword argument 33 @type position: int or None 34 """ 35
36 - def __init__(self, arg, expected_type, position):
37 Exception.__init__(self) 38 self.arg = arg 39 self.expected_type = expected_type 40 self.position = position
41
42 - def __str__(self):
43 """ Textual representation of the Exception 44 45 @rtype: string 46 """ 47 if self.position is not None: 48 position = "at position %s" % self.position 49 else: 50 position = "as keyword argument" 51 msg = "Expected an %r %s, got %r (%r) instead." 52 msg = msg % (self.expected_type.__name__, position, 53 self.arg, type(self.arg)) 54 return msg
55
56 -class Signal(object):
57 """ 58 A Signal is a 1-to-many communication system. 59 It provides a way for two objects to communicate without knowing 60 about each other. They only have to know about the signal object. Receivers 61 objects connect to the signal and emitters can trigger the emission of the 62 signal so that it is sent to all the receivers. Signal emission is 63 synchronous, that is, the receivers are immediatly called upon emission, 64 one after the other. 65 66 WARNING: 67 - a Signal is *not* thread-safe 68 - the receivers are called in the emitting thread 69 - receivers calling is not ordered 70 71 TODO: 72 - might be useful to send a reference of the sender 73 - maybe add static extra data at connect time 74 - think about implementing return values support 75 - think about weak references for receivers 76 http://docs.python.org/lib/module-weakref.html 77 - think about integrating it with Twisted and making signals 78 deferred in threads 79 80 """ 81 82
83 - def __init__(self, name, *args_types, **kw_types):
84 """ 85 Initialize a signal with the signature of receivers that can connect 86 to it. 87 88 @param name: name of the signal. Not unique, but developer 89 friendly 90 @type name: string 91 @param args_types: arguments types of the receivers that can connect 92 to the signal 93 @type args_types: tuple of types 94 @param kw_types: arguments types of the receivers that can connect 95 to the signal 96 @type kw_types: dictionary of types 97 """ 98 self._name = name 99 self._args_types = args_types 100 self._kw_types = kw_types 101 self._receivers = []
102
103 - def __repr__(self):
104 """ Textual representation of the Signal 105 106 @returns: information about the signal and its receivers 107 @rtype: string 108 """ 109 r = '<Signal %s with %d receivers>' % (self._name, len(self._receivers)) 110 return r
111
112 - def connect(self, receiver):
113 """ 114 Connect a receiver to the signal. It will be called when the signal 115 is emitted. 116 117 @param receiver: object to be called when the signal is emitted 118 @type receiver: callable 119 120 @exception TypeError: raised if the receiver is not callable 121 """ 122 if callable(receiver): 123 if receiver not in self._receivers: 124 self._receivers.append(receiver) 125 else: 126 raise TypeError("Receiver is not a callable")
127
128 - def disconnect(self, receiver):
129 """ 130 Disconnect a receiver from the signal. 131 132 @param receiver: object to be disconnected 133 @type receiver: callable 134 135 @exception Exception: raised if the receiver is not connected 136 """ 137 try: 138 self._receivers.remove(receiver) 139 except: 140 raise Exception("%s not connected to the signal" % receiver)
141
142 - def emit(self, *args, **kw):
143 """ 144 Call all the connected receivers. It avoids calling the 145 neutralized ones. Since receivers calling is not ordered, one should be 146 careful with the potential side effects in the receivers. 147 148 @param args: arguments passed to the receivers 149 @type args: tuple 150 @param kw: arguments passed to the receivers 151 @type kw: dictionary 152 153 @exception WrongArgument: Raised if one of the arguments is of the 154 wrong type 155 """ 156 self._check_args_types(args, kw) 157 158 for receiver in self._receivers: 159 receiver(*args, **kw)
160
161 - def _check_args_types(self, args, kw):
162 """ Check the types of arguments and keywords comply with 163 types specifications given at Signal creation. 164 165 @exception WrongArgument: Raised if one of the arguments is of the 166 wrong type 167 """ 168 position = 0 169 for arg in args: 170 expected_type = self._args_types[position] 171 if not isinstance(arg, expected_type): 172 raise WrongArgument(arg, expected_type, position) 173 position += 1 174 175 for key, value in self._kw_types.iteritems(): 176 if key in kw and not isinstance(kw[key], value): 177 raise WrongArgument(kw[key], value, None)
178 179 #if __name__ == "__main__": 180 # def test(number, text, foo=0): 181 # print "test %d %s foo=%s" % (number, text, foo) 182 # 183 # signal1 = Signal('test-signal', int, object, foo=int) 184 # signal1.connect(test) 185 # signal1.emit(42, "boudiou",foo='bar') 186