Bug 10174

Summary: When guessing return signature for a tuple that is not a Struct, produce a multiple-argument reply rather than a single Struct
Product: dbus Reporter: Thomas Wisniewski <wisniewskit>
Component: pythonAssignee: Simon McVittie <smcv>
Status: RESOLVED FIXED QA Contact: Robert McQueen <robert>
Severity: normal    
Priority: medium    
Version: unspecified   
Hardware: All   
OS: All   
Whiteboard:
i915 platform: i915 features:
Attachments: Proposed patch

Description Thomas Wisniewski 2007-03-03 17:43:08 UTC
_method_reply_return in service.py (dbus 1.0.2-r1 in Gentoo) mentions that if a signature of '' is given, the behaviour was to "accept anything", but this was changed to assume no outputs at all. However, some programs (NetworkManager) expect varying numbers and types of outputs, so coming up with a method signature is impossible.
For instance, "getNetworkProperties" always takes some basic values that are specific, but also takes one of four (presently) sets of values for the security settings of the network which have different signatures.
The easiest workaround is to revert to the behaviour alleged in the comment for the function, where unspecified out_signature does not equate to '', but rather to a varied signature where the user must return DBus objects and no introspection is to be done.
Comment 1 Thomas Wisniewski 2007-03-03 20:32:55 UTC
On closer inspection, this is already being done but there is a bug. If no signature is given, _message_cb in service.py incorrectly wraps a retval into another tuple, even if it's already a tuple or list.

This is bad, because the signature-guessing function will then proceed to guess the signature incorrectly. When this behaviour is omitted the guesser works properly and things work out.

I tried attaching a patch, but Bugzilla insisted it was an empty file I was attaching, so here it is (I hope it's not mangled too badly):
--- service.py 2007-01-17 07:25:38.000000000 -0500
+++ service.py 2007-03-03 23:25:05.645547180 -0500
@@ -484,7 +484,7 @@
             else:
                 if retval == None:
                     retval = ()
-                else:
+                elif type(retval)!=type(()) and type(retval)!=type([]):
                     retval = (retval,)
 
             _method_reply_return(connection, message, method_name, signature, *retval)
t
Comment 2 Simon McVittie 2007-03-29 05:01:41 UTC
If what you say is true, then the Network Manager D-Bus API is misdesigned (well, we knew that - it was one of the first D-Bus APIs, so there weren't really any conventions yet). We ought to be able to work around that, though.

This needs looking at when I have some time - there are more subtleties here than I can deal with quickly. In particular we can't tell whether someone returning a tuple wanted to return multiple values, or whether they wanted to return a struct (which tuples are normally coerced into); ditto lists and arrays.

The correct fix is probably to add a subtype of tuple (or list?) called dbus.MultipleArguments (or dbus.lowlevel.MultipleArguments?), which is coerced into multiple values rather than a struct or array. Only people trying to implement unconventional APIs for which signatures don't really work (like the Network Manager API) should ever need it.
Comment 3 Simon McVittie 2007-04-16 09:12:09 UTC
The summary doesn't describe my current thoughts about this bug, so changing it.

If a Python service method with no out_signature returns a tuple, I think dbus-python should default to producing a reply with multiple arguments, rather than a reply with a single struct argument - there's no reason you should ever want the latter when the former is possible, and in any case you can force the latter behaviour by returning a dbus.Struct.

So:

@method(dbus_interface='...')
def Foo(self):
    return (1, 2)
    # or equivalently: return 1, 2

should return a message containing integers 1 and 2 (signature ii), rather than a message containing a struct containing those integers (signature (ii)); to return that struct it should be necessary to do

@method(dbus_interface='...')
def Foo(self):
    return Struct(1, 2)

I'm not sure what should happen in async-implemented service methods: it's clear  that, as is currently done,

@method(dbus_interface='...', async_callbacks=('reply_cb', 'error_cb'))
def Foo(self, reply_cb=None, error_cb=None):
    reply_cb(1, 2)

should produce a reply with signature ii, and that

@method(dbus_interface='...', async_callbacks=('reply_cb', 'error_cb'))
def Foo(self, reply_cb=None, error_cb=None):
    reply_cb(Struct(1, 2))

should produce a reply with signature (ii), but what's not clear is what

@method(dbus_interface='...', async_callbacks=('reply_cb', 'error_cb')
def Foo(self, reply_cb=None, error_cb=None):
    reply_cb((1, 2))

should return. I think a struct (ii), as is done now, would be least astonishing, despite the fact that that's inconsistent with the synchronous case.

I don't think the current behaviour for lists should be changed:

@method(dbus_interface='...')
def Foo(self):
    return [1, 2]

should continue to produce a reply containing an array of integers (signature ai rather than ii).
Comment 4 Simon McVittie 2007-04-24 10:14:12 UTC
Created attachment 9721 [details] [review]
Proposed patch

I think this patch should do what you want. Mailed to the list for review.

"""
Fix fd.o #10174: make it possible to return multiple values wit
h no signature.
More specifically: when a service method with no signature synchronously
returns a tuple that is not a Struct, interpret it as a multi-valued return,
rather than as a structure.

This is a common Python idiom, and returning a struct makes little sense
anyway when D-Bus lets you return multiple values.

Returned lists are still interpreted as arrays - returning an array is
entirely sensible, and indeed likely to be common.

Async service methods are unaffected (there is no ambiguity), and it's still
possible to return a structure by returning a dbus.Struct with appropriate
contents.
"""
Comment 5 Simon McVittie 2007-04-25 03:34:58 UTC
Fixed in git.

Use of freedesktop.org services, including Bugzilla, is subject to our Code of Conduct. How we collect and use information is described in our Privacy Policy.