[tor-commits] [stem/master] Using a decorator to support having a default value
atagar at torproject.org
atagar at torproject.org
Sat Nov 8 21:48:25 UTC 2014
commit 56a1f03e14ca15fd5daf1f3c3405c91397e0e4e8
Author: Damian Johnson <atagar at torproject.org>
Date: Sat Nov 8 12:11:27 2014 -0800
Using a decorator to support having a default value
Had a eurika moment this last week that we can replace our...
def foo(self, default = UNDEFINED):
try:
... do stuff...
except Exception as exc:
if default != UNDEFINED:
return default
else:
raise exc
... with a decorator...
@with_default
def foo(self, default = UNDEFINED):
... do stuff...
We do this for just about every getter method, so having a decorator
significately cuts down on boilerplate.
---
docs/faq.rst | 2 +-
stem/control.py | 587 ++++++++++++++++++++++++-----------------------------
stem/util/conf.py | 18 +-
3 files changed, 277 insertions(+), 330 deletions(-)
diff --git a/docs/faq.rst b/docs/faq.rst
index aa9b7f9..b235961 100644
--- a/docs/faq.rst
+++ b/docs/faq.rst
@@ -318,7 +318,7 @@ An important thing to note is that a new circuit does not necessarily mean a new
Tor does not have a method for cycling your IP address. This is on purpose, and done for a couple reasons. The first is that this capability is usually requested for not-so-nice reasons such as ban evasion or SEO. Second, repeated circuit creation puts a very high load on the Tor network, so please don't!
-With all that out of the way, how do you create a new circuit? You can customise the rate at which Tor cycles circuits with the **MaxCircuitDirtiness** option in your `torrc <https://www.torproject.org/docs/faq.html.en#torrc>`_. `Vidalia <https://www.torproject.org/getinvolved/volunteer.html.en#project-vidalia>`_ and `arm <https://www.atagar.com/arm/>`_ both provide a method to request a new identity, and you can do so programmatically by sending Tor a NEWNYM signal.
+With all that out of the way, how do you create a new circuit? You can customize the rate at which Tor cycles circuits with the **MaxCircuitDirtiness** option in your `torrc <https://www.torproject.org/docs/faq.html.en#torrc>`_. `Vidalia <https://www.torproject.org/getinvolved/volunteer.html.en#project-vidalia>`_ and `arm <https://www.atagar.com/arm/>`_ both provide a method to request a new identity, and you can do so programmatically by sending Tor a NEWNYM signal.
To do this with telnet...
diff --git a/stem/control.py b/stem/control.py
index c297f0b..d815fcc 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -222,6 +222,7 @@ If you're fine with allowing your script to raise exceptions then this can be mo
import calendar
import collections
import datetime
+import inspect
import io
import os
import Queue
@@ -357,6 +358,53 @@ AccountingStats = collections.namedtuple('AccountingStats', [
])
+def with_default(yields = False):
+ """
+ Provides a decorator to support having a default value. This should be
+ treated as private.
+ """
+
+ def decorator(func):
+ def get_default(func, args, kwargs):
+ arg_names = inspect.getargspec(func).args
+ default_position = arg_names.index('default') if 'default' in arg_names else None
+
+ if default_position and default_position < len(args):
+ return args[default_position]
+ else:
+ return kwargs.get('default', UNDEFINED)
+
+ if not yields:
+ def wrapped(*args, **kwargs):
+ try:
+ return func(*args, **kwargs)
+ except Exception as exc:
+ default = get_default(func, args, kwargs)
+
+ if default == UNDEFINED:
+ raise exc
+ else:
+ return default
+ else:
+ def wrapped(*args, **kwargs):
+ try:
+ for val in func(*args, **kwargs):
+ yield val
+ except Exception as exc:
+ default = get_default(func, args, kwargs)
+
+ if default == UNDEFINED:
+ raise exc
+ else:
+ if default is not None:
+ for val in default:
+ yield val
+
+ return wrapped
+
+ return decorator
+
+
class BaseController(object):
"""
Controller for the tor process. This is a minimal base class for other
@@ -894,6 +942,7 @@ class Controller(BaseController):
import stem.connection
stem.connection.authenticate(self, *args, **kwargs)
+ @with_default()
def get_info(self, params, default = UNDEFINED, get_bytes = False):
"""
Queries the control socket for the given GETINFO option. If provided a
@@ -950,10 +999,8 @@ class Controller(BaseController):
for param in params:
if param.startswith('ip-to-country/') and self.is_geoip_unavailable():
# the geoip database already looks to be unavailable - abort the request
- if default == UNDEFINED:
- raise stem.ProtocolError('Tor geoip database is unavailable')
- else:
- return default
+
+ raise stem.ProtocolError('Tor geoip database is unavailable')
# if everything was cached then short circuit making the query
if not params:
@@ -1013,11 +1060,9 @@ class Controller(BaseController):
log.debug('GETINFO %s (failed: %s)' % (' '.join(params), exc))
- if default == UNDEFINED:
- raise exc
- else:
- return default
+ raise exc
+ @with_default()
def get_version(self, default = UNDEFINED):
"""
A convenience method to get tor version that current controller is
@@ -1035,20 +1080,15 @@ class Controller(BaseController):
An exception is only raised if we weren't provided a default response.
"""
- try:
- version = self._get_cache('version')
+ version = self._get_cache('version')
- if not version:
- version = stem.version.Version(self.get_info('version'))
- self._set_cache({'version': version})
+ if not version:
+ version = stem.version.Version(self.get_info('version'))
+ self._set_cache({'version': version})
- return version
- except Exception as exc:
- if default == UNDEFINED:
- raise exc
- else:
- return default
+ return version
+ @with_default()
def get_exit_policy(self, default = UNDEFINED):
"""
Effective ExitPolicy for our relay. This accounts for
@@ -1067,30 +1107,25 @@ class Controller(BaseController):
"""
with self._msg_lock:
- try:
- config_policy = self._get_cache('exit_policy')
+ config_policy = self._get_cache('exit_policy')
- if not config_policy:
- policy = []
+ if not config_policy:
+ policy = []
- if self.get_conf('ExitPolicyRejectPrivate') == '1':
- policy.append('reject private:*')
+ if self.get_conf('ExitPolicyRejectPrivate') == '1':
+ policy.append('reject private:*')
- for policy_line in self.get_conf('ExitPolicy', multiple = True):
- policy += policy_line.split(',')
+ for policy_line in self.get_conf('ExitPolicy', multiple = True):
+ policy += policy_line.split(',')
- policy += self.get_info('exit-policy/default').split(',')
+ policy += self.get_info('exit-policy/default').split(',')
- config_policy = stem.exit_policy.get_config_policy(policy, self.get_info('address'))
- self._set_cache({'exit_policy': config_policy})
+ config_policy = stem.exit_policy.get_config_policy(policy, self.get_info('address'))
+ self._set_cache({'exit_policy': config_policy})
- return config_policy
- except Exception as exc:
- if default == UNDEFINED:
- raise exc
- else:
- return default
+ return config_policy
+ @with_default()
def get_ports(self, listener_type, default = UNDEFINED):
"""
Provides the local ports where tor is listening for the given type of
@@ -1111,14 +1146,9 @@ class Controller(BaseController):
and no default was provided
"""
- try:
- return [port for (addr, port) in self.get_listeners(listener_type) if addr == '127.0.0.1']
- except stem.ControllerError as exc:
- if default == UNDEFINED:
- raise exc
- else:
- return default
+ return [port for (addr, port) in self.get_listeners(listener_type) if addr == '127.0.0.1']
+ @with_default()
def get_listeners(self, listener_type, default = UNDEFINED):
"""
Provides the addresses and ports where tor is listening for connections of
@@ -1139,79 +1169,74 @@ class Controller(BaseController):
and no default was provided
"""
+ proxy_addrs = []
+ query = 'net/listeners/%s' % listener_type.lower()
+
try:
- proxy_addrs = []
- query = 'net/listeners/%s' % listener_type.lower()
+ for listener in self.get_info(query).split():
+ if not (listener.startswith('"') and listener.endswith('"')):
+ raise stem.ProtocolError("'GETINFO %s' responses are expected to be quoted: %s" % (query, listener))
+ elif ':' not in listener:
+ raise stem.ProtocolError("'GETINFO %s' had a listener without a colon: %s" % (query, listener))
- try:
- for listener in self.get_info(query).split():
- if not (listener.startswith('"') and listener.endswith('"')):
- raise stem.ProtocolError("'GETINFO %s' responses are expected to be quoted: %s" % (query, listener))
- elif ':' not in listener:
- raise stem.ProtocolError("'GETINFO %s' had a listener without a colon: %s" % (query, listener))
+ listener = listener[1:-1] # strip quotes
+ addr, port = listener.split(':')
- listener = listener[1:-1] # strip quotes
+ # Skip unix sockets, for instance...
+ #
+ # GETINFO net/listeners/control
+ # 250-net/listeners/control="unix:/tmp/tor/socket"
+ # 250 OK
+
+ if addr == 'unix':
+ continue
+
+ proxy_addrs.append((addr, port))
+ except stem.InvalidArguments:
+ # Tor version is old (pre-tor-0.2.2.26-beta), use get_conf() instead.
+ # Some options (like the ORPort) can have optional attributes after the
+ # actual port number.
+
+ port_option = {
+ Listener.OR: 'ORPort',
+ Listener.DIR: 'DirPort',
+ Listener.SOCKS: 'SocksPort',
+ Listener.TRANS: 'TransPort',
+ Listener.NATD: 'NatdPort',
+ Listener.DNS: 'DNSPort',
+ Listener.CONTROL: 'ControlPort',
+ }[listener_type]
+
+ listener_option = {
+ Listener.OR: 'ORListenAddress',
+ Listener.DIR: 'DirListenAddress',
+ Listener.SOCKS: 'SocksListenAddress',
+ Listener.TRANS: 'TransListenAddress',
+ Listener.NATD: 'NatdListenAddress',
+ Listener.DNS: 'DNSListenAddress',
+ Listener.CONTROL: 'ControlListenAddress',
+ }[listener_type]
+
+ port_value = self.get_conf(port_option).split()[0]
+
+ for listener in self.get_conf(listener_option, multiple = True):
+ if ':' in listener:
addr, port = listener.split(':')
-
- # Skip unix sockets, for instance...
- #
- # GETINFO net/listeners/control
- # 250-net/listeners/control="unix:/tmp/tor/socket"
- # 250 OK
-
- if addr == 'unix':
- continue
-
proxy_addrs.append((addr, port))
- except stem.InvalidArguments:
- # Tor version is old (pre-tor-0.2.2.26-beta), use get_conf() instead.
- # Some options (like the ORPort) can have optional attributes after the
- # actual port number.
-
- port_option = {
- Listener.OR: 'ORPort',
- Listener.DIR: 'DirPort',
- Listener.SOCKS: 'SocksPort',
- Listener.TRANS: 'TransPort',
- Listener.NATD: 'NatdPort',
- Listener.DNS: 'DNSPort',
- Listener.CONTROL: 'ControlPort',
- }[listener_type]
-
- listener_option = {
- Listener.OR: 'ORListenAddress',
- Listener.DIR: 'DirListenAddress',
- Listener.SOCKS: 'SocksListenAddress',
- Listener.TRANS: 'TransListenAddress',
- Listener.NATD: 'NatdListenAddress',
- Listener.DNS: 'DNSListenAddress',
- Listener.CONTROL: 'ControlListenAddress',
- }[listener_type]
-
- port_value = self.get_conf(port_option).split()[0]
-
- for listener in self.get_conf(listener_option, multiple = True):
- if ':' in listener:
- addr, port = listener.split(':')
- proxy_addrs.append((addr, port))
- else:
- proxy_addrs.append((listener, port_value))
+ else:
+ proxy_addrs.append((listener, port_value))
- # validate that address/ports are valid, and convert ports to ints
+ # validate that address/ports are valid, and convert ports to ints
- for addr, port in proxy_addrs:
- if not stem.util.connection.is_valid_ipv4_address(addr):
- raise stem.ProtocolError('Invalid address for a %s listener: %s' % (listener_type, addr))
- elif not stem.util.connection.is_valid_port(port):
- raise stem.ProtocolError('Invalid port for a %s listener: %s' % (listener_type, port))
+ for addr, port in proxy_addrs:
+ if not stem.util.connection.is_valid_ipv4_address(addr):
+ raise stem.ProtocolError('Invalid address for a %s listener: %s' % (listener_type, addr))
+ elif not stem.util.connection.is_valid_port(port):
+ raise stem.ProtocolError('Invalid port for a %s listener: %s' % (listener_type, port))
- return [(addr, int(port)) for (addr, port) in proxy_addrs]
- except Exception as exc:
- if default == UNDEFINED:
- raise exc
- else:
- return default
+ return [(addr, int(port)) for (addr, port) in proxy_addrs]
+ @with_default()
def get_accounting_stats(self, default = UNDEFINED):
"""
Provides stats related to our relaying limitations if AccountingMax was set
@@ -1239,37 +1264,31 @@ class Controller(BaseController):
and no default was provided
"""
- try:
- if self.get_info('accounting/enabled') != '1':
- raise stem.ControllerError("Accounting isn't enabled")
-
- retrieved = time.time()
- status = self.get_info('accounting/hibernating')
- interval_end = self.get_info('accounting/interval-end')
- used = self.get_info('accounting/bytes')
- left = self.get_info('accounting/bytes-left')
-
- interval_end = datetime.datetime.strptime(interval_end, '%Y-%m-%d %H:%M:%S')
- used_read, used_written = [int(val) for val in used.split(' ', 1)]
- left_read, left_written = [int(val) for val in left.split(' ', 1)]
-
- return AccountingStats(
- retrieved = retrieved,
- status = status,
- interval_end = interval_end,
- time_until_reset = calendar.timegm(interval_end.timetuple()) - int(retrieved),
- read_bytes = used_read,
- read_bytes_left = left_read,
- read_limit = used_read + left_read,
- written_bytes = used_written,
- write_bytes_left = left_written,
- write_limit = used_written + left_written,
- )
- except Exception as exc:
- if default == UNDEFINED:
- raise exc
- else:
- return default
+ if self.get_info('accounting/enabled') != '1':
+ raise stem.ControllerError("Accounting isn't enabled")
+
+ retrieved = time.time()
+ status = self.get_info('accounting/hibernating')
+ interval_end = self.get_info('accounting/interval-end')
+ used = self.get_info('accounting/bytes')
+ left = self.get_info('accounting/bytes-left')
+
+ interval_end = datetime.datetime.strptime(interval_end, '%Y-%m-%d %H:%M:%S')
+ used_read, used_written = [int(val) for val in used.split(' ', 1)]
+ left_read, left_written = [int(val) for val in left.split(' ', 1)]
+
+ return AccountingStats(
+ retrieved = retrieved,
+ status = status,
+ interval_end = interval_end,
+ time_until_reset = calendar.timegm(interval_end.timetuple()) - int(retrieved),
+ read_bytes = used_read,
+ read_bytes_left = left_read,
+ read_limit = used_read + left_read,
+ written_bytes = used_written,
+ write_bytes_left = left_written,
+ write_limit = used_written + left_written,
+ )
def get_socks_listeners(self, default = UNDEFINED):
"""
@@ -1290,6 +1309,7 @@ class Controller(BaseController):
return self.get_listeners(Listener.SOCKS, default)
+ @with_default()
def get_protocolinfo(self, default = UNDEFINED):
"""
A convenience method to get the protocol info of the controller.
@@ -1308,15 +1328,9 @@ class Controller(BaseController):
"""
import stem.connection
+ return stem.connection.get_protocolinfo(self)
- try:
- return stem.connection.get_protocolinfo(self)
- except Exception as exc:
- if default == UNDEFINED:
- raise exc
- else:
- return default
-
+ @with_default()
def get_user(self, default = UNDEFINED):
"""
Provides the user tor is running as. This often only works if tor is
@@ -1344,14 +1358,10 @@ class Controller(BaseController):
if user:
self._set_cache({'user': user})
return user
- elif default == UNDEFINED:
- if self.is_localhost():
- raise ValueError("Unable to resolve tor's user")
- else:
- raise ValueError("Tor isn't running locally")
else:
- return default
+ raise ValueError("Unable to resolve tor's user" if self.is_localhost() else "Tor isn't running locally")
+ @with_default()
def get_pid(self, default = UNDEFINED):
"""
Provides the process id of tor. This often only works if tor is running
@@ -1400,14 +1410,10 @@ class Controller(BaseController):
if pid:
self._set_cache({'pid': pid})
return pid
- elif default == UNDEFINED:
- if self.is_localhost():
- raise ValueError("Unable to resolve tor's pid")
- else:
- raise ValueError("Tor isn't running locally")
else:
- return default
+ raise ValueError("Unable to resolve tor's pid" if self.is_localhost() else "Tor isn't running locally")
+ @with_default()
def get_microdescriptor(self, relay = None, default = UNDEFINED):
"""
Provides the microdescriptor for the relay with the given fingerprint or
@@ -1435,28 +1441,23 @@ class Controller(BaseController):
An exception is only raised if we weren't provided a default response.
"""
- try:
- if relay is None:
- try:
- relay = self.get_info('fingerprint')
- except stem.ControllerError as exc:
- raise stem.ControllerError('Unable to determine our own fingerprint: %s' % exc)
+ if relay is None:
+ try:
+ relay = self.get_info('fingerprint')
+ except stem.ControllerError as exc:
+ raise stem.ControllerError('Unable to determine our own fingerprint: %s' % exc)
- if stem.util.tor_tools.is_valid_fingerprint(relay):
- query = 'md/id/%s' % relay
- elif stem.util.tor_tools.is_valid_nickname(relay):
- query = 'md/name/%s' % relay
- else:
- raise ValueError("'%s' isn't a valid fingerprint or nickname" % relay)
+ if stem.util.tor_tools.is_valid_fingerprint(relay):
+ query = 'md/id/%s' % relay
+ elif stem.util.tor_tools.is_valid_nickname(relay):
+ query = 'md/name/%s' % relay
+ else:
+ raise ValueError("'%s' isn't a valid fingerprint or nickname" % relay)
- desc_content = self.get_info(query, get_bytes = True)
- return stem.descriptor.microdescriptor.Microdescriptor(desc_content)
- except Exception as exc:
- if default == UNDEFINED:
- raise exc
- else:
- return default
+ desc_content = self.get_info(query, get_bytes = True)
+ return stem.descriptor.microdescriptor.Microdescriptor(desc_content)
+ @with_default(yields = True)
def get_microdescriptors(self, default = UNDEFINED):
"""
Provides an iterator for all of the microdescriptors that tor presently
@@ -1477,35 +1478,28 @@ class Controller(BaseController):
"""
try:
- try:
- data_directory = self.get_conf('DataDirectory')
- except stem.ControllerError as exc:
- raise stem.OperationFailed(message = 'Unable to determine the data directory (%s)' % exc)
+ data_directory = self.get_conf('DataDirectory')
+ except stem.ControllerError as exc:
+ raise stem.OperationFailed(message = 'Unable to determine the data directory (%s)' % exc)
- cached_descriptor_path = os.path.join(data_directory, 'cached-microdescs')
+ cached_descriptor_path = os.path.join(data_directory, 'cached-microdescs')
- if not os.path.exists(data_directory):
- raise stem.OperationFailed(message = "Data directory reported by tor doesn't exist (%s)" % data_directory)
- elif not os.path.exists(cached_descriptor_path):
- raise stem.OperationFailed(message = "Data directory doens't contain cached microescriptors (%s)" % cached_descriptor_path)
+ if not os.path.exists(data_directory):
+ raise stem.OperationFailed(message = "Data directory reported by tor doesn't exist (%s)" % data_directory)
+ elif not os.path.exists(cached_descriptor_path):
+ raise stem.OperationFailed(message = "Data directory doens't contain cached microescriptors (%s)" % cached_descriptor_path)
- with stem.descriptor.reader.DescriptorReader([cached_descriptor_path]) as reader:
- for desc in reader:
- # It shouldn't be possible for these to be something other than
- # microdescriptors but as the saying goes: trust but verify.
+ with stem.descriptor.reader.DescriptorReader([cached_descriptor_path]) as reader:
+ for desc in reader:
+ # It shouldn't be possible for these to be something other than
+ # microdescriptors but as the saying goes: trust but verify.
- if not isinstance(desc, stem.descriptor.microdescriptor.Microdescriptor):
- raise stem.OperationFailed(message = 'BUG: Descriptor reader provided non-microdescriptor content (%s)' % type(desc))
+ if not isinstance(desc, stem.descriptor.microdescriptor.Microdescriptor):
+ raise stem.OperationFailed(message = 'BUG: Descriptor reader provided non-microdescriptor content (%s)' % type(desc))
- yield desc
- except Exception as exc:
- if default == UNDEFINED:
- raise exc
- else:
- if default is not None:
- for entry in default:
- yield entry
+ yield desc
+ @with_default()
def get_server_descriptor(self, relay = None, default = UNDEFINED):
"""
Provides the server descriptor for the relay with the given fingerprint or
@@ -1555,14 +1549,12 @@ class Controller(BaseController):
desc_content = self.get_info(query, get_bytes = True)
return stem.descriptor.server_descriptor.RelayDescriptor(desc_content)
except Exception as exc:
- if default == UNDEFINED:
- if not self._is_server_descriptors_available():
- raise ValueError(SERVER_DESCRIPTORS_UNSUPPORTED)
+ if not self._is_server_descriptors_available():
+ raise ValueError(SERVER_DESCRIPTORS_UNSUPPORTED)
- raise exc
- else:
- return default
+ raise exc
+ @with_default(yields = True)
def get_server_descriptors(self, default = UNDEFINED):
"""
Provides an iterator for all of the server descriptors that tor presently
@@ -1583,26 +1575,18 @@ class Controller(BaseController):
default was provided
"""
- try:
- # TODO: We should iterate over the descriptors as they're read from the
- # socket rather than reading the whole thing into memory.
- #
- # https://trac.torproject.org/8248
+ # TODO: We should iterate over the descriptors as they're read from the
+ # socket rather than reading the whole thing into memory.
+ #
+ # https://trac.torproject.org/8248
- desc_content = self.get_info('desc/all-recent', get_bytes = True)
+ desc_content = self.get_info('desc/all-recent', get_bytes = True)
- if not desc_content and not self._is_server_descriptors_available():
- raise ValueError(SERVER_DESCRIPTORS_UNSUPPORTED)
+ if not desc_content and not self._is_server_descriptors_available():
+ raise ValueError(SERVER_DESCRIPTORS_UNSUPPORTED)
- for desc in stem.descriptor.server_descriptor._parse_file(io.BytesIO(desc_content)):
- yield desc
- except Exception as exc:
- if default == UNDEFINED:
- raise exc
- else:
- if default is not None:
- for entry in default:
- yield entry
+ for desc in stem.descriptor.server_descriptor._parse_file(io.BytesIO(desc_content)):
+ yield desc
def _is_server_descriptors_available(self):
"""
@@ -1612,6 +1596,7 @@ class Controller(BaseController):
return self.get_version() < stem.version.Requirement.MICRODESCRIPTOR_IS_DEFAULT or \
self.get_conf('UseMicrodescriptors', None) == '0'
+ @with_default()
def get_network_status(self, relay = None, default = UNDEFINED):
"""
Provides the router status entry for the relay with the given fingerprint
@@ -1656,32 +1641,27 @@ class Controller(BaseController):
#
# https://trac.torproject.org/7953
- try:
- if relay is None:
- try:
- relay = self.get_info('fingerprint')
- except stem.ControllerError as exc:
- raise stem.ControllerError('Unable to determine our own fingerprint: %s' % exc)
+ if relay is None:
+ try:
+ relay = self.get_info('fingerprint')
+ except stem.ControllerError as exc:
+ raise stem.ControllerError('Unable to determine our own fingerprint: %s' % exc)
- if stem.util.tor_tools.is_valid_fingerprint(relay):
- query = 'ns/id/%s' % relay
- elif stem.util.tor_tools.is_valid_nickname(relay):
- query = 'ns/name/%s' % relay
- else:
- raise ValueError("'%s' isn't a valid fingerprint or nickname" % relay)
+ if stem.util.tor_tools.is_valid_fingerprint(relay):
+ query = 'ns/id/%s' % relay
+ elif stem.util.tor_tools.is_valid_nickname(relay):
+ query = 'ns/name/%s' % relay
+ else:
+ raise ValueError("'%s' isn't a valid fingerprint or nickname" % relay)
- desc_content = self.get_info(query, get_bytes = True)
+ desc_content = self.get_info(query, get_bytes = True)
- if self.get_conf('UseMicrodescriptors', '0') == '1':
- return stem.descriptor.router_status_entry.RouterStatusEntryMicroV3(desc_content)
- else:
- return stem.descriptor.router_status_entry.RouterStatusEntryV3(desc_content)
- except Exception as exc:
- if default == UNDEFINED:
- raise exc
- else:
- return default
+ if self.get_conf('UseMicrodescriptors', '0') == '1':
+ return stem.descriptor.router_status_entry.RouterStatusEntryMicroV3(desc_content)
+ else:
+ return stem.descriptor.router_status_entry.RouterStatusEntryV3(desc_content)
+ @with_default(yields = True)
def get_network_statuses(self, default = UNDEFINED):
"""
Provides an iterator for all of the router status entries that tor
@@ -1713,29 +1693,21 @@ class Controller(BaseController):
else:
desc_class = stem.descriptor.router_status_entry.RouterStatusEntryV3
- try:
- # TODO: We should iterate over the descriptors as they're read from the
- # socket rather than reading the whole thing into memory.
- #
- # https://trac.torproject.org/8248
+ # TODO: We should iterate over the descriptors as they're read from the
+ # socket rather than reading the whole thing into memory.
+ #
+ # https://trac.torproject.org/8248
- desc_content = self.get_info('ns/all', get_bytes = True)
+ desc_content = self.get_info('ns/all', get_bytes = True)
- desc_iterator = stem.descriptor.router_status_entry._parse_file(
- io.BytesIO(desc_content),
- True,
- entry_class = desc_class,
- )
+ desc_iterator = stem.descriptor.router_status_entry._parse_file(
+ io.BytesIO(desc_content),
+ True,
+ entry_class = desc_class,
+ )
- for desc in desc_iterator:
- yield desc
- except Exception as exc:
- if default == UNDEFINED:
- raise exc
- else:
- if default is not None:
- for entry in default:
- yield entry
+ for desc in desc_iterator:
+ yield desc
def get_conf(self, param, default = UNDEFINED, multiple = False):
"""
@@ -2042,6 +2014,7 @@ class Controller(BaseController):
else:
raise stem.ProtocolError('Returned unexpected status code: %s' % response.code)
+ @with_default()
def get_hidden_service_conf(self, default = UNDEFINED):
"""
This provides a mapping of hidden service directories to their
@@ -2084,11 +2057,7 @@ class Controller(BaseController):
(time.time() - start_time))
except stem.ControllerError as exc:
log.debug('GETCONF HiddenServiceOptions (failed: %s)' % exc)
-
- if default != UNDEFINED:
- return default
- else:
- raise exc
+ raise exc
service_dir_map = OrderedDict()
directory = None
@@ -2513,6 +2482,7 @@ class Controller(BaseController):
self._enabled_features += [entry.upper() for entry in features]
+ @with_default()
def get_circuit(self, circuit_id, default = UNDEFINED):
"""
Provides a circuit presently available from tor.
@@ -2529,18 +2499,13 @@ class Controller(BaseController):
An exception is only raised if we weren't provided a default response.
"""
- try:
- for circ in self.get_circuits():
- if circ.id == circuit_id:
- return circ
+ for circ in self.get_circuits():
+ if circ.id == circuit_id:
+ return circ
- raise ValueError("Tor presently does not have a circuit with the id of '%s'" % circuit_id)
- except Exception as exc:
- if default == UNDEFINED:
- raise exc
- else:
- return default
+ raise ValueError("Tor presently does not have a circuit with the id of '%s'" % circuit_id)
+ @with_default()
def get_circuits(self, default = UNDEFINED):
"""
Provides tor's currently available circuits.
@@ -2552,21 +2517,15 @@ class Controller(BaseController):
:raises: :class:`stem.ControllerError` if the call fails and no default was provided
"""
- try:
- circuits = []
- response = self.get_info('circuit-status')
+ circuits = []
+ response = self.get_info('circuit-status')
- for circ in response.splitlines():
- circ_message = stem.socket.recv_message(StringIO.StringIO('650 CIRC ' + circ + '\r\n'))
- stem.response.convert('EVENT', circ_message, arrived_at = 0)
- circuits.append(circ_message)
+ for circ in response.splitlines():
+ circ_message = stem.socket.recv_message(StringIO.StringIO('650 CIRC ' + circ + '\r\n'))
+ stem.response.convert('EVENT', circ_message, arrived_at = 0)
+ circuits.append(circ_message)
- return circuits
- except Exception as exc:
- if default == UNDEFINED:
- raise exc
- else:
- return default
+ return circuits
def new_circuit(self, path = None, purpose = 'general', await_build = False):
"""
@@ -2728,6 +2687,7 @@ class Controller(BaseController):
else:
raise stem.ProtocolError('CLOSECIRCUIT returned unexpected response code: %s' % response.code)
+ @with_default()
def get_streams(self, default = UNDEFINED):
"""
Provides the list of streams tor is currently handling.
@@ -2740,21 +2700,15 @@ class Controller(BaseController):
provided
"""
- try:
- streams = []
- response = self.get_info('stream-status')
+ streams = []
+ response = self.get_info('stream-status')
- for stream in response.splitlines():
- message = stem.socket.recv_message(StringIO.StringIO('650 STREAM ' + stream + '\r\n'))
- stem.response.convert('EVENT', message, arrived_at = 0)
- streams.append(message)
+ for stream in response.splitlines():
+ message = stem.socket.recv_message(StringIO.StringIO('650 STREAM ' + stream + '\r\n'))
+ stem.response.convert('EVENT', message, arrived_at = 0)
+ streams.append(message)
- return streams
- except Exception as exc:
- if default == UNDEFINED:
- raise exc
- else:
- return default
+ return streams
def attach_stream(self, stream_id, circuit_id, exiting_hop = None):
"""
@@ -2870,6 +2824,7 @@ class Controller(BaseController):
return max(0.0, self._last_newnym + 10 - time.time())
+ @with_default()
def get_effective_rate(self, default = UNDEFINED, burst = False):
"""
Provides the maximum rate this relay is configured to relay in bytes per
@@ -2896,18 +2851,12 @@ class Controller(BaseController):
value = None
for attr in attributes:
- try:
- attr_value = int(self.get_conf(attr))
+ attr_value = int(self.get_conf(attr))
- if attr_value == 0 and attr.startswith('Relay'):
- continue # RelayBandwidthRate and RelayBandwidthBurst default to zero
+ if attr_value == 0 and attr.startswith('Relay'):
+ continue # RelayBandwidthRate and RelayBandwidthBurst default to zero
- value = min(value, attr_value) if value else attr_value
- except stem.ControllerError as exc:
- if default == UNDEFINED:
- raise exc
- else:
- return default
+ value = min(value, attr_value) if value else attr_value
return value
@@ -3136,6 +3085,7 @@ def _parse_circ_entry(entry):
return (fingerprint, nickname)
+ at with_default()
def _case_insensitive_lookup(entries, key, default = UNDEFINED):
"""
Makes a case insensitive lookup within a list or dictionary, providing the
@@ -3160,7 +3110,4 @@ def _case_insensitive_lookup(entries, key, default = UNDEFINED):
if entry.lower() == key.lower():
return entry
- if default != UNDEFINED:
- return default
- else:
- raise ValueError("key '%s' doesn't exist in dict: %s" % (key, entries))
+ raise ValueError("key '%s' doesn't exist in dict: %s" % (key, entries))
diff --git a/stem/util/conf.py b/stem/util/conf.py
index c96aa88..aafb140 100644
--- a/stem/util/conf.py
+++ b/stem/util/conf.py
@@ -244,9 +244,9 @@ def get_config(handle):
def uses_settings(handle, path, lazy_load = True):
"""
- Provides a function that can be used as an annotation for other functions
- that require settings to be loaded. Functions with this annotation will be
- provided with the configuration as its 'config' keyword argument.
+ Provides a function that can be used as a decorator for other functions that
+ require settings to be loaded. Functions with this decorator will be provided
+ with the configuration as its 'config' keyword argument.
.. versionchanged:: 1.3.0
Omits the 'config' argument if the funcion we're decorating doesn't accept
@@ -262,14 +262,14 @@ def uses_settings(handle, path, lazy_load = True):
:param str handle: hande for the configuration
:param str path: path where the configuration should be loaded from
- :param bool lazy_load: loads the configuration file when the annotation is
+ :param bool lazy_load: loads the configuration file when the decorator is
used if true, otherwise it's loaded right away
- :returns: **function** that can be used as an annotation to provide the
+ :returns: **function** that can be used as a decorator to provide the
configuration
:raises: **IOError** if we fail to read the configuration file, if
- **lazy_load** is true then this arises when we use the annotation
+ **lazy_load** is true then this arises when we use the decorator
"""
config = get_config(handle)
@@ -278,20 +278,20 @@ def uses_settings(handle, path, lazy_load = True):
config.load(path)
config.set('settings_loaded', 'true')
- def annotation(func):
+ def decorator(func):
def wrapped(*args, **kwargs):
if lazy_load and not config.get('settings_loaded', False):
config.load(path)
config.set('settings_loaded', 'true')
- if 'config' in inspect.getargspec(func)[0]:
+ if 'config' in inspect.getargspec(func).args:
return func(*args, config = config, **kwargs)
else:
return func(*args, **kwargs)
return wrapped
- return annotation
+ return decorator
def parse_enum(key, value, enumeration):
More information about the tor-commits
mailing list