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