[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [stem/master] Behave smartly with context-sensitive config keys
commit 9e40210c83c2af32fecb08e4ba4ea71833ce8050
Author: Ravi Chandra Padmala <neenaoffline@xxxxxxxxx>
Date: Thu Jun 21 03:39:34 2012 +0530
Behave smartly with context-sensitive config keys
---
stem/control.py | 108 +++++++++++++++++++++++++++++---------
stem/response/getconf.py | 19 ++-----
test/integ/control/controller.py | 25 +++++----
test/unit/response/getconf.py | 14 +++---
4 files changed, 108 insertions(+), 58 deletions(-)
diff --git a/stem/control.py b/stem/control.py
index 5ce6780..45e4f10 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -404,6 +404,14 @@ class Controller(BaseController):
BaseController and provides a more user friendly API for library users.
"""
+ _mapped_config_keys = {
+ "HiddenServiceDir": "HiddenServiceOptions",
+ "HiddenServicePort": "HiddenServiceOptions",
+ "HiddenServiceVersion": "HiddenServiceOptions",
+ "HiddenServiceAuthorizeClient": "HiddenServiceOptions",
+ "HiddenServiceOptions": "HiddenServiceOptions"
+ }
+
def from_port(control_addr = "127.0.0.1", control_port = 9051):
"""
Constructs a ControlPort based Controller.
@@ -535,12 +543,12 @@ class Controller(BaseController):
def get_conf(self, param, default = UNDEFINED, multiple = False):
"""
- Queries the control socket for the values of given configuration options. If
+ Queries the control socket for the value of a given configuration option. If
provided a default then that's returned as if the GETCONF option is undefined
or if the call fails for any reason (invalid configuration option, error
response, control port closed, initiated, etc).
- :param str,list param: GETCONF option or options to be queried
+ :param str param: GETCONF option to be queried
:param object default: response if the query fails
:param bool multiple: if True, the value(s) provided are lists of all returned values,
otherwise this just provides the first value
@@ -548,39 +556,89 @@ class Controller(BaseController):
:returns:
Response depends upon how we were called as follows...
- * str with the response if our param was a str and multiple was False
- * dict with the param (str) => response (str) mapping if our param was a list and multiple was False
- * list with the response strings if our param was a str and multiple was True
- * dict with the param (str) => response (list) mapping if our param was a list and multiple was True
+ * str with the response if multiple was False
+ * list with the response strings multiple was True
* default if one was provided and our call failed
:raises:
:class:`stem.socket.ControllerError` if the call fails, and we weren't provided a default response
- :class:`stem.socket.InvalidArguments` if the configuration options requested were invalid
+ :class:`stem.socket.InvalidArguments` if the configuration option requested was invalid
+ """
+
+ try:
+ if param == "": raise stem.socket.InvalidRequest("Received empty parameter")
+
+ # automagically change the requested parameter if it's context sensitive
+ # and cannot be returned on it's own.
+ if param.lower() in self._mapped_config_keys.keys():
+ return self.get_conf_map(self._mapped_config_keys[param], default, multiple)[param]
+
+ response = self.msg("GETCONF %s" % param)
+ stem.response.convert("GETCONF", response)
+
+ # error if we got back different parameters than we requested
+ if response.entries.keys()[0].lower() != param.lower():
+ raise stem.socket.ProtocolError("GETCONF reply doesn't match the parameters that we requested. Queried '%s' but got '%s'." % (param, response.entries.keys()[0]))
+
+ if not multiple:
+ return response.entries[param][0]
+ return response.entries[param]
+
+ except stem.socket.ControllerError, exc:
+ if default is UNDEFINED: raise exc
+ else: return default
+
+ def get_conf_map(self, param, default = UNDEFINED, multiple = False):
+ """
+ Queries the control socket for the values of given configuration options and
+ provides a mapping of the keys to the values. If provided a default then
+ that's returned as if the GETCONF option is undefined or if the call fails
+ for any reason (invalid configuration option, error response, control port
+ closed, initiated, etc).
+
+ :param str,list param: GETCONF option(s) to be queried
+ :param object default: response if the query fails
+ :param bool multiple: if True, the value(s) provided are lists of all returned values,
+ otherwise this just provides the first value
+
+ :returns:
+ Response depends upon how we were called as follows...
+
+ * dict with param (str) => response mappings (str) if multiple was False
+ * dict with param (str) => response mappings (list) if multiple was True
+ * dict with param (str) => default mappings if a default value was provided and our call failed
+
+ :raises:
+ :class:`stem.socket.ControllerError` if the call fails, and we weren't provided a default response
+ :class:`stem.socket.InvalidArguments` if the configuration option requested was invalid
"""
if isinstance(param, str):
- is_multiple = False
param = [param]
- else:
- is_multiple = True
try:
- response = self.msg("GETCONF %s" % " ".join(param))
- stem.response.convert("GETCONF", response, multiple = multiple)
+ if param == [""] or param == []:
+ raise stem.socket.InvalidRequest("Received empty parameter")
- if is_multiple:
- return response.entries
- else:
- try: return response.entries[param[0]]
- except KeyError: raise stem.socket.InvalidRequest("Received empty string")
+ response = self.msg("GETCONF %s" % ' '.join(param))
+ stem.response.convert("GETCONF", response)
+
+ requested_params = set(map(lambda x: x.lower(), param))
+ reply_params = set(map(lambda x: x.lower(), response.entries.keys()))
+
+ # if none of the requested parameters are context sensitive and if the
+ # parameters received don't match the parameters requested
+ if not set(self._mapped_config_keys.values()) & requested_params and requested_params != reply_params:
+ requested_label = ", ".join(requested_params)
+ reply_label = ", ".join(reply_params)
+
+ raise stem.socket.ProtocolError("GETCONF reply doesn't match the parameters that we requested. Queried '%s' but got '%s'." % (requested_label, reply_label))
+
+ if not multiple:
+ return dict([(entry[0], entry[1][0]) for entry in response.entries.items()])
+ return response.entries
+
except stem.socket.ControllerError, exc:
- if default is UNDEFINED: raise exc
- elif is_multiple:
- if default != UNDEFINED:
- return dict([(p, default) for p in param])
- else:
- return dict([(p, None) for p in param])
- else:
- return default
+ if default != UNDEFINED: return dict([(p, default) for p in param])
+ else: raise exc
diff --git a/stem/response/getconf.py b/stem/response/getconf.py
index 9ab15c7..1527e39 100644
--- a/stem/response/getconf.py
+++ b/stem/response/getconf.py
@@ -5,17 +5,10 @@ class GetConfResponse(stem.response.ControlMessage):
"""
Reply for a GETCONF query.
- :var dict entries:
- mapping between the queried options (string) and their values (string/list
- of strings)
+ :var dict entries: mapping between the queried options (string) and their values (list of strings)
"""
- def _parse_message(self, multiple = False):
- """
- :param bool multiple:
- if True stores each value in a list, otherwise stores only the first
- values as a sting
- """
+ def _parse_message(self):
# Example:
# 250-CookieAuthentication=0
# 250-ControlPort=9100
@@ -34,8 +27,9 @@ class GetConfResponse(stem.response.ControlMessage):
unrecognized_keywords.append(line[32:-1])
if unrecognized_keywords:
- raise stem.socket.InvalidArguments("552", "GETCONF request contained unrecognized keywords: %s\n" \
+ exc = stem.socket.InvalidArguments("552", "GETCONF request contained unrecognized keywords: %s" \
% ', '.join(unrecognized_keywords), unrecognized_keywords)
+ raise exc
else:
raise stem.socket.ProtocolError("GETCONF response contained a non-OK status code:\n%s" % self)
@@ -51,8 +45,5 @@ class GetConfResponse(stem.response.ControlMessage):
entry = self.entries.get(key, None)
- if multiple:
- self.entries.setdefault(key, []).append(value)
- else:
- self.entries.setdefault(key, value)
+ self.entries.setdefault(key, []).append(value)
diff --git a/test/integ/control/controller.py b/test/integ/control/controller.py
index 38f6f10..f9db10e 100644
--- a/test/integ/control/controller.py
+++ b/test/integ/control/controller.py
@@ -159,24 +159,23 @@ class TestController(unittest.TestCase):
# succeessful batch query
expected = {config_key: connection_value}
- self.assertEqual(expected, controller.get_conf([config_key]))
- self.assertEqual(expected, controller.get_conf([config_key], "la-di-dah"))
+ self.assertEqual(expected, controller.get_conf_map([config_key]))
+ self.assertEqual(expected, controller.get_conf_map([config_key], "la-di-dah"))
getconf_params = set(["ControlPort", "DirPort", "DataDirectory"])
- self.assertEqual(getconf_params, set(controller.get_conf(["ControlPort",
+ self.assertEqual(getconf_params, set(controller.get_conf_map(["ControlPort",
"DirPort", "DataDirectory"])))
# non-existant option(s)
- self.assertRaises(stem.socket.InvalidRequest, controller.get_conf, "blarg")
- self.assertEqual("la-di-dah", controller.get_conf("blarg", "la-di-dah"))
- self.assertRaises(stem.socket.InvalidRequest, controller.get_conf, "blarg")
+ self.assertRaises(stem.socket.InvalidArguments, controller.get_conf, "blarg")
self.assertEqual("la-di-dah", controller.get_conf("blarg", "la-di-dah"))
+ self.assertRaises(stem.socket.InvalidArguments, controller.get_conf_map, "blarg")
+ self.assertEqual({"blarg": "la-di-dah"}, controller.get_conf_map("blarg", "la-di-dah"))
- self.assertRaises(stem.socket.InvalidRequest, controller.get_conf,
- ["blarg", "huadf"], multiple = True)
+ self.assertRaises(stem.socket.InvalidRequest, controller.get_conf_map, ["blarg", "huadf"], multiple = True)
self.assertEqual({"erfusdj": "la-di-dah", "afiafj": "la-di-dah"},
- controller.get_conf(["erfusdj", "afiafj"], "la-di-dah", multiple = True))
+ controller.get_conf_map(["erfusdj", "afiafj"], "la-di-dah", multiple = True))
# multivalue configuration keys
@@ -187,8 +186,10 @@ class TestController(unittest.TestCase):
# empty input
self.assertRaises(stem.socket.InvalidRequest, controller.get_conf, "")
- self.assertEqual("la-di-dah", controller.get_conf("", "la-di-dah"))
+ self.assertRaises(stem.socket.InvalidRequest, controller.get_conf_map, [])
+ self.assertRaises(stem.socket.InvalidRequest, controller.get_conf_map, "")
- self.assertEqual({}, controller.get_conf([]))
- self.assertEqual({}, controller.get_conf([], {}))
+ self.assertEqual("la-di-dah", controller.get_conf("", "la-di-dah"))
+ self.assertEqual({"": "la-di-dah"}, controller.get_conf_map("", "la-di-dah"))
+ self.assertEqual({}, controller.get_conf_map([], "la-di-dah"))
diff --git a/test/unit/response/getconf.py b/test/unit/response/getconf.py
index e0020fb..58e80d6 100644
--- a/test/unit/response/getconf.py
+++ b/test/unit/response/getconf.py
@@ -56,7 +56,7 @@ class TestGetConfResponse(unittest.TestCase):
control_message = mocking.get_message(SINGLE_RESPONSE)
stem.response.convert("GETCONF", control_message)
- self.assertEqual({"DataDirectory": "/home/neena/.tor"}, control_message.entries)
+ self.assertEqual({"DataDirectory": ["/home/neena/.tor"]}, control_message.entries)
def test_batch_response(self):
"""
@@ -67,10 +67,10 @@ class TestGetConfResponse(unittest.TestCase):
stem.response.convert("GETCONF", control_message)
expected = {
- "CookieAuthentication": "0",
- "ControlPort": "9100",
- "DataDirectory": "/tmp/fake dir",
- "DirPort": None,
+ "CookieAuthentication": ["0"],
+ "ControlPort": ["9100"],
+ "DataDirectory": ["/tmp/fake dir"],
+ "DirPort": [None],
}
self.assertEqual(expected, control_message.entries)
@@ -81,7 +81,7 @@ class TestGetConfResponse(unittest.TestCase):
"""
control_message = mocking.get_message(MULTIVALUE_RESPONSE)
- stem.response.convert("GETCONF", control_message, multiple = True)
+ stem.response.convert("GETCONF", control_message)
expected = {
"ControlPort": ["9100"],
@@ -99,7 +99,7 @@ class TestGetConfResponse(unittest.TestCase):
self.assertRaises(stem.socket.InvalidArguments, stem.response.convert, "GETCONF", control_message)
try:
- stem.response.convert("GETCONF", control_message, multiple = True)
+ stem.response.convert("GETCONF", control_message)
except stem.socket.InvalidArguments, exc:
self.assertEqual(exc.arguments, ["brickroad", "submarine"])
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits