diff --git a/rosapi/__init__.py b/rosapi/__init__.py index 0ed89aa..7f94f67 100644 --- a/rosapi/__init__.py +++ b/rosapi/__init__.py @@ -203,10 +203,10 @@ def __init__(self, api, namespace): self.api = api self.namespace = namespace - def call(self, command, set_kwargs, query_kwargs=None): - query_kwargs = query_kwargs or {} - query_arguments = self._prepare_arguments(True, **query_kwargs) - set_arguments = self._prepare_arguments(False, **set_kwargs) + def call(self, command, set_kwargs, query_args=None): + query_args = query_args or [] + query_arguments = self._prepare_arguments(True, query_args) + set_arguments = self._prepare_arguments(False, set_kwargs.items()) query = ([('%s/%s' % (self.namespace, command)).encode('ascii')] + query_arguments + set_arguments) response = self.api.api_client.talk(query) @@ -219,15 +219,19 @@ def call(self, command, set_kwargs, query_kwargs=None): return output @staticmethod - def _prepare_arguments(is_query, **kwargs): + def _prepare_arguments(is_query, args): command_arguments = [] - for key, value in kwargs.items(): + for key, value in args: if key in ['id', 'proplist']: key = '.%s' % key key = key.replace('_', '-') selector_char = '?' if is_query else '=' - command_arguments.append( - ('%s%s=' % (selector_char, key)).encode('ascii') + value) + if is_query and value is None: + # support valueless queries like '?name' which checks if property 'name' exists + command_arguments.append(('?%s' % (key,)).encode('ascii')) + else: + command_arguments.append( + ('%s%s=' % (selector_char, key)).encode('ascii') + value) return command_arguments @@ -241,11 +245,36 @@ def _remove_first_char_from_keys(dictionary): elements.append((key, value)) return dict(elements) - def get(self, **kwargs): - return self.call('print', {}, kwargs) - - def detailed_get(self, **kwargs): - return self.call('print', {'detail': b''}, kwargs) + def get(self, *args, **kwargs): + """ + Accepts multiple parameters, each should be a key-value pair. + For example, the following query: + + /interface/print + ?type=ether + ?type=vlan + ?#| + + Would be called as: + >>> resource.get( ('type', 'ether'), ('type', 'vlan'), ('#|', None) ) + + (Note: the #| syntax ORs the result of the previous two queries together, + thus this is a query for "type=ether OR type=vlan". Correct order of query + words is required for this stack-based syntax to work.) + + This parameter syntax is used because order of query words is significant + (according to the Routerboard API documentation), and since dicts + are inherently unordered, kwargs cannot be used here. + + For compatibility, the old kwargs syntax is still accepted, but + parameters might be sent in any order. + """ + args += tuple(kwargs.items()) + return self.call('print', {}, args) + + def detailed_get(self, *args, **kwargs): + args += tuple(kwargs.items()) + return self.call('print', {'detail': b''}, args) def set(self, **kwargs): return self.call('set', kwargs) @@ -258,21 +287,22 @@ def remove(self, **kwargs): class RouterboardResource(BaseRouterboardResource): - def detailed_get(self, **kwargs): - return self.call('print', {'detail': ''}, kwargs) + def detailed_get(self, *args, **kwargs): + args += tuple(kwargs.items()) + return self.call('print', {'detail': ''}, args) - def call(self, command, set_kwargs, query_kwargs=None): - query_kwargs = query_kwargs or {} + def call(self, command, set_kwargs, query_args=None): + query_args = query_args or [] result = super(RouterboardResource, self).call( - command, self._encode_kwargs(set_kwargs), - self._encode_kwargs(query_kwargs)) + command, dict(self._encode_items(set_kwargs.items())), + self._encode_items(query_args)) for item in result: for k in item: item[k] = item[k].decode('ascii') return result - def _encode_kwargs(self, kwargs): - return dict((k, v.encode('ascii')) for k, v in kwargs.items()) + def _encode_items(self, items): + return [(k, v.encode('ascii')) for k, v in items] class RouterboardAPI(object):