Configuration File Handling
***************************

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…

   # This is my sample config
   user.name Galen
   user.password yabba1234 # here's an inline comment
   user.notes takes a fancy to pepperjack cheese
   blankEntry.example

   msg.greeting
   |Multi-line message exclaiming of the
   |wonder and awe that is pepperjack!

… would be loaded as…

   config = {
     'user.name': 'Galen',
     'user.password': 'yabba1234',
     'user.notes': 'takes a fancy to pepperjack cheese',
     'blankEntry.example': '',
     'msg.greeting': 'Multi-line message exclaiming of the\nwonder and awe that is pepperjack!',
   }

Configurations are managed via the "Config" class. The "Config" can be
be used directly with its "get()" and "set()" methods, but usually
modules will want a local dictionary with just the configurations that
it cares about.

To do this use the "config_dict()" function. For example…

   import getpass
   from stem.util import conf, connection

   def config_validator(key, value):
     if key == 'timeout':
       # require at least a one second timeout
       return max(1, value)
     elif key == 'endpoint':
       if not connection.is_valid_ipv4_address(value):
         raise ValueError("'%s' isn't a valid IPv4 address" % value)
     elif key == 'port':
       if not connection.is_valid_port(value):
         raise ValueError("'%s' isn't a valid port" % value)
     elif key == 'retries':
       # negative retries really don't make sense
       return max(0, value)

   CONFIG = conf.config_dict('ssh_login', {
     'username': getpass.getuser(),
     'password': '',
     'timeout': 10,
     'endpoint': '263.12.8.0',
     'port': 22,
     'reconnect': False,
     'retries': 3,
   }, config_validator)

There’s several things going on here so lets take it step by step…

* The "config_dict()" provides a dictionary that’s bound to a given
  configuration. If the “ssh_proxy_config” configuration changes then
  so will the contents of CONFIG.

* The dictionary we’re passing to "config_dict()" provides two
  important pieces of information: default values and their types. See
  the Config’s "get()" method for how these type inferences work.

* The config_validator is a hook we’re adding to make sure CONFIG
  only gets values we think are valid. In this case it ensures that
  our timeout value is at least one second, and rejects endpoints or
  ports that are invalid.

Now lets say our user has the following configuration file…

   username waddle_doo
   password jabberwocky
   timeout -15
   port 9000000
   retries lots
   reconnect true
   logging debug

… and we load it as follows…

   >>> from stem.util import conf
   >>> our_config = conf.get_config('ssh_login')
   >>> our_config.load('/home/atagar/user_config')
   >>> print CONFIG  # doctest: +SKIP
   {
     "username": "waddle_doo",
     "password": "jabberwocky",
     "timeout": 1,
     "endpoint": "263.12.8.0",
     "port": 22,
     "reconnect": True,
     "retries": 3,
   }

Here’s an expanation of what happened…

* the username, password, and reconnect attributes took the values
  in the configuration file

* the ‘config_validator’ we added earlier allows for a minimum
  timeout of one and rejected the invalid port (with a log message)

* we weren’t able to convert the retries’ “lots” value to an integer
  so it kept its default value and logged a warning

* the user didn’t supply an endpoint so that remained unchanged

* our CONFIG didn’t have a ‘logging’ attribute so it was ignored

**Module Overview:**

   config_dict - provides a dictionary that's kept in sync with our config
   get_config - singleton for getting configurations
   uses_settings - provides an annotation for functions that use configurations
   parse_enum_csv - helper funcion for parsing confguration entries for enums

   Config - Custom configuration
     |- load - reads a configuration file
     |- save - writes the current configuration to a file
     |- clear - empties our loaded configuration contents
     |- add_listener - notifies the given listener when an update occurs
     |- clear_listeners - removes any attached listeners
     |- keys - provides keys in the loaded configuration
     |- set - sets the given key/value pair
     |- unused_keys - provides keys that have never been requested
     |- get - provides the value for a given key, with type inference
     +- get_value - provides the value for a given key as a string

stem.util.conf.config_dict(handle, conf_mappings, handler=None)

   Makes a dictionary that stays synchronized with a configuration.

   This takes a dictionary of ‘config_key => default_value’ mappings
   and changes the values to reflect our current configuration. This
   will leave the previous values alone if…

   * we don’t have a value for that config_key

   * we can’t convert our value to be the same type as the
     default_value

   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 (key, value) for the new values and return
   what we should actually insert into the dictionary. If this returns
   None then the value is updated as normal.

   For more information about how we convert types see our "get()"
   method.

   **The dictionary you get from this is manged by the Config class
   and should be treated as being read-only.**

   Parameters:
      * **handle** (*str*) – unique identifier for a config instance

      * **conf_mappings** (*dict*) – config key/value mappings used
        as our defaults

      * **handler** (*functor*) – function referred to prior to
        assigning values

stem.util.conf.get_config(handle)

   Singleton constructor for configuration file instances. If a
   configuration already exists for the handle then it’s returned.
   Otherwise a fresh instance is constructed.

   Parameters:
      **handle** (*str*) – unique identifier used to access this
      config instance

stem.util.conf.uses_settings(handle, path, lazy_load=True)

   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.

   Changed in version 1.3.0: Omits the ‘config’ argument if the
   funcion we’re decorating doesn’t accept it.

      uses_settings = stem.util.conf.uses_settings('my_app', '/path/to/settings.cfg')

      @uses_settings
      def my_function(config):
        print 'hello %s!' % config.get('username', '')

   Parameters:
      * **handle** (*str*) – hande for the configuration

      * **path** (*str*) – path where the configuration should be
        loaded from

      * **lazy_load** (*bool*) – loads the configuration file when
        the decorator is used if true, otherwise it’s loaded right
        away

   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 decorator

stem.util.conf.parse_enum(key, value, enumeration)

   Provides the enumeration value for a given key. This is a case
   insensitive lookup and raises an exception if the enum key doesn’t
   exist.

   Parameters:
      * **key** (*str*) – configuration key being looked up

      * **value** (*str*) – value to be parsed

      * **enumeration** (*stem.util.enum.Enum*) – enumeration the
        values should be in

   Returns:
      enumeration value

   Raises:
      **ValueError** if the **value** isn’t among the enumeration keys

stem.util.conf.parse_enum_csv(key, value, enumeration, count=None)

   Parses a given value as being a comma separated listing of
   enumeration keys, returning the corresponding enumeration values.
   This is intended to be a helper for config handlers. The checks
   this does are case insensitive.

   The **count** attribute can be used to make assertions based on the
   number of values. This can be…

   * None to indicate that there’s no restrictions.

   * An int to indicate that we should have this many values.

   * An (int, int) tuple to indicate the range that values can be
     in. This range is inclusive and either can be None to indicate
     the lack of a lower or upper bound.

   Parameters:
      * **key** (*str*) – configuration key being looked up

      * **value** (*str*) – value to be parsed

      * **enumeration** (*stem.util.enum.Enum*) – enumeration the
        values should be in

      * **count** (*int**,**tuple*) – validates that we have this
        many items

   Returns:
      list with the enumeration values

   Raises:
      **ValueError** if the count assertion fails or the **value**
      entries don’t match the enumeration keys

class stem.util.conf.Config

   Bases: "object"

   Handler for easily working with custom configurations, providing
   persistence to and from files. All operations are thread safe.

   **Example usage:**

   User has a file at ‘/home/atagar/myConfig’ with…

      destination.ip 1.2.3.4
      destination.port blarg

      startup.run export PATH=$PATH:~/bin
      startup.run alias l=ls

   And they have a script with…

      from stem.util import conf

      # Configuration values we'll use in this file. These are mappings of
      # configuration keys to the default values we'll use if the user doesn't
      # have something different in their config file (or it doesn't match this
      # type).

      ssh_config = conf.config_dict('ssh_login', {
        'login.user': 'atagar',
        'login.password': 'pepperjack_is_awesome!',
        'destination.ip': '127.0.0.1',
        'destination.port': 22,
        'startup.run': [],
      })

      # Makes an empty config instance with the handle of 'ssh_login'. This is
      # a singleton so other classes can fetch this same configuration from
      # this handle.

      user_config = conf.get_config('ssh_login')

      # Loads the user's configuration file, warning if this fails.

      try:
        user_config.load("/home/atagar/myConfig")
      except IOError as exc:
        print "Unable to load the user's config: %s" % exc

      # This replace 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
      #   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
      #
      # * 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',
      #  'destination.port': 22,
      #  'startup.run': ['export PATH=$PATH:~/bin', 'alias l=ls']}
      #
      # Information for what values fail to load and why are reported to
      # 'stem.util.log'.

      .. versionchanged:: 1.7.0
         Class can now be used as a dictionary.

   load(path=None, commenting=True)

      Reads in the contents of the given path, adding its
      configuration values to our current contents. If the path is a
      directory then this loads each of the files, recursively.

      Changed in version 1.3.0: Added support for directories.

      Changed in version 1.3.0: Added the **commenting** argument.

      Changed in version 1.6.0: Avoid loading vim swap files.

      Parameters:
         * **path** (*str*) – file or directory path to be loaded,
           this uses the last loaded path if not provided

         * **commenting** (*bool*) – ignore line content after a ‘#’
           if **True**, read otherwise

      Raises:
         * **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

   save(path=None)

      Saves configuration contents to disk. If a path is provided then
      it replaces the configuration location that we track.

      Parameters:
         **path** (*str*) – location to be saved to

      Raises:
         * **IOError** if we fail to save the file (insufficient
           permissions, etc)

         * **ValueError** if no path was provided and we’ve never
           been provided one

   clear()

      Drops the configuration contents and reverts back to a blank,
      unloaded state.

   add_listener(listener, backfill=True)

      Registers the function to be notified of configuration updates.
      Listeners are expected to be functors which accept (config,
      key).

      Parameters:
         * **listener** (*functor*) – function to be notified when
           our configuration is changed

         * **backfill** (*bool*) – calls the function with our
           current values if **True**

   clear_listeners()

      Removes all attached listeners.

   keys()

      Provides all keys in the currently loaded configuration.

      Returns:
         **list** if strings for the configuration keys we’ve loaded

   unused_keys()

      Provides the configuration keys that have never been provided to
      a caller via "config_dict()" or the "get()" and "get_value()"
      methods.

      Returns:
         **set** of configuration keys we’ve loaded but have never
         been requested

   set(key, value, overwrite=True)

      Appends the given key/value configuration mapping, behaving the
      same as if we’d loaded this from a configuration file.

      Changed in version 1.5.0: Allow removal of values by overwriting
      with a **None** value.

      Parameters:
         * **key** (*str*) – key for the configuration mapping

         * **value** (*str**,**list*) – value we’re setting the
           mapping to

         * **overwrite** (*bool*) – replaces the previous value if
           **True**, otherwise the values are appended

   get(key, default=None)

      Fetches the given configuration, using the key and default value
      to determine the type it should be. Recognized inferences are:

      * **default is a boolean => boolean**

        * values are case insensitive

        * provides the default if the value isn’t “true” or “false”

      * **default is an integer => int**

        * provides the default if the value can’t be converted to an
          int

      * **default is a float => float**

        * provides the default if the value can’t be converted to a
          float

      * **default is a list => list**

        * string contents for all configuration values with this key

      * **default is a tuple => tuple**

        * string contents for all configuration values with this key

      * **default is a dictionary => dict**

        * values without “=>” in them are ignored

        * values are split into key/value pairs on “=>” with extra
          whitespace stripped

      Parameters:
         * **key** (*str*) – config setting to be fetched

         * **object** (*default*) – value provided if no such key
           exists or fails to be converted

      Returns:
         given configuration value with its type inferred with the
         above rules

   get_value(key, default=None, multiple=False)

      This provides the current value associated with a given key.

      Parameters:
         * **key** (*str*) – config setting to be fetched

         * **default** (*object*) – value provided if no such key
           exists

         * **multiple** (*bool*) – provides back a list of all
           values if **True**, otherwise this returns the last loaded
           configuration value

      Returns:
         **str** or **list** of string configuration values associated
         with the given key, providing the default if no such key
         exists
