1Usage
2========
3
4Here are some code examples of API usage.
5
6Loading single config file
7----------------------------
8
9Here are some example code to load single config file:
10
11.. code-block:: python
12
13  import anyconfig
14
15  # Config type (format) is automatically detected by filename (file
16  # extension).
17  data1 = anyconfig.load("/path/to/foo/conf.d/a.yml")
18
19  # Loaded config data is a dict-like object.
20  # examples:
21  # data1["a"] => 1
22  # data1["b"]["b1"] => "xyz"
23  # data1["c"]["c1"]["c13"] => [1, 2, 3]
24
25  # Same as above but I recommend to use the former.
26  data2 = anyconfig.single_load("/path/to/foo/conf.d/a.yml")
27
28  # Or you can specify config type explicitly as needed.
29  cnf_path = "/path/to/foo/conf.d/b.conf"
30  data3 = anyconfig.load(cnf_path, ac_parser="yaml")
31
32  # Same as above but ...
33  data4 = anyconfig.single_load(cnf_path, ac_parser="yaml")
34
35  # Same as above as a result but make parser instance and pass it explicitly.
36  yml_psr = anyconfig.find_loader(None, ac_parser="yaml")
37  data5 = anyconfig.single_load(cnf_path, yml_psr)  # Or: anyconfig.load(...)
38
39  # Same as above as a result but make parser instance and pass it explicitly.
40  yml_psr = anyconfig.find_loader(None, ac_parser="yaml")
41  data6 = anyconfig.single_load(cnf_path, yml_psr)  # Or: anyconfig.load(...)
42
43  # Similar to the previous examples but parser is specified explicitely to use
44  # ruamel.yaml based YAML parser instead of PyYAML based one, and give
45  # ruamel.yaml specific option.
46  data7 = anyconfig.load(cnf_path, ac_parser="ruamel.yaml",
47                         allow_duplicate_keys=True)
48
49  # Same as above but open the config file explicitly before load.
50  with anyconfig.open("/path/to/foo/conf.d/a.yml") as istrm:
51      data10 = anyconfig.load(istrm)
52
53  # Same as above but with specifying config type explicitly.
54  with anyconfig.open("/path/to/foo/conf.d/a.yml", ac_parser="yaml") as istrm:
55      data11 = anyconfig.load(istrm)
56
57Exceptions raised on load
58^^^^^^^^^^^^^^^^^^^^^^^^^^^
59
60Exception may be raised if something goes wrong. Then, you have to catch them
61if you want to process more w/ errors ignored or handled.
62
63.. code-block:: console
64
65  >>> import anyconfig
66  >>> anyconfig.single_load(None)
67  Traceback (most recent call last):
68    ...
69  ValueError: path_or_stream or forced_type must be some value
70  >>> anyconfig.single_load(None, ac_parser="backend_module_not_avail")
71  Traceback (most recent call last):
72    ...
73  anyconfig.backends.UnknownParserTypeError: No parser found for type 'backend_module_not_avail'
74  >>> anyconfig.single_load(None, ac_parser="not_existing_type")
75  Traceback (most recent call last):
76    ...
77  anyconfig.backends.UnknownParserTypeError: No parser found for type 'not_existing_type'
78  >>> anyconfig.single_load("unknown_type_file.conf")
79  Traceback (most recent call last):
80    ...
81  anyconfig.backends.UnknownFileTypeError: No parser found for file 'unknown_type_file.conf'
82  >>>
83
84Common and backend specific Keyword options on load single config file
85^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
86
87Here is a brief summary of keyword options prefixed with 'ac\_' to change the
88behavior on load.
89
90.. csv-table::
91   :header: Option, Type, Note
92   :widths: 10, 20, 40
93
94   ac_parser, str or :class:`anyconfig.backend.base.Parser`, Forced parser type or parser object
95   ac_dict, callable, "Any callable (function or class) to make mapping object will be returned as a result or None. If not given or ac_dict is None, default mapping object used to store resutls is dict or :class:`~collections.OrderedDict` if ac_ordered is True and selected backend can keep the order of items in mapping objects."
96   ac_ordered, bool, True to keep resuls ordered. Please note that order of items in results may be lost depends on backend used.
97   ac_template, bool, Assume given file may be a template file and try to compile it AAR if True
98   ac_context, mapping object, Mapping object presents context to instantiate template
99   ac_schema, str, JSON schema file path to validate given config file
100   ac_query, str, JMESPath expression to query data
101
102You can pass backend (config loader) specific keyword options to these load and
103dump functions as needed along with the above anyconfig specific keyword
104options:
105
106.. code-block:: python
107
108  # from python -c "import json; help(json.load)":
109  # Help on function load in module json:
110  #
111  # load(fp, encoding=None, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)
112  #    Deserialize ``fp`` (a ``.read()``-supporting file-like object containing
113  #    a JSON document) to a Python object.
114  #    ...
115  data6 = anyconfig.load("foo.json", parse_float=None)
116
117Allowed keyword options depend on backend, so please take a look at each
118backend API docs for more details about it.
119
120Others topics on load
121^^^^^^^^^^^^^^^^^^^^^^^
122
123Anyconfig also enables:
124
125- 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.
126- to validate a config file with a JSON schema [#]_ before load. See `Validation with and/or generate JSON Schema`_ section for more details.
127- to search and filter results with a JMESPath expression [#]_ after load. See `Query results with JMESPath expression`_ section for more details.
128
129.. note::
130   The returned object is a mapping object, dict or collections.OrderedDict object by default.
131
132.. [#] http://jinja.pocoo.org
133.. [#] http://json-schema.org
134.. [#] http://jmespath.org
135
136Loading multiple config files
137-------------------------------
138
139Here are some example code to load multiple config files:
140
141.. code-block:: python
142
143  import anyconfig
144
145  # Specify config files by list of paths:
146  data1 = anyconfig.load(["/etc/foo.d/a.json", "/etc/foo.d/b.json"])
147
148  # Similar to the above but all or one of config files are missing:
149  data2 = anyconfig.load(["/etc/foo.d/a.json", "/etc/foo.d/b.json"],
150                         ignore_missing=True)
151
152  # Specify config files by glob path pattern:
153  cnf_path = "/etc/foo.d/*.json"
154  data3 = anyconfig.load(cnf_path)
155
156  # Similar to above but make parser instance and pass it explicitly.
157  psr = anyconfig.find_loader(cnf_path)
158  data4 = anyconfig.load(cnf_path, psr)
159
160  # Similar to the above but parameters in the former config file will be simply
161  # overwritten by the later ones:
162  data5 = anyconfig.load("/etc/foo.d/*.json", ac_merge=anyconfig.MS_REPLACE)
163
164Strategies to merge data loaded from multiple config files
165^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
166
167On loading multiple config files, you can choose 'strategy' to merge
168configurations from the followings and pass it with ac_merge keyword option:
169
170* anyconfig.MS_REPLACE: Replace all configuration parameter values provided in
171  former config files are simply replaced w/ the ones in later config files.
172
173  For example, if a.yml and b.yml are like followings:
174
175  a.yml:
176
177
178  .. code-block:: yaml
179
180    a: 1
181    b:
182       - c: 0
183       - c: 2
184    d:
185       e: "aaa"
186       f: 3
187
188  b.yml:
189
190  .. code-block:: yaml
191
192    b:
193       - c: 3
194    d:
195       e: "bbb"
196
197  then:
198
199  .. code-block:: python
200
201    load(["a.yml", "b.yml"], ac_merge=anyconfig.MS_REPLACE)
202
203  will give object such like:
204
205  .. code-block:: python
206
207    {'a': 1, 'b': [{'c': 3}], 'd': {'e': "bbb"}}
208
209* anyconfig.MS_NO_REPLACE: Do not replace configuration parameter values
210  provided in former config files.
211
212  For example, if a.yml and b.yml are like followings:
213
214  a.yml:
215
216  .. code-block:: yaml
217
218    b:
219       - c: 0
220       - c: 2
221    d:
222       e: "aaa"
223       f: 3
224
225  b.yml:
226
227  .. code-block:: yaml
228
229    a: 1
230    b:
231       - c: 3
232    d:
233       e: "bbb"
234
235  then:
236
237  .. code-block:: python
238
239    load(["a.yml", "b.yml"], ac_merge=anyconfig.MS_NO_REPLACE)
240
241  will give object such like:
242
243  .. code-block:: python
244
245    {'a': 1, 'b': [{'c': 0}, {'c': 2}], 'd': {'e': "bbb", 'f': 3}}
246
247* anyconfig.MS_DICTS (default): Merge dicts recursively. That is, the following:
248
249  .. code-block:: python
250
251    load(["a.yml", "b.yml"], ac_merge=anyconfig.MS_DICTS)
252
253  will give object such like:
254
255  .. code-block:: python
256
257    {'a': 1, 'b': [{'c': 3}], 'd': {'e': "bbb", 'f': 3}}
258
259  This is the merge strategy choosen by default.
260
261* anyconfig.MS_DICTS_AND_LISTS: Merge dicts and lists recursively. That is, the
262  following:
263
264  .. code-block:: python
265
266    load(["a.yml", "b.yml"], ac_merge=anyconfig.MS_DICTS_AND_LISTS)
267
268  will give object such like:
269
270  .. code-block:: python
271
272    {'a': 1, 'b': [{'c': 0}, {'c': 2}, {'c': 3}], 'd': {'e': "bbb", 'f': 3}}
273
274Or you you can implement custom function or class or anything callables to
275merge nested dicts by yourself and utilize it with ac_merge keyword option like
276this:
277
278  .. code-block:: python
279
280    def my_merge_fn(self, other, key, val=None, **options):
281        """
282        :param self: mapping object to update with `other`
283        :param other: mapping object to update `self`
284        :param key: key of mapping object to update
285        :param val: value to update self alternatively
286
287        :return: None but `self` will be updated
288        """
289        if key not in self:
290            self[key] = other[key] if val is None else val
291
292    load(["a.yml", "b.yml"], ac_merge=my_merge_fn)
293
294Please refer to the exsiting functions in anyconfig.dicsts (_update_\*
295functions) to implement custom functions to merge nested dicts for more
296details.
297
298Common and backend specific Keyword options on load multiple config files
299^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
300
301Here is a brief summary of keyword options prefixed with 'ac\_' in addition to
302the keyword options explained in the `Common and backend specific Keyword
303options on load single config file`_  section to change the behavior on load
304multiple files.
305
306.. csv-table::
307   :header: Option, Type, Note
308   :widths: 10, 20, 40
309
310   ac_merge, str, One of anyconfig.dicts.MERGE_STRATEGIES to select strategy of how to merge results loaded from multiple configuration files. See the doc of :mod:`anyconfig.dicts` for more details of strategies. The default is anyconfig.dicts.MS_DICTS.
311   ac_marker, str, Glob marker string to detect paths patterns. '*' by default.
312
313Dumping config data
314---------------------
315
316A pair of APIs are provided to dump config data loaded w/ using loading APIs as
317described previously and corresponding to them.
318
319- :func:`dumps`: Dump data as a string
320- :func:`dump`: Dump data to file of which path was given or file-like object opened
321
322.. note::
323
324   To specify the format or backend type w/ ac_parser keyword option is
325   necessary for :func:`dumps` API because anyconfig cannot determine the type
326   w/o it.
327
328Like loading APIs, you can pass common and backend specific keyword options to them.
329
330- common keyword options: ac_parser to determine which backend to use
331- backend specific keyword options: see each backends' details
332
333Here are some examples of these usage:
334
335.. code-block:: python
336
337  In [1]: s = """a: A
338     .....: b:
339     .....:   - b0: 0
340     .....:   - b1: 1
341     .....: c:
342     .....:   d:
343     .....:     e: E
344     .....: """
345
346  In [2]: cnf = anyconfig.loads(s, ac_parser="yaml")
347
348  In [3]: cnf
349  Out[3]: {'a': 'A', 'b': [{'b0': 0}, {'b1': 1}], 'c': {'d': {'e': 'E'}}}
350
351  In [4]: anyconfig.dumps(cnf, ac_parser="yaml")  # ac_parser option is necessary.
352  Out[4]: 'a: A\nc:\n  d: {e: E}\nb:\n- {b0: 0}\n- {b1: 1}\n'
353
354  In [5]: print(anyconfig.dumps(cnf, ac_parser="yaml"))
355  a: A
356  c:
357    d: {e: E}
358  b:
359  - {b0: 0}
360  - {b1: 1}
361
362  In [6]: print(anyconfig.dumps(cnf, ac_parser="json"))
363  {"a": "A", "c": {"d": {"e": "E"}}, "b": [{"b0": 0}, {"b1": 1}]}
364
365  In [7]: print(anyconfig.dumps(cnf, ac_parser="ini"))  # It cannot!
366  ---------------------------------------------------------------------------
367  AttributeError                            Traceback (most recent call last)
368  <ipython-input-228-2b2771a44a7e> in <module>()
369  ----> 1 print(anyconfig.dumps(cnf, ac_parser="ini"))
370      ...
371  AttributeError: 'str' object has no attribute 'iteritems'
372
373  In [8]: print(anyconfig.dumps(cnf, ac_parser="configobj"))
374  a = A
375  b = {'b0': 0}, {'b1': 1}
376  [c]
377  [[d]]
378  e = E
379
380  In [9]:
381
382Like this example, it's not always possible to dump data to any formats because
383of limitations of formarts and/or backends.
384
385Keep the order of configuration items
386----------------------------------------
387
388If you want to keep the order of configuration items, specify ac_order=True on
389load or specify ac_dict to any mapping object can save the order of items such
390like :class:`collections.OrderedDict` (or
391:class:`~anyconfig.compat.OrderedDict`). Otherwise, the order of configuration
392items will be lost by default.
393
394Please note that anyconfig.load APIs sometimes cannot keep the order of items
395in the original data even if ac_order=True or ac_dict=<ordereddict> was
396specified because used backend or module cannot keep that. For example, JSON
397backend can keep items but current YAML backend does not due to the limitation
398of YAML module it using.
399
400Validation with and/or generate JSON Schema
401----------------------------------------------
402
403If jsonschema [#]_ is installed and available, you can validate config files
404with using anyconfig.validate() since 0.0.10.
405
406.. code-block:: python
407
408  # Validate a JSON config file (conf.json) with JSON schema (schema.json).
409  # If validatation suceeds, `rc` -> True, `err` -> ''.
410  conf1 = anyconfig.load("/path/to/conf.json")
411  schema1 = anyconfig.load("/path/to/schema.json")
412  (rc, err) = anyconfig.validate(conf1, schema1)
413
414  # Similar to the above but both config and schema files are in YAML.
415  conf2 = anyconfig.load("/path/to/conf.yml")
416  schema2 = anyconfig.load("/path/to/schema.yml")
417  (rc, err) = anyconfig.validate(conf2, schema2)
418
419  # Similar to the above but exception will be raised if validation fails.
420  (rc, _err) = anyconfig.validate(conf2, schema2, ac_schema_safe=False)
421
422It's also able to validate config files during load:
423
424.. code-block:: python
425
426  # Validate a config file (conf.yml) with JSON schema (schema.yml) while
427  # loading the config file.
428  conf1 = anyconfig.load("/a/b/c/conf.yml", ac_schema="/c/d/e/schema.yml")
429
430  # Validate config loaded from multiple config files with JSON schema
431  # (schema.json) while loading them.
432  conf2 = anyconfig.load("conf.d/*.yml", ac_schema="/c/d/e/schema.json")
433
434And even if you don't have any JSON schema files, don't worry ;-), anyconfig
435*can generate* the schema for your config files on demand and you can save it
436in any formats anyconfig supports.
437
438.. code-block:: python
439
440  # Generate a simple JSON schema file from config file loaded.
441  conf1 = anyconfig.load("/path/to/conf1.json")
442  schema1 = anyconfig.gen_schema(conf1)
443  anyconfig.dump(schema1, "/path/to/schema1.yml")
444
445  # Generate more strict (precise) JSON schema file from config file loaded.
446  schema2 = anyconfig.gen_schema(conf1, ac_schema_strict=True)
447  anyconfig.dump(schema2, "/path/to/schema2.json")
448
449.. note:: If you just want to generate JSON schema from your config files, then
450   you don't need to install jsonschema in advance because *anyconfig can
451   generate JSON schema without jsonschema module*.
452
453.. [#] https://pypi.python.org/pypi/jsonschema
454
455Template config support
456---------------------------
457
458anyconfig supports template config files since 0.0.6.  That is, config files
459written in Jinja2 template [#]_ will be compiled before loading w/ backend
460module.
461
462.. 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.
463
464Anyway, a picture is worth a thousand words. Here is an example of template
465config files.
466
467  .. code-block:: console
468
469    ssato@localhost% cat a.yml
470    a: 1
471    b:
472      {% for i in [1, 2, 3] -%}
473      - index: {{ i }}
474      {% endfor %}
475    {% include "b.yml" %}
476    ssato@localhost% cat b.yml
477    c:
478      d: "efg"
479    ssato@localhost% anyconfig_cli a.yml --template -O yaml -s
480    a: 1
481    b:
482    - {index: 1}
483    - {index: 2}
484    - {index: 3}
485    c: {d: efg}
486    ssato@localhost%
487
488And another one:
489
490  .. code-block:: console
491
492    In [1]: import anyconfig
493
494    In [2]: ls *.yml
495    a.yml  b.yml
496
497    In [3]: cat a.yml
498    a: {{ a }}
499    b:
500      {% for i in b -%}
501      - index: {{ i }}
502      {% endfor %}
503    {% include "b.yml" %}
504
505    In [4]: cat b.yml
506    c:
507      d: "efg"
508
509    In [5]: context = dict(a=1, b=[2, 4])
510
511    In [6]: anyconfig.load("*.yml", ac_template=True, ac_context=context)
512    Out[6]: {'a': 1, 'b': [{'index': 2}, {'index': 4}], 'c': {'d': 'efg'}}
513
514.. [#] Jinja2 template engine (http://jinja.pocoo.org) and its language (http://jinja.pocoo.org/docs/dev/)
515
516Query results with JMESPath expression
517-------------------------------------------
518
519anyconfig supports to query result mapping object with JMESPath expression
520since 0.8.3 like the following example [#]_ .
521
522.. code-block:: console
523
524  >>> yaml_s = """\
525  ... locations:
526  ...   - name: Seattle
527  ...     state: WA
528  ...   - name: New York
529  ...     state: NY
530  ...   - name: Olympia
531  ...     state: WA
532  ... """
533  >>> query = "locations[?state == 'WA'].name | sort(@) | {WashingtonCities: join(', ', @)}"
534  >>> anyconfig.loads(yaml_s, ac_parser="yaml", ac_query=query)
535   {'WashingtonCities': 'Olympia, Seattle'}
536  >>>
537
538Different from other libraries can process JMESPath expressions, anyconfig can
539query data of any formats it supports, with help of the jmespath support
540library [#]_ . That is, you can query XML, YAML, BSON, Toml, and, of course
541JSON files with JMESPath expression.
542
543.. [#] This example is borrowed from JMESPath home, http://jmespath.org
544.. [#] https://github.com/jmespath/jmespath.py
545
546Other random topics with API usage
547-----------------------------------
548
549Suppress logging messages from anyconfig module
550^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
551
552anyconfig uses a global logger named **anyconfig** and logging messages are
553suppressed by default as NullHandler was attached to the logger [#]_ . If you
554want to see its log messages out, you have to configure it (add handler and
555optionally set log level) like the followings.
556
557- Set the log level and handler of anyconfig module before load to print log messages such as some backend modules are not available, when it's initialized:
558
559.. code-block:: python
560
561  In [1]: import logging
562
563  In [2]: LOGGER = logging.getLogger("anyconfig")
564
565  In [3]: LOGGER.addHandler(logging.StreamHandler())
566
567  In [4]: LOGGER.setLevel(logging.ERROR)
568
569  In [5]: import anyconfig
570
571  In [6]: anyconfig.dumps(dict(a=1, b=[1,2]), "aaa")
572  No parser found for given type: aaa
573  Out[6]: '{"a": 1, "b": [1, 2]}'
574
575  In [7]:
576
577- Set log level of anyconfig module after load:
578
579.. code-block:: console
580
581  In [1]: import anyconfig, logging
582
583  In [2]: LOGGER = logging.getLogger("anyconfig")
584
585  In [3]: LOGGER.addHandler(logging.StreamHandler())
586
587  In [4]: anyconfig.dumps(dict(a=2, b=[1,2]), "unknown_type")
588  No parser found for given type: unknown_type
589  Parser unknown_type was not found!
590  Dump method not implemented. Fallback to json.Parser
591  Out[4]: '{"a": 2, "b": [1, 2]}'
592
593  In [5]:
594
595.. [#] https://docs.python.org/2/howto/logging.html#library-config
596
597Combination with other modules
598^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
599
600anyconfig can be combined with other modules such as pyxdg and appdirs [#]_ .
601
602For example, you can utilize anyconfig and pyxdg or appdirs in you application
603software to load user config files like this:
604
605.. code-block:: python
606
607  import anyconfig
608  import appdirs
609  import os.path
610  import xdg.BaseDirectory
611
612  APP_NAME = "foo"
613  APP_CONF_PATTERN = "*.yml"
614
615
616  def config_path_by_xdg(app=APP_NAME, pattern=APP_CONF_PATTERN):
617      return os.path.join(xdg.BaseDirectory.save_config_path(app), pattern)
618
619
620  def config_path_by_appdirs(app=APP_NAME, pattern=APP_CONF_PATTERN):
621      os.path.join(appdirs.user_config_dir(app), pattern)
622
623
624  def load_config(fun=config_path_by_xdg):
625      return anyconfig.load(fun())
626
627.. [#] http://freedesktop.org/wiki/Software/pyxdg/
628.. [#] https://pypi.python.org/pypi/appdirs/
629
630Default config values
631^^^^^^^^^^^^^^^^^^^^^^^^^^^
632
633Current implementation of anyconfig.\*load\*() do not provide a way to provide
634some sane default configuration values (as a dict parameter for example)
635before/while loading config files. Instead, you can accomplish that by a few
636lines of code like the followings:
637
638.. code-block:: python
639
640   import anyconfig
641
642   conf = dict(foo=0, bar='1', baz=[2, 3])  # Default values
643   conf_from_files = anyconfig.load("/path/to/config_files_dir/*.yml")
644   anyconfig.merge(conf, conf_from_files)  # conf will be updated.
645
646   # Use `conf` ...
647
648or:
649
650.. code-block:: python
651
652   conf = dict(foo=0, bar='1', baz=[2, 3])
653   anyconfig.merge(conf, anyconfig.load("/path/to/config_files_dir/*.yml"))
654
655Environment Variables
656^^^^^^^^^^^^^^^^^^^^^^^^
657
658It's a piece of cake to use environment variables as config default values like
659this:
660
661.. code-block:: python
662
663   conf = os.environ.copy()
664   anyconfig.merge(conf, anyconfig.load("/path/to/config_files_dir/*.yml"))
665
666Load from compressed files
667^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
668
669Since 0.2.0, python-anyconfig can load configuration from file or file-like
670object, called *stream* internally. And this should help loading configurations
671from compressed files.
672
673- Loading from a compressed JSON config file:
674
675.. code-block:: python
676
677   import gzip
678
679   strm = gzip.open("/path/to/gzip/compressed/cnf.json.gz")
680   cnf = anyconfig.load(strm, "json")
681
682- Loading from some compressed JSON config files:
683
684.. code-block:: python
685
686   import gzip
687   import glob
688
689   cnfs = "/path/to/gzip/conf/files/*.yml.gz"
690   strms = [gzip.open(f) for f in sorted(glob.glob(cnfs))]
691   cnf = anyconfig.load(strms, "yaml")
692
693Please note that "json" argument passed to anyconfig.load is necessary to help
694anyconfig find out the configuration type of the file.
695
696Convert from/to bunch objects
697^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
698
699It's easy to convert result conf object from/to bunch objects [#]_ as
700anyconfig.load{s,} return a dict-like object:
701
702.. code-block:: python
703
704   import anyconfig
705   import bunch
706
707   conf = anyconfig.load("/path/to/some/config/files/*.yml")
708   bconf = bunch.bunchify(conf)
709   bconf.akey = ...  # Overwrite a config parameter.
710      ...
711   anyconfig.dump(bconf.toDict(), "/tmp/all.yml")
712
713.. [#] bunch: https://pypi.python.org/pypi/bunch/
714
715.. vim:sw=2:ts=2:et:
716