After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 434924 - Add signal helper
Add signal helper
Status: RESOLVED FIXED
Product: pygobject
Classification: Bindings
Component: gobject
Git master
Other Linux
: Normal enhancement
: ---
Assigned To: Nobody's working on this now (help wanted and appreciated)
Python bindings maintainers
Depends on:
Blocks: 672207
 
 
Reported: 2007-05-01 15:54 UTC by Johan (not receiving bugmail) Dahlin
Modified: 2012-09-03 13:48 UTC
See Also:
GNOME target: ---
GNOME version: ---


Attachments
Addition of Signal class for decorating and binding custom signals. (11.52 KB, patch)
2012-03-15 09:02 UTC, Simon Feltman
none Details | Review
2. Addition of Signal class for decorating and binding custom signals. (11.88 KB, patch)
2012-03-15 22:20 UTC, Simon Feltman
none Details | Review
Addition of Signal class for adding and connecting custom signals (16.47 KB, patch)
2012-03-20 11:41 UTC, Simon Feltman
needs-work Details | Review
Patch updates (22.10 KB, patch)
2012-07-07 09:53 UTC, Simon Feltman
accepted-commit_after_freeze Details | Review
Removed debug print (22.07 KB, patch)
2012-08-27 07:09 UTC, Simon Feltman
none Details | Review

Description Johan (not receiving bugmail) Dahlin 2007-05-01 15:54:11 UTC
+++ This bug was initially created as a clone of Bug #338098 +++

Defining signals for an object is quite tricky, some of the
problems with the current situation:

* The API is just horrible, __gsignals__ is plain ugly.
* You need to specify reduntant information, in most cases it doesn't 
  matter when the signal is run nor what the return value is

For earlier discussion see bug 338098
Comment 1 Gustavo Carneiro 2007-07-07 13:41:49 UTC
This is perhaps similar to bug 332782.
Comment 2 Simon Feltman 2012-03-15 09:02:32 UTC
Created attachment 209808 [details] [review]
Addition of Signal class for decorating and binding custom signals.

The Signal class allows for easy creation of signals and removes the
need for __gsignals__ in client code. The Signal class can also be
used as a decorator for wrapping up the custom closure. It provides
a "BoundSignal" when accessed on an instance allowing for
connections without specifying a signal name string.

Example:
class Thing(GObject.GObject):
    value = 0
    @GObject.Signal
    def pushed(self):
        self.value += 1
    @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST)
    def pulled(self):
        self.value -= 1
    stomped = GObject.Signal('stomped', argTypes=(int,))

def onPushed(obj):
    print obj
thing = Thing()
thing.pushed.connect(onPushed)
thing.pushed.emit()
Comment 3 Simon Feltman 2012-03-15 17:46:06 UTC
A couple extra comments on the patch:

* I just realized parameters should be named with lowercase and underscore style. Is this correct? (argTypes should be arg_types)
* The class name itself was named with title case (Signal) to match the style of other classes in GObject. But differs from the "property" class which I propose we rename: https://bugzilla.gnome.org/show_bug.cgi?id=672168
* The class is derived from str so that it can be passed into GObject.connect without problems. I would prefer deriving from object but then GObject.connect would need an override to run str() on the input signal name.
Comment 4 Simon Feltman 2012-03-15 22:20:05 UTC
Created attachment 209893 [details] [review]
2. Addition of Signal class for decorating and binding custom signals.

* Renamed argument and instance vars to lowercase with underscore.
* Fixed potential signal name / class variable naming discrepancy.
Comment 5 Martin Pitt 2012-03-16 16:54:11 UTC
I haven't done a detailled code review yet, but I generally like the idea.

However, as this is quite intrusive, I propose we keep this for post-3.2 (i. e. after GNOME 3.4), as GNOME 3.4 is already deep in feature/API freeze, and next Monday is hard code freeze already.

Thanks for working on this!

P.S. Just caught my eye: onPulled -> on_pulled in the example doc string
Comment 6 Simon Feltman 2012-03-16 22:17:13 UTC
Sounds fine with me. It does need more review and I would like to look into avoiding deriving from str and making connect more robust in that it calls "str" on input signal parameters.
Comment 7 Simon Feltman 2012-03-20 11:41:46 UTC
Created attachment 210171 [details] [review]
Addition of Signal class for adding and connecting custom signals

* Fixed unnamed signals (uses class variable name like Property).
* Added more unittests and examples.
Comment 8 Johan (not receiving bugmail) Dahlin 2012-04-04 14:00:03 UTC
Review of attachment 210171 [details] [review]:

Thanks for starting to do this work, this will be pretty nice.

A couple of design questions follows.

::: examples/properties.py
@@ +3,2 @@
 class MyObject(GObject.GObject):
+    _intdata = 0

This should be an instance variable.

::: examples/signal.py
@@ +7,3 @@
         print "C: class closure for `my_signal' called with argument", arg
 
+    @GObject.Signal(arg_types=(GObject.TYPE_INT,))

I think this should spell; 

@GObject.Signal(int)

Eg, varargs should be the parameter types.

::: gi/_gobject/Makefile.am
@@ +31,3 @@
 	constants.py \
+	propertyhelper.py \
+	signal.py

signalhelper.py to follow the style?

::: gi/_gobject/__init__.py
@@ +109,3 @@
         cls.do_set_property = obj_set_property
 
+    def _install_signals(cls):

Could parts of this be moved to a Signal classmethod to keep most of the implementation in the same module?

@@ +123,3 @@
+                    setattr(cls, name, signal)
+                if signalName in gsignals:
+                # Fixup a signal which is unnamed by using the class variable name.

This exception message could be improved.

::: gi/_gobject/signal.py
@@ +35,3 @@
+        def pulled(self):
+            self.value -= 1
+        stomped = GObject.Signal('stomped', arg_types=(int,))

I'm not sure if the non-decorated case should be supported.
I think it's perfectly fine to require a default method, eg, require a function in constructor.

@@ +43,3 @@
+    obj.pulled.emit()
+    """
+    class BoundSignal(str):

This is pretty weird, can't we just subclass from object?
Comment 9 Simon Feltman 2012-04-07 08:11:43 UTC
Review of attachment 210171 [details] [review]:

Thanks for the review. I will submit another patch upon the details being worked out.

::: examples/properties.py
@@ +3,2 @@
 class MyObject(GObject.GObject):
+    _intdata = 0

I did this for brevity, upon setting the variable using 'self._intdata = value' it will become an instance variable. It can be changed but I don't think it really matters either way.

::: examples/signal.py
@@ +7,3 @@
         print "C: class closure for `my_signal' called with argument", arg
 
+    @GObject.Signal(arg_types=(GObject.TYPE_INT,))

I don't get how this could work. You are suggesting *args be used in place of the explicit "arg_types"? If this were the case, you would only have the ability to initialize a Signal object with callback arg types as any other key arg would immediately be clobbered by what was intended to be used as "arg_types". For instance if the __init__ signature looked like this:
  def __init__(self, flags=0, *args)
Then calling Signal(int) would apply int as the flags parameter and *args would be empty.

Python3 gives us some extra foo that we can play with in regards to inspectable parameter annotations on the closure. For instance, we could use syntax like this when creating a signal:

@Signal
def my_signal(self, amount:float, count:int) -> int:
    return 2

The Signal decorator can pull that meta information out of the function and use it for creating the GObject signal. But this is only supported in python3 so it could come at a later time.

::: gi/_gobject/Makefile.am
@@ +31,3 @@
 	constants.py \
+	propertyhelper.py \
+	signal.py

I dislike things named with either helper or util as these words don't add any more meaning to what is already there. I'd vote for renaming propertyhelper.py to property.py instead, but if you guys aren't ok with that then keeping them consistently named with helper is better then having them different.

::: gi/_gobject/__init__.py
@@ +109,3 @@
         cls.do_set_property = obj_set_property
 
+    def _install_signals(cls):

I think so, I would revise and see how much of it can be moved.

@@ +123,3 @@
+                    setattr(cls, name, signal)
+                if signalName in gsignals:
+                # Fixup a signal which is unnamed by using the class variable name.

Sure thing.

::: gi/_gobject/signal.py
@@ +35,3 @@
+        def pulled(self):
+            self.value -= 1
+        stomped = GObject.Signal('stomped', arg_types=(int,))

The non-decorated usage is actually my primary use case for these. As I don't usually use a closure along with the signal.

@@ +43,3 @@
+    obj.pulled.emit()
+    """
+    class BoundSignal(str):

I agree it seems a bit odd. The reason is for supporting usage of: 
 obj.connect(obj.signal, callback)

So the Signal instance at the class or instance level can be used as an arg to GObject.connect. The other route here would be to change all of the GObject.connect* methods to use 'str' on it's input but this would be a lot of work in comparison with simply deriving from str. But I would be fine with doing that work as well.
Comment 10 Simon Feltman 2012-04-07 08:12:44 UTC
Review of attachment 210171 [details] [review]:

Thanks for the review. I will submit another patch upon the details being worked out.

::: examples/properties.py
@@ +3,2 @@
 class MyObject(GObject.GObject):
+    _intdata = 0

I did this for brevity, upon setting the variable using 'self._intdata = value' it will become an instance variable. It can be changed but I don't think it really matters either way.

::: examples/signal.py
@@ +7,3 @@
         print "C: class closure for `my_signal' called with argument", arg
 
+    @GObject.Signal(arg_types=(GObject.TYPE_INT,))

I don't get how this could work. You are suggesting *args be used in place of the explicit "arg_types"? If this were the case, you would only have the ability to initialize a Signal object with callback arg types as any other key arg would immediately be clobbered by what was intended to be used as "arg_types". For instance if the __init__ signature looked like this:
  def __init__(self, flags=0, *args)
Then calling Signal(int) would apply int as the flags parameter and *args would be empty.

Python3 gives us some extra foo that we can play with in regards to inspectable parameter annotations on the closure. For instance, we could use syntax like this when creating a signal:

@Signal
def my_signal(self, amount:float, count:int) -> int:
    return 2

The Signal decorator can pull that meta information out of the function and use it for creating the GObject signal. But this is only supported in python3 so it could come at a later time.

::: gi/_gobject/Makefile.am
@@ +31,3 @@
 	constants.py \
+	propertyhelper.py \
+	signal.py

I dislike things named with either helper or util as these words don't add any more meaning to what is already there. I'd vote for renaming propertyhelper.py to property.py instead, but if you guys aren't ok with that then keeping them consistently named with helper is better then having them different.

::: gi/_gobject/__init__.py
@@ +109,3 @@
         cls.do_set_property = obj_set_property
 
+    def _install_signals(cls):

I think so, I would revise and see how much of it can be moved.

@@ +123,3 @@
+                    setattr(cls, name, signal)
+                if signalName in gsignals:
+                # Fixup a signal which is unnamed by using the class variable name.

Sure thing.

::: gi/_gobject/signal.py
@@ +35,3 @@
+        def pulled(self):
+            self.value -= 1
+        stomped = GObject.Signal('stomped', arg_types=(int,))

The non-decorated usage is actually my primary use case for these. As I don't usually use a closure along with the signal.

@@ +43,3 @@
+    obj.pulled.emit()
+    """
+    class BoundSignal(str):

I agree it seems a bit odd. The reason is for supporting usage of: 
 obj.connect(obj.signal, callback)

So the Signal instance at the class or instance level can be used as an arg to GObject.connect. The other route here would be to change all of the GObject.connect* methods to use 'str' on it's input but this would be a lot of work in comparison with simply deriving from str. But I would be fine with doing that work as well.
Comment 11 Simon Feltman 2012-04-07 08:13:00 UTC
Review of attachment 210171 [details] [review]:

Thanks for the review. I will submit another patch upon the details being worked out.

::: examples/properties.py
@@ +3,2 @@
 class MyObject(GObject.GObject):
+    _intdata = 0

I did this for brevity, upon setting the variable using 'self._intdata = value' it will become an instance variable. It can be changed but I don't think it really matters either way.

::: examples/signal.py
@@ +7,3 @@
         print "C: class closure for `my_signal' called with argument", arg
 
+    @GObject.Signal(arg_types=(GObject.TYPE_INT,))

I don't get how this could work. You are suggesting *args be used in place of the explicit "arg_types"? If this were the case, you would only have the ability to initialize a Signal object with callback arg types as any other key arg would immediately be clobbered by what was intended to be used as "arg_types". For instance if the __init__ signature looked like this:
  def __init__(self, flags=0, *args)
Then calling Signal(int) would apply int as the flags parameter and *args would be empty.

Python3 gives us some extra foo that we can play with in regards to inspectable parameter annotations on the closure. For instance, we could use syntax like this when creating a signal:

@Signal
def my_signal(self, amount:float, count:int) -> int:
    return 2

The Signal decorator can pull that meta information out of the function and use it for creating the GObject signal. But this is only supported in python3 so it could come at a later time.

::: gi/_gobject/Makefile.am
@@ +31,3 @@
 	constants.py \
+	propertyhelper.py \
+	signal.py

I dislike things named with either helper or util as these words don't add any more meaning to what is already there. I'd vote for renaming propertyhelper.py to property.py instead, but if you guys aren't ok with that then keeping them consistently named with helper is better then having them different.

::: gi/_gobject/__init__.py
@@ +109,3 @@
         cls.do_set_property = obj_set_property
 
+    def _install_signals(cls):

I think so, I would revise and see how much of it can be moved.

@@ +123,3 @@
+                    setattr(cls, name, signal)
+                if signalName in gsignals:
+                # Fixup a signal which is unnamed by using the class variable name.

Sure thing.

::: gi/_gobject/signal.py
@@ +35,3 @@
+        def pulled(self):
+            self.value -= 1
+        stomped = GObject.Signal('stomped', arg_types=(int,))

The non-decorated usage is actually my primary use case for these. As I don't usually use a closure along with the signal.

@@ +43,3 @@
+    obj.pulled.emit()
+    """
+    class BoundSignal(str):

I agree it seems a bit odd. The reason is for supporting usage of: 
 obj.connect(obj.signal, callback)

So the Signal instance at the class or instance level can be used as an arg to GObject.connect. The other route here would be to change all of the GObject.connect* methods to use 'str' on it's input but this would be a lot of work in comparison with simply deriving from str. But I would be fine with doing that work as well.
Comment 12 Simon Feltman 2012-04-07 08:13:13 UTC
Review of attachment 210171 [details] [review]:

Thanks for the review. I will submit another patch upon the details being worked out.

::: examples/properties.py
@@ +3,2 @@
 class MyObject(GObject.GObject):
+    _intdata = 0

I did this for brevity, upon setting the variable using 'self._intdata = value' it will become an instance variable. It can be changed but I don't think it really matters either way.

::: examples/signal.py
@@ +7,3 @@
         print "C: class closure for `my_signal' called with argument", arg
 
+    @GObject.Signal(arg_types=(GObject.TYPE_INT,))

I don't get how this could work. You are suggesting *args be used in place of the explicit "arg_types"? If this were the case, you would only have the ability to initialize a Signal object with callback arg types as any other key arg would immediately be clobbered by what was intended to be used as "arg_types". For instance if the __init__ signature looked like this:
  def __init__(self, flags=0, *args)
Then calling Signal(int) would apply int as the flags parameter and *args would be empty.

Python3 gives us some extra foo that we can play with in regards to inspectable parameter annotations on the closure. For instance, we could use syntax like this when creating a signal:

@Signal
def my_signal(self, amount:float, count:int) -> int:
    return 2

The Signal decorator can pull that meta information out of the function and use it for creating the GObject signal. But this is only supported in python3 so it could come at a later time.

::: gi/_gobject/Makefile.am
@@ +31,3 @@
 	constants.py \
+	propertyhelper.py \
+	signal.py

I dislike things named with either helper or util as these words don't add any more meaning to what is already there. I'd vote for renaming propertyhelper.py to property.py instead, but if you guys aren't ok with that then keeping them consistently named with helper is better then having them different.

::: gi/_gobject/__init__.py
@@ +109,3 @@
         cls.do_set_property = obj_set_property
 
+    def _install_signals(cls):

I think so, I would revise and see how much of it can be moved.

@@ +123,3 @@
+                    setattr(cls, name, signal)
+                if signalName in gsignals:
+                # Fixup a signal which is unnamed by using the class variable name.

Sure thing.

::: gi/_gobject/signal.py
@@ +35,3 @@
+        def pulled(self):
+            self.value -= 1
+        stomped = GObject.Signal('stomped', arg_types=(int,))

The non-decorated usage is actually my primary use case for these. As I don't usually use a closure along with the signal.

@@ +43,3 @@
+    obj.pulled.emit()
+    """
+    class BoundSignal(str):

I agree it seems a bit odd. The reason is for supporting usage of: 
 obj.connect(obj.signal, callback)

So the Signal instance at the class or instance level can be used as an arg to GObject.connect. The other route here would be to change all of the GObject.connect* methods to use 'str' on it's input but this would be a lot of work in comparison with simply deriving from str. But I would be fine with doing that work as well.
Comment 13 Simon Feltman 2012-04-07 08:13:14 UTC
Review of attachment 210171 [details] [review]:

Thanks for the review. I will submit another patch upon the details being worked out.

::: examples/properties.py
@@ +3,2 @@
 class MyObject(GObject.GObject):
+    _intdata = 0

I did this for brevity, upon setting the variable using 'self._intdata = value' it will become an instance variable. It can be changed but I don't think it really matters either way.

::: examples/signal.py
@@ +7,3 @@
         print "C: class closure for `my_signal' called with argument", arg
 
+    @GObject.Signal(arg_types=(GObject.TYPE_INT,))

I don't get how this could work. You are suggesting *args be used in place of the explicit "arg_types"? If this were the case, you would only have the ability to initialize a Signal object with callback arg types as any other key arg would immediately be clobbered by what was intended to be used as "arg_types". For instance if the __init__ signature looked like this:
  def __init__(self, flags=0, *args)
Then calling Signal(int) would apply int as the flags parameter and *args would be empty.

Python3 gives us some extra foo that we can play with in regards to inspectable parameter annotations on the closure. For instance, we could use syntax like this when creating a signal:

@Signal
def my_signal(self, amount:float, count:int) -> int:
    return 2

The Signal decorator can pull that meta information out of the function and use it for creating the GObject signal. But this is only supported in python3 so it could come at a later time.

::: gi/_gobject/Makefile.am
@@ +31,3 @@
 	constants.py \
+	propertyhelper.py \
+	signal.py

I dislike things named with either helper or util as these words don't add any more meaning to what is already there. I'd vote for renaming propertyhelper.py to property.py instead, but if you guys aren't ok with that then keeping them consistently named with helper is better then having them different.

::: gi/_gobject/__init__.py
@@ +109,3 @@
         cls.do_set_property = obj_set_property
 
+    def _install_signals(cls):

I think so, I would revise and see how much of it can be moved.

@@ +123,3 @@
+                    setattr(cls, name, signal)
+                if signalName in gsignals:
+                # Fixup a signal which is unnamed by using the class variable name.

Sure thing.

::: gi/_gobject/signal.py
@@ +35,3 @@
+        def pulled(self):
+            self.value -= 1
+        stomped = GObject.Signal('stomped', arg_types=(int,))

The non-decorated usage is actually my primary use case for these. As I don't usually use a closure along with the signal.

@@ +43,3 @@
+    obj.pulled.emit()
+    """
+    class BoundSignal(str):

I agree it seems a bit odd. The reason is for supporting usage of: 
 obj.connect(obj.signal, callback)

So the Signal instance at the class or instance level can be used as an arg to GObject.connect. The other route here would be to change all of the GObject.connect* methods to use 'str' on it's input but this would be a lot of work in comparison with simply deriving from str. But I would be fine with doing that work as well.
Comment 14 Simon Feltman 2012-04-07 08:23:40 UTC
Sorry for the spam, that publish button seems to be problematic especially when you aren't logged in!
Comment 15 Martin Pitt 2012-04-17 10:05:03 UTC
(In reply to comment #13)
> Thanks for the review. I will submit another patch upon the details being
> worked out.

Thanks. Setting the current one to "needs-work" to reflect the status and clean the patch review queue.

> @@ +31,3 @@
>      constants.py \
> +    propertyhelper.py \
> +    signal.py
> 
> I dislike things named with either helper or util as these words don't add any
> more meaning to what is already there. I'd vote for renaming propertyhelper.py
> to property.py instead

+1 from me. This is mostly bikeshedding of course. If there is no objection, I'll rename it.
Comment 16 Simon Feltman 2012-07-07 09:53:09 UTC
Created attachment 218214 [details] [review]
Patch updates

This latest patch includes the following fixes:
- Renamed signal.py to signalhelper.py for consistency.
- Broke override signal support out into a simplified sub-class named "SignalOverride" for use as a decorator.
- Verified python support from 2.7 to 3.2
- Updated documentation and unittests
- Moved code for managing signal installation from gi/_gobject/__init__.py into gi/_gobject/signalhelper.py
- Fixed problem in test_signal.py where "float" and "double" were being clobbered with GObject.TYPE_FLOAT, renamed usage to gfloat and gdouble respectively.
- Added support for inspecting a signals closure using python3 annotations to denote arg and return types.
Comment 17 Martin Pitt 2012-08-23 04:57:56 UTC
Thanks for the update! This looks quite nice now.

We are past feature and API freeze now, so I'm targetting this at GNOME 3.8 so that we can commit it early in the next cycle. Please feel free to ask the release team for an exception, of course!
Comment 18 Simon Feltman 2012-08-23 07:17:33 UTC
Thanks Martin,
I made a request to the release-team to include this. 

The patch has been tested against the latest source with Python 2.6, 2.7, 3.1, 3.2, and 3.3 and it's good.
Comment 19 Martin Pitt 2012-08-27 04:18:35 UTC
Comment on attachment 218214 [details] [review]
Patch updates

This line should be removed for committing:

458 +            print(args, kargs)

Otherwise this looks fine to me, thanks! I think you already got one +1 from the release team, can you please let me know when you got the second one?

From my POV I'm fine with adding that now, as it does not change existing API and the new features are covered by tests.
Comment 20 Simon Feltman 2012-08-27 07:09:29 UTC
Created attachment 222516 [details] [review]
Removed debug print

Same patch with single debugging print removed.

Only a single +1 from the release team so far. I will update if that changes. Thanks.
Comment 21 Simon Feltman 2012-09-03 11:53:26 UTC
Just got another +1 from Javier Jardón. So this patch should be good to go for the upcoming release!
Comment 22 Martin Pitt 2012-09-03 13:48:12 UTC
Great! I pushed this with a PEP-8 whitespace fix and some changelog improvement.

Thanks a lot!