commit a30d89064f857e355019dbd70a37997abe475699
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sat Oct 27 14:02:09 2012 -0700
Revised API docs for stem.util.conf
The utility's header was pretty sucky so rewrote it to better exemplify how
this module should be used.
---
docs/api.rst | 1 +
docs/contents.rst | 1 +
docs/util/conf.rst | 5 +
stem/descriptor/networkstatus.py | 10 +-
stem/descriptor/router_status_entry.py | 8 +-
stem/exit_policy.py | 2 +-
stem/util/conf.py | 220 ++++++++++++++++++++++----------
stem/util/enum.py | 4 +-
8 files changed, 172 insertions(+), 79 deletions(-)
diff --git a/docs/api.rst b/docs/api.rst
index bb0d293..f92a84a 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -22,5 +22,6 @@ Descriptors
Utilities
---------
+* `stem.util.conf <util/conf.html>`_ - Configuration file handling.
* `stem.util.enum <util/enum.html>`_ - Enumeration class.
diff --git a/docs/contents.rst b/docs/contents.rst
index 52cb6f4..90b1af6 100644
--- a/docs/contents.rst
+++ b/docs/contents.rst
@@ -20,5 +20,6 @@ Contents
types/exit_policy
types/version
+ util/conf
util/enum
diff --git a/docs/util/conf.rst b/docs/util/conf.rst
new file mode 100644
index 0000000..15ce123
--- /dev/null
+++ b/docs/util/conf.rst
@@ -0,0 +1,5 @@
+Configuration File Handling
+===========================
+
+.. automodule:: stem.util.conf
+
diff --git a/stem/descriptor/networkstatus.py b/stem/descriptor/networkstatus.py
index d78a2c6..a3eed1f 100644
--- a/stem/descriptor/networkstatus.py
+++ b/stem/descriptor/networkstatus.py
@@ -60,12 +60,12 @@ memory usage and upfront runtime.
parse_file - parses a network status file, providing an iterator for its routers
- NetworkStatusDocument - Network status document.
- |- NetworkStatusDocumentV2 - Version 2 network status document.
- +- NetworkStatusDocumentV3 - Version 3 network status document.
+ NetworkStatusDocument - Network status document
+ |- NetworkStatusDocumentV2 - Version 2 network status document
+ +- NetworkStatusDocumentV3 - Version 3 network status document
- DocumentSignature - Signature of a document by a directory authority.
- DirectoryAuthority - Directory authority as defined in a v3 network status document.
+ DocumentSignature - Signature of a document by a directory authority
+ DirectoryAuthority - Directory authority as defined in a v3 network status document
"""
import datetime
diff --git a/stem/descriptor/router_status_entry.py b/stem/descriptor/router_status_entry.py
index df0562c..dfa26fe 100644
--- a/stem/descriptor/router_status_entry.py
+++ b/stem/descriptor/router_status_entry.py
@@ -10,10 +10,10 @@ sources...
::
- RouterStatusEntry - Common parent for router status entries.
- |- RouterStatusEntryV2 - Entry for a network status v2 document.
- |- RouterStatusEntryV3 - Entry for a network status v3 document.
- +- RouterStatusEntryMicroV3 - Entry for a microdescriptor flavored v3 document.
+ RouterStatusEntry - Common parent for router status entries
+ |- RouterStatusEntryV2 - Entry for a network status v2 document
+ |- RouterStatusEntryV3 - Entry for a network status v3 document
+ +- RouterStatusEntryMicroV3 - Entry for a microdescriptor flavored v3 document
"""
import base64
diff --git a/stem/exit_policy.py b/stem/exit_policy.py
index 00133d7..dc50c6e 100644
--- a/stem/exit_policy.py
+++ b/stem/exit_policy.py
@@ -35,7 +35,7 @@ exiting to a destination is permissable or not. For instance...
|- is_port_wildcard - checks if we'll accept any port
|- is_match - checks if we match a given destination
+- __str__ - string representation for this rule
-
+
AddressType - Enumerations for IP address types that can be in an exit policy
|- WILDCARD - any address of either IPv4 or IPv6
|- IPv4 - IPv4 address
diff --git a/stem/util/conf.py b/stem/util/conf.py
index 9781370..7bf157a 100644
--- a/stem/util/conf.py
+++ b/stem/util/conf.py
@@ -1,8 +1,15 @@
"""
-This provides handlers for specially formatted configuration files. Entries are
-expected to consist of simple key/value pairs, and anything after "#" is
-stripped as a comment. Excess whitespace is trimmed and empty lines are
-ignored. For instance:
+Handlers for text configuration files. Configurations are simple string to
+string mappings, with the configuration files using the following rules...
+
+* the key/value is separated by a space
+* anything after a "#" is ignored as a comment
+* excess whitespace is trimmed
+* empty lines are ignored
+* multi-line values can be defined by following the key with lines starting
+ with a '|'
+
+For instance...
::
@@ -11,44 +18,108 @@ ignored. For instance:
user.password yabba1234 # here's an inline comment
user.notes takes a fancy to pepperjack chese
blankEntry.example
+
+ msg.greeting
+ |Multi-line message exclaiming of the
+ |wonder and awe that is pepperjack!
-would be loaded as four entries, the last one's value being an empty string.
-Mulit-line entries can be defined my providing an entry followed by lines with
-a '|' prefix. For instance...
+... would be loaded as...
::
- msg.greeting
- |This is a multi-line message
- |exclaiming about the wonders
- |and awe that is pepperjack!
+ config = {
+ "user.name": "Galen",
+ "user.password": "yabba1234",
+ "user.notes": "takes a fancy to pepperjack chese",
+ "blankEntry.example": "",
+ "msg.greeting": "Multi-line message exclaiming of the\\nwonder and awe that is pepperjack!",
+ }
-The Config class acts as a central store for configuration values. Users of
-this store have their own dictionaries of config key/value pairs that provide
-three things...
+Configurations are loaded or saved via the :class:`~stem.util.conf.Config`
+class. The :class:`~stem.util.conf.Config` can be be used directly with its
+:func:`~stem.util.conf.Config.get` and :func:`~stem.util.conf.Config.set`
+methods, but usually modules will want a local dictionary with just the
+configurations that it cares about. This can be done a couple ways...
- 1. Default values for the configuration keys in case they're either undefined
- or of the wrong type.
- 2. Types that we should attempt to cast the configuration values to.
- 3. An easily accessable container for getting the config values.
+* **Independent Dictionary**
-There are many ways of using the Config class but the most common ones are...
+To simply get a dictionary that has configurations use the
+:func:`~stem.util.conf.Config.synchronize` method. This takes as an argument
+a dictionary with the default values.
-* Call config_dict to get a dictionary that's always synced with a Config.
+For instance, lets say that you had a file at '/home/atagar/user_config'
+with...
-* Make a dictionary and call synchronize() to bring it into sync with the
- Config. This does not keep it in sync as the Config changes. See the Config
- class' pydocs for an example.
+::
+
+ username waddle_doo
+ permissions.ssh true
+ some.extra.stuff foobar
-* Just call the Config's get() or get_value() methods directly.
+... then run...
+
+::
+
+ >>> from stem.util.conf import get_config
+ >>> my_module_config = {
+ ... "username": "default",
+ ... "permissions.ssh": False,
+ ... "permissions.sudo": False,
+ ... }
+
+ >>> config = get_config("user_config")
+ >>> config.load("/home/atagar/user_config")
+ >>> config.synchronize(my_module_config)
+
+ >>> print my_module_config
+ {'username': 'waddle_doo', 'permissions.sudo': False, 'permissions.ssh': True}
+
+The configuration file just contains string mappings, but the
+:class:`~stem.util.conf.Config` attempts to convert those values to be the same
+types as the dictionary's defaults. For more information on how type inferences
+work see the :func:`~stem.util.conf.Config.get` method.
+
+* **Linked Dictionary**
+
+Alternatively you can get a read-only dictionary that stays in sync with the
+:class:`~stem.util.conf.Config` by using the
+:func:`~stem.util.conf.config_dict` function...
+
+::
+
+ >>> # Make a dictionary that watches the 'user_config' configuration for changes.
+
+ >>> from stem.util.conf import config_dict
+ >>> my_module_config = config_dict("user_config", {
+ ... "username": "default",
+ ... "permissions.ssh": False,
+ ... "permissions.sudo": False,
+ ... })
+ >>> print my_module_config
+ {'username': 'default', 'permissions.sudo': False, 'permissions.ssh': False}
+
+ >>> # Something (maybe another module) loads the config, causing the
+ >>> # my_module_config above to be updated.
+
+ >>> from stem.util.conf import get_config
+ >>> config = get_config("user_config")
+ >>> config.load("/home/atagar/user_config")
+
+ >>> print my_module_config
+ {'username': 'waddle_doo', 'permissions.sudo': False, 'permissions.ssh': True}
+
+ >>> config.set('username', 'ness')
+ >>> print my_module_config
+ {'username': 'ness', 'permissions.sudo': False, 'permissions.ssh': True}
**Module Overview:**
::
config_dict - provides a dictionary that's kept synchronized with a config
- get_config - Singleton for getting configurations
- Config - Custom configuration.
+ get_config - singleton for getting configurations
+
+ Config - Custom configuration
|- load - reads a configuration file
|- save - writes the current configuration to a file
|- clear - empties our loaded configuration contents
@@ -72,7 +143,7 @@ import stem.util.log as log
CONFS = {} # mapping of identifier to singleton instances of configs
-class SyncListener(object):
+class _SyncListener(object):
def __init__(self, config_dict, interceptor):
self.config_dict = config_dict
self.interceptor = interceptor
@@ -91,8 +162,9 @@ class SyncListener(object):
def config_dict(handle, conf_mappings, handler = None):
"""
Makes a dictionary that stays synchronized with a configuration. The process
- for staying in sync is similar to the Config class' synchronize() method,
- only changing the dictionary's values if we're able to cast to the same type.
+ for staying in sync is similar to the :class:`~stem.util.conf.Config` class'
+ :func:`~stem.util.conf.Config.synchronize` method, only changing the
+ dictionary's values if we're able to cast to the same type.
If a handler is provided then this is called just prior to assigning new
values to the config_dict. The handler function is expected to accept the
@@ -106,7 +178,7 @@ def config_dict(handle, conf_mappings, handler = None):
"""
selected_config = get_config(handle)
- selected_config.add_listener(SyncListener(conf_mappings, handler).update)
+ selected_config.add_listener(_SyncListener(conf_mappings, handler).update)
return conf_mappings
def get_config(handle):
@@ -170,17 +242,22 @@ class Config(object):
# Replaces the contents of ssh_config with the values from the user's
# config file if...
- # - the key is present in the config file
- # - we're able to convert the configuration file's value to the same type
+ #
+ # * the key is present in the config file
+ # * we're able to convert the configuration file's value to the same type
# as what's in the mapping (see the Config.get() method for how these
# type inferences work)
#
- # For instance in this case the login values are left alone (because they
- # aren't in the user's config file), and the 'destination.port' is also
- # left with the value of 22 because we can't turn "blarg" into an
- # integer.
+ # For instance in this case...
+ #
+ # * the login values are left alone because they aren't in the user's
+ # config file
+ #
+ # * the 'destination.port' is also left with the value of 22 because we
+ # can't turn "blarg" into an integer
#
# The other values are replaced, so ssh_config now becomes...
+ #
# {"login.user": "atagar",
# "login.password": "pepperjack_is_awesome!",
# "destination.ip": "1.2.3.4",
@@ -194,10 +271,6 @@ class Config(object):
"""
def __init__(self):
- """
- Creates a new configuration instance.
- """
-
self._path = None # location we last loaded from or saved to
self._contents = {} # configuration key/value pairs
self._raw_contents = [] # raw contents read from configuration file
@@ -214,11 +287,13 @@ class Config(object):
Reads in the contents of the given path, adding its configuration values
to our current contents.
- :param str path: file path to be loaded
+ :param str path: file path to be loaded, this uses the last loaded path if
+ not provided
:raises:
- * IOError if we fail to read the file (it doesn't exist, insufficient permissions, etc)
- * ValueError if we don't have a default path and none was provided
+ * **IOError** if we fail to read the file (it doesn't exist, insufficient
+ permissions, etc)
+ * **ValueError** if no path was provided and we've never been provided one
"""
if path:
@@ -267,12 +342,12 @@ class Config(object):
def save(self, path = None):
"""
- Saves configuration contents to the config file or to the path
- specified. If a path is provided then it replaces the configuration
- location that we track.
+ Saves configuration contents to disk. If a path is provided then it
+ replaces the configuration location that we track.
:param str path: location to be saved to
- :raises: ValueError if we don't have a default path and none was provided
+
+ :raises: **ValueError** if no path was provided and we've never been provided one
"""
if path:
@@ -311,10 +386,11 @@ class Config(object):
* we can't convert our value to be the same type as the default_value
For more information about how we convert types see our
- :func:`stem.util.conf.Config.get` method.
+ :func:`~stem.util.conf.Config.get` method.
:param dict conf_mappings: configuration key/value mappings to be revised
- :param dict limits: mappings of limits on numeric values, expected to be of the form "configKey -> min" or "configKey -> (min, max)"
+ :param dict limits: mappings of limits on numeric values, expected to be of
+ the form "configKey -> min" or "configKey -> (min, max)"
"""
if limits is None: limits = {}
@@ -338,11 +414,11 @@ class Config(object):
def add_listener(self, listener, backfill = True):
"""
- Registers the given function to be notified of configuration updates.
- Listeners are expected to be functors which accept (config, key).
+ Registers the function to be notified of configuration updates. Listeners
+ are expected to be functors which accept (config, key).
:param functor listener: function to be notified when our configuration is changed
- :param bool backfill: calls the function with our current values if true
+ :param bool backfill: calls the function with our current values if **True**
"""
with self._contents_lock:
@@ -354,7 +430,7 @@ class Config(object):
def clear_listeners(self):
"""
- Removes any attached listeners.
+ Removes all attached listeners.
"""
self._listeners = []
@@ -363,7 +439,7 @@ class Config(object):
"""
Provides all keys in the currently loaded configuration.
- :returns: list if strings for the configuration keys we've loaded
+ :returns: **list** if strings for the configuration keys we've loaded
"""
return self._contents.keys()
@@ -371,9 +447,11 @@ class Config(object):
def unused_keys(self):
"""
Provides the configuration keys that have never been provided to a caller
- via the get, get_value, or synchronize methods.
+ via the :func:`~stem.util.conf.Config.get`,
+ :func:`~stem.util.conf.Config.get_value`, or
+ :func:`~stem.util.conf.Config.synchronize` methods.
- :returns: set of configuration keys we've loaded but have never been requested
+ :returns: **set** of configuration keys we've loaded but have never been requested
"""
return set(self.keys()).difference(self._requested_keys)
@@ -385,7 +463,8 @@ class Config(object):
:param str key: key for the configuration mapping
:param str,list value: value we're setting the mapping to
- :param bool overwrite: replaces the previous value if true, otherwise the values are appended
+ :param bool overwrite: replaces the previous value if **True**, otherwise
+ the values are appended
"""
with self._contents_lock:
@@ -483,9 +562,11 @@ class Config(object):
:param str key: config setting to be fetched
:param object default: value provided if no such key exists
- :param bool multiple: provides back a list of all values if true, otherwise this returns the last loaded configuration value
+ :param bool multiple: provides back a list of all values if **True**,
+ otherwise this returns the last loaded configuration value
- :returns: string or list of string configuration values associated with the given key, providing the default if no such key exists
+ :returns: **str** or **list** of string configuration values associated
+ with the given key, providing the default if no such key exists
"""
with self._contents_lock:
@@ -506,11 +587,14 @@ class Config(object):
Fetches the given key as a comma separated value.
:param str key: config setting to be fetched, last if multiple exists
- :param object default: value provided if no such key exists or doesn't match the count
- :param int count: if set then the default is returned when the number of elements doesn't match this value
- :param str sub_key: handle the configuration entry as a dictionary and use this key within it
-
- :returns: list with the stripped values
+ :param object default: value provided if no such key exists or doesn't
+ match the count
+ :param int count: if set then the default is returned when the number of
+ elements doesn't match this value
+ :param str sub_key: handle the configuration entry as a dictionary and use
+ this key within it
+
+ :returns: **list** with the stripped values
"""
if sub_key: conf_value = self.get(key, {}).get(sub_key)
@@ -539,13 +623,15 @@ class Config(object):
values aren't integers or don't follow the given constraints.
:param str key: config setting to be fetched, last if multiple exists
- :param object default: value provided if no such key exists, doesn't match the count, values aren't all integers, or doesn't match the bounds
+ :param object default: value provided if no such key exists, doesn't match
+ the count, values aren't all integers, or doesn't match the bounds
:param int count: checks that the number of values matches this if set
:param int min_value: checks that all values are over this if set
:param int max_value: checks that all values are under this if set
- :param str sub_key: handle the configuration entry as a dictionary and use this key within it
+ :param str sub_key: handle the configuration entry as a dictionary and use
+ this key within it
- :returns: list with the stripped values
+ :returns: **list** with the stripped values
"""
conf_comp = self.get_str_csv(key, default, count, sub_key)
diff --git a/stem/util/enum.py b/stem/util/enum.py
index 9941de4..42ca7ea 100644
--- a/stem/util/enum.py
+++ b/stem/util/enum.py
@@ -26,9 +26,9 @@ constructed as simple type listings...
::
- UppercaseEnum - Provides an enum instance with capitalized values.
+ UppercaseEnum - Provides an enum instance with capitalized values
- Enum - Provides a basic, ordered enumeration.
+ Enum - Provides a basic, ordered enumeration
|- keys - string representation of our enum keys
|- index_of - indice of an enum value
|- next - provides the enum after a given enum value