Don't import inspect at module level
[pygobject.git] / gi / _signalhelper.py
blob30ff541a5f080554f672c4dbcd6c37c6edd91ecf
1 # -*- Mode: Python; py-indent-offset: 4 -*-
2 # pygobject - Python bindings for the GObject library
3 # Copyright (C) 2012 Simon Feltman
5 # gi/_signalhelper.py: GObject signal binding decorator object
7 # This library is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU Lesser General Public
9 # License as published by the Free Software Foundation; either
10 # version 2.1 of the License, or (at your option) any later version.
12 # This library is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 # Lesser General Public License for more details.
17 # You should have received a copy of the GNU Lesser General Public
18 # License along with this library; if not, see <http://www.gnu.org/licenses/>.
20 import sys
22 from ._gi import _gobject
24 # Callable went away in python 3.0 and came back in 3.2.
25 # Use versioning to figure out when to define it, otherwise we have to deal with
26 # the complexity of using __builtin__ or builtin between python versions to
27 # check if callable exists which PyFlakes will also complain about.
28 if (3, 0) <= sys.version_info < (3, 2):
29 def callable(fn):
30 return hasattr(fn, '__call__')
33 class Signal(str):
34 """Object which gives a nice API for creating and binding signals.
36 :param name:
37 Name of signal or callable closure when used as a decorator.
38 :type name: str or callable
39 :param callable func:
40 Callable closure method.
41 :param GObject.SignalFlags flags:
42 Flags specifying when to run closure.
43 :param type return_type:
44 Return type of the Signal.
45 :param list arg_types:
46 List of argument types specifying the signals function signature
47 :param str doc:
48 Documentation of signal object.
49 :param callable accumulator:
50 Accumulator method with the signature:
51 func(ihint, return_accu, handler_return, accu_data) -> boolean
52 :param object accu_data:
53 User data passed to the accumulator.
55 :Example:
57 .. code-block:: python
59 class Spam(GObject.Object):
60 velocity = 0
62 @GObject.Signal
63 def pushed(self):
64 self.velocity += 1
66 @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST)
67 def pulled(self):
68 self.velocity -= 1
70 stomped = GObject.Signal('stomped', arg_types=(int,))
72 @GObject.Signal
73 def annotated_signal(self, a:int, b:str):
74 "Python3 annotation support for parameter types.
76 def on_pushed(obj):
77 print(obj)
79 spam = Spam()
80 spam.pushed.connect(on_pushed)
81 spam.pushed.emit()
82 """
83 class BoundSignal(str):
84 """
85 Temporary binding object which can be used for connecting signals
86 without specifying the signal name string to connect.
87 """
88 def __new__(cls, name, *args, **kargs):
89 return str.__new__(cls, name)
91 def __init__(self, signal, gobj):
92 str.__init__(self)
93 self.signal = signal
94 self.gobj = gobj
96 def __repr__(self):
97 return 'BoundSignal("%s")' % self
99 def __call__(self, *args, **kargs):
100 """Call the signals closure."""
101 return self.signal.func(self.gobj, *args, **kargs)
103 def connect(self, callback, *args, **kargs):
104 """Same as GObject.Object.connect except there is no need to specify
105 the signal name."""
106 return self.gobj.connect(self, callback, *args, **kargs)
108 def connect_detailed(self, callback, detail, *args, **kargs):
109 """Same as GObject.Object.connect except there is no need to specify
110 the signal name. In addition concats "::<detail>" to the signal name
111 when connecting; for use with notifications like "notify" when a property
112 changes.
114 return self.gobj.connect(self + '::' + detail, callback, *args, **kargs)
116 def disconnect(self, handler_id):
117 """Same as GObject.Object.disconnect."""
118 self.instance.disconnect(handler_id)
120 def emit(self, *args, **kargs):
121 """Same as GObject.Object.emit except there is no need to specify
122 the signal name."""
123 return self.gobj.emit(str(self), *args, **kargs)
125 def __new__(cls, name='', *args, **kargs):
126 if callable(name):
127 name = name.__name__
128 return str.__new__(cls, name)
130 def __init__(self, name='', func=None, flags=_gobject.SIGNAL_RUN_FIRST,
131 return_type=None, arg_types=None, doc='', accumulator=None, accu_data=None):
132 if func and not name:
133 name = func.__name__
134 elif callable(name):
135 func = name
136 name = func.__name__
137 if func and not doc:
138 doc = func.__doc__
140 str.__init__(self)
142 if func and not (return_type or arg_types):
143 return_type, arg_types = get_signal_annotations(func)
144 if arg_types is None:
145 arg_types = tuple()
147 self.func = func
148 self.flags = flags
149 self.return_type = return_type
150 self.arg_types = arg_types
151 self.__doc__ = doc
152 self.accumulator = accumulator
153 self.accu_data = accu_data
155 def __get__(self, instance, owner=None):
156 """Returns a BoundSignal when accessed on an object instance."""
157 if instance is None:
158 return self
159 return self.BoundSignal(self, instance)
161 def __call__(self, obj, *args, **kargs):
162 """Allows for instantiated Signals to be used as a decorator or calling
163 of the underlying signal method."""
165 # If obj is a GObject, than we call this signal as a closure otherwise
166 # it is used as a re-application of a decorator.
167 if isinstance(obj, _gobject.GObject):
168 self.func(obj, *args, **kargs)
169 else:
170 # If self is already an allocated name, use it otherwise create a new named
171 # signal using the closure name as the name.
172 if str(self):
173 name = str(self)
174 else:
175 name = obj.__name__
176 # Return a new value of this type since it is based on an immutable string.
177 return type(self)(name=name, func=obj, flags=self.flags,
178 return_type=self.return_type, arg_types=self.arg_types,
179 doc=self.__doc__, accumulator=self.accumulator, accu_data=self.accu_data)
181 def copy(self, newName=None):
182 """Returns a renamed copy of the Signal."""
183 if newName is None:
184 newName = self.name
185 return type(self)(name=newName, func=self.func, flags=self.flags,
186 return_type=self.return_type, arg_types=self.arg_types,
187 doc=self.__doc__, accumulator=self.accumulator, accu_data=self.accu_data)
189 def get_signal_args(self):
190 """Returns a tuple of: (flags, return_type, arg_types, accumulator, accu_data)"""
191 return (self.flags, self.return_type, self.arg_types, self.accumulator, self.accu_data)
194 class SignalOverride(Signal):
195 """Specialized sub-class of Signal which can be used as a decorator for overriding
196 existing signals on GObjects.
198 :Example:
200 .. code-block:: python
202 class MyWidget(Gtk.Widget):
203 @GObject.SignalOverride
204 def configure_event(self):
205 pass
207 def get_signal_args(self):
208 """Returns the string 'override'."""
209 return 'override'
212 def get_signal_annotations(func):
213 """Attempt pulling python 3 function annotations off of 'func' for
214 use as a signals type information. Returns an ordered nested tuple
215 of (return_type, (arg_type1, arg_type2, ...)). If the given function
216 does not have annotations then (None, tuple()) is returned.
218 arg_types = tuple()
219 return_type = None
221 if hasattr(func, '__annotations__'):
222 # import inspect only when needed because it takes ~10 msec to load
223 import inspect
224 spec = inspect.getfullargspec(func)
225 arg_types = tuple(spec.annotations[arg] for arg in spec.args
226 if arg in spec.annotations)
227 if 'return' in spec.annotations:
228 return_type = spec.annotations['return']
230 return return_type, arg_types
233 def install_signals(cls):
234 """Adds Signal instances on a GObject derived class into the '__gsignals__'
235 dictionary to be picked up and registered as real GObject signals.
237 gsignals = cls.__dict__.get('__gsignals__', {})
238 newsignals = {}
239 for name, signal in cls.__dict__.items():
240 if isinstance(signal, Signal):
241 signalName = str(signal)
242 # Fixup a signal which is unnamed by using the class variable name.
243 # Since Signal is based on string which immutable,
244 # we must copy and replace the class variable.
245 if not signalName:
246 signalName = name
247 signal = signal.copy(name)
248 setattr(cls, name, signal)
249 if signalName in gsignals:
250 raise ValueError('Signal "%s" has already been registered.' % name)
251 newsignals[signalName] = signal
252 gsignals[signalName] = signal.get_signal_args()
254 cls.__gsignals__ = gsignals
256 # Setup signal closures by adding the specially named
257 # method to the class in the form of "do_<signal_name>".
258 for name, signal in newsignals.items():
259 if signal.func is not None:
260 funcName = 'do_' + name.replace('-', '_')
261 if not hasattr(cls, funcName):
262 setattr(cls, funcName, signal.func)