API Usage Examples
===================

Here are some code examples of API usage.

Loading single config file
----------------------------

To load single config file:

.. code-block:: python

  import anyconfig

  # Config type (format) is automatically detected by filename (file
  # extension).
  data1 = anyconfig.load("/path/to/foo/conf.d/a.yml")

  # Loaded config data is a dict-like object.
  # examples:
  # data1["a"] => 1
  # data1["b"]["b1"] => "xyz"
  # data1["c"]["c1"]["c13"] => [1, 2, 3]

  # Same as above but I recommend to use the former.
  data2 = anyconfig.single_load("/path/to/foo/conf.d/a.yml")

  # Or you can specify config type explicitly as needed.
  cnf_path = "/path/to/foo/conf.d/b.conf"
  data3 = anyconfig.load(cnf_path, "yaml")

  # Same as above but ...
  data4 = anyconfig.single_load(cnf_path, "yaml")

  # Same as above as a result but make parser instance and pass it explicitly.
  yml_psr = anyconfig.find_loader(None, "yaml")
  data5 = anyconfig.single_load(cnf_path, yml_psr)  # Or: anyconfig.load(...)

You can pass backend (config loader) specific optional parameters to
these load and dump functions as needed:

.. code-block:: python

  # from python -c "import json; help(json.load)":
  # Help on function load in module json:
  #
  # load(fp, encoding=None, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)
  #    Deserialize ``fp`` (a ``.read()``-supporting file-like object containing
  #    a JSON document) to a Python object.
  #    ...
  data6 = anyconfig.load("foo.json", parse_float=None)

Also it's possible:

- to load a config which is actually a Jinja2 [#]_ template file, the file will be rendered before load. See `Template config support`_ section for more details.
- to validate a config file with a JSON schema [#]_ before load. See `Validation with JSON Schema`_ section for more details.

.. note:: The returned object is an instance of
   anyconfig.mergeabledict.MergeableDict class by default, to support recursive
   merge operations needed when loading multiple config files.

.. [#] http://jinja.pocoo.org
.. [#] http://json-schema.org

Loading multiple config files
-------------------------------

To load multiple config files:

.. code-block:: python

  import anyconfig

  # Specify config files by list of paths:
  data1 = anyconfig.load(["/etc/foo.d/a.json", "/etc/foo.d/b.json"])

  # Similar to the above but all or one of config files are missing:
  data2 = anyconfig.load(["/etc/foo.d/a.json", "/etc/foo.d/b.json"],
                         ignore_missing=True)

  # Specify config files by glob path pattern:
  cnf_path = "/etc/foo.d/*.json"
  data3 = anyconfig.load(cnf_path)

  # Similar to above but make parser instance and pass it explicitly.
  psr = anyconfig.find_loader(cnf_path)
  data4 = anyconfig.load(cnf_path, psr)

  # Similar to the above but parameters in the former config file will be simply
  # overwritten by the later ones:
  data5 = anyconfig.load("/etc/foo.d/*.json", ac_merge=anyconfig.MS_REPLACE)

On loading multiple config files, you can choose 'strategy' to merge
configurations from the followings:

* anyconfig.MS_REPLACE: Replace all configuration parameter values provided in
  former config files are simply replaced w/ the ones in later config files.

  For example, if a.yml and b.yml are like followings:

  a.yml:


  .. code-block:: yaml

    a: 1
    b:
       - c: 0
       - c: 2
    d:
       e: "aaa"
       f: 3

  b.yml:

  .. code-block:: yaml

    b:
       - c: 3
    d:
       e: "bbb"

  then:

  .. code-block:: python

    load(["a.yml", "b.yml"], ac_merge=anyconfig.MS_REPLACE)

  will give object such like:
  
  .. code-block:: python

    {'a': 1, 'b': [{'c': 3}], 'd': {'e': "bbb"}}

* anyconfig.MS_NO_REPLACE: Do not replace configuration parameter values
  provided in former config files.

  For example, if a.yml and b.yml are like followings:

  a.yml:
  
  .. code-block:: yaml

    b:
       - c: 0
       - c: 2
    d:
       e: "aaa"
       f: 3

  b.yml:
  
  .. code-block:: yaml

    a: 1
    b:
       - c: 3
    d:
       e: "bbb"

  then:
  
  .. code-block:: python

    load(["a.yml", "b.yml"], ac_merge=anyconfig.MS_NO_REPLACE)

  will give object such like:

  .. code-block:: python

    {'a': 1, 'b': [{'c': 0}, {'c': 2}], 'd': {'e': "bbb", 'f': 3}}

* anyconfig.MS_DICTS (default): Merge dicts recursively. That is, the following:

  .. code-block:: python

    load(["a.yml", "b.yml"], ac_merge=anyconfig.MS_DICTS)

  will give object such like:

  .. code-block:: python

    {'a': 1, 'b': [{'c': 3}], 'd': {'e': "bbb", 'f': 3}}

  This is the merge strategy choosen by default.

* anyconfig.MS_DICTS_AND_LISTS: Merge dicts and lists recursively. That is, the
  following:

  .. code-block:: python
 
    load(["a.yml", "b.yml"], ac_merge=anyconfig.MS_DICTS_AND_LISTS)

  will give object such like:

  .. code-block:: python

    {'a': 1, 'b': [{'c': 0}, {'c': 2}, {'c': 3}], 'd': {'e': "bbb", 'f': 3}}

Validation with JSON Schema
-------------------------------

If you have jsonschema [#]_ installed, you can validate config files with using
anyconfig.validate() since 0.0.10.

.. code-block:: python

  # Validate a JSON config file (conf.json) with JSON schema (schema.json).
  # If validatation suceeds, `rc` -> True, `err` -> ''.
  conf1 = anyconfig.load("/path/to/conf.json")
  schema1 = anyconfig.load("/path/to/schema.json")
  (rc, err) = anyconfig.validate(conf1, schema1)

  # Similar to the above but both config and schema files are in YAML.
  conf2 = anyconfig.load("/path/to/conf.yml")
  schema2 = anyconfig.load("/path/to/schema.yml")
  (rc, err) = anyconfig.validate(conf2, schema2)

It's also possible to validate config files during load:

.. code-block:: python

  # Validate a config file (conf.yml) with JSON schema (schema.yml) while
  # loading the config file.
  conf1 = anyconfig.load("/a/b/c/conf.yml", ac_schema="/c/d/e/schema.yml")

  # Validate config loaded from multiple config files with JSON schema
  # (schema.json) while loading them.
  conf2 = anyconfig.load("conf.d/*.yml", ac_schema="/c/d/e/schema.json")

.. [#] https://pypi.python.org/pypi/jsonschema

Template config support
---------------------------

anyconfig module supports template config files since 0.0.6.
That is, config files written in Jinja2 template [#]_ will be compiled before
loading w/ backend module.

.. note:: Template config support is disabled by default to avoid side effects when processing config files of jinja2 template or having some expressions similar to jinaj2 template syntax.

Anyway, a picture is worth a thousand words. Here is an example of template
config files.

  .. code-block:: console

    ssato@localhost% cat a.yml
    a: 1
    b:
      {% for i in [1, 2, 3] -%}
      - index: {{ i }}
      {% endfor %}
    {% include "b.yml" %}
    ssato@localhost% cat b.yml
    c:
      d: "efg"
    ssato@localhost% anyconfig_cli a.yml --template -O yaml -s
    a: 1
    b:
    - {index: 1}
    - {index: 2}
    - {index: 3}
    c: {d: efg}
    ssato@localhost%

And another one:

  .. code-block:: console

    In [1]: import anyconfig

    In [2]: ls *.yml
    a.yml  b.yml

    In [3]: cat a.yml
    a: {{ a }}
    b:
      {% for i in b -%}
      - index: {{ i }}
      {% endfor %}
    {% include "b.yml" %}

    In [4]: cat b.yml
    c:
      d: "efg"

    In [5]: context = dict(a=1, b=[2, 4])

    In [6]: anyconfig.load("*.yml", ac_template=True, ac_context=context)
    Out[6]: {'a': 1, 'b': [{'index': 2}, {'index': 4}], 'c': {'d': 'efg'}}

.. [#] Jinja2 template engine (http://jinja.pocoo.org) and its language (http://jinja.pocoo.org/docs/dev/)

Other random topics with API usage
-----------------------------------

Suppress logging messages from anyconfig module
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

anyconfig uses a global logger named 'anyconfig' [#]_ and provide an utility
function :func:`anyconfig.set_loglevel` to set logging level of this logger
like the followings:

- Set log level of anyconfig module before load:

.. code-block:: python

  import logging

  logging.getLogger("anyconfig").setLevel(logging.ERROR)

  import anyconfig

  ...

- Set log level of anyconfig module after load:

.. code-block:: python

  import anyconfig
  import logging

  anyconfig.set_loglevel(logging.WARN)  # Or: anyconfig.LOGGER.setLevel(logging.ERROR)

.. [#] I https://docs.python.org/2/howto/logging.html#library-config

Combination with other modules
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

anyconfig can be combined with other modules such as pyxdg and appdirs [#]_ .

For example, you can utilize anyconfig and pyxdg or appdirs in you application
software to load user config files like this:

.. code-block:: python

  import anyconfig
  import appdirs
  import os.path
  import xdg.BaseDirectory

  APP_NAME = "foo"
  APP_CONF_PATTERN = "*.yml"


  def config_path_by_xdg(app=APP_NAME, pattern=APP_CONF_PATTERN):
      return os.path.join(xdg.BaseDirectory.save_config_path(app), pattern)


  def config_path_by_appdirs(app=APP_NAME, pattern=APP_CONF_PATTERN):
      os.path.join(appdirs.user_config_dir(app), pattern)


  def load_config(fun=config_path_by_xdg):
      return anyconfig.load(fun())

.. [#] http://freedesktop.org/wiki/Software/pyxdg/
.. [#] https://pypi.python.org/pypi/appdirs/

Default config values
^^^^^^^^^^^^^^^^^^^^^^^^^^^

Current implementation of anyconfig.\*load\*() do not provide a way to provide
some sane default configuration values (as a dict parameter for example)
before/while loading config files. Instead, you can accomplish that by a few
lines of code like the followings:

.. code-block:: python

   import anyconfig

   default = dict(foo=0, bar='1', baz=[2, 3])  # Default values
   conf = anyconfig.container(default)  # or: anyconfig.container(**default)
   conf_from_files = anyconfig.load("/path/to/config_files_dir/*.yml")

   conf.update(conf_from_files)

   # Use `conf` ... 

or:

.. code-block:: python

   default = dict(foo=0, bar='1', baz=[2, 3])
   conf = anyconfig.container(default)
   conf.update(anyconfig.load("/path/to/config_files_dir/*.yml"))

Environment Variables
^^^^^^^^^^^^^^^^^^^^^^^^

It's a piece of cake to use environment variables as config default values like
this:

.. code-block:: python

   conf = anyconfig.container(os.environ.copy())
   conf.update(anyconfig.load("/path/to/config_files_dir/*.yml"))

Load from compressed files
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Since 0.2.0, python-anyconfig can load configuration from file or file-like
object (called 'stream' internally). And this should help loading
configurations from compressed files.

- Loading from a compressed JSON config file:

.. code-block:: python

   import gzip

   strm = gzip.open("/path/to/gzip/compressed/cnf.json.gz")
   cnf = anyconfig.load(strm, "json")

- Loading from some compressed JSON config files:

.. code-block:: python

   import gzip
   import glob

   cnfs = "/path/to/gzip/conf/files/*.yml.gz"
   strms = [gzip.open(f) for f in sorted(glob.glob(cnfs))]
   cnf = anyconfig.load(strms, "yaml")

Please note that "json" argument passed to anyconfig.load is necessary to help
anyconfig find out the configuration type of the file.

Convert from/to bunch objects
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

It's easy to convert result conf object from/to bunch objects [#]_ as
anyconfig.load{s,} return a dict-like object:

.. code-block:: python

   import anyconfig
   import bunch

   conf = anyconfig.load("/path/to/some/config/files/*.yml")
   bconf = bunch.bunchify(conf)
   bconf.akey = ...  # Overwrite a config parameter.
      ...
   anyconfig.dump(bconf.toDict(), "/tmp/all.yml")

.. [#] bunch: https://pypi.python.org/pypi/bunch/

.. vim:sw=2:ts=2:et:
