[tor-commits] [stem/master] Revised API docs for stem.util.conf

atagar at torproject.org atagar at torproject.org
Sun Oct 28 20:56:34 UTC 2012


commit a30d89064f857e355019dbd70a37997abe475699
Author: Damian Johnson <atagar at 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





More information about the tor-commits mailing list