# HG changeset patch # User Franz Glasner # Date 1523046525 -7200 # Node ID c87b0dc54e1d5b6a79b9175538f8628d90436a0e # Parent b883f4ef19679635b91170274307e4a43243fb45 Allow custom configuration filename extensions and custom loaders that can handle custom configuration file syntax styles diff -r b883f4ef1967 -r c87b0dc54e1d CHANGES.txt --- a/CHANGES.txt Fri Apr 06 09:42:17 2018 +0200 +++ b/CHANGES.txt Fri Apr 06 22:28:45 2018 +0200 @@ -51,6 +51,12 @@ Allow JSON formatted files as configuration files also (suffix ".json"). + .. change:: + :tags: feature + + Allow custom configuration filename extensions and custom loaders that + can handle custom configuration file syntax styles. + .. changelog:: :version: 0.5 diff -r b883f4ef1967 -r c87b0dc54e1d configmix/__init__.py --- a/configmix/__init__.py Fri Apr 06 09:42:17 2018 +0200 +++ b/configmix/__init__.py Fri Apr 06 22:28:45 2018 +0200 @@ -23,7 +23,9 @@ from .config import Configuration -__all__ = ["load", "safe_load", "Configuration"] +__all__ = ["load", "safe_load", + "set_loader", "default_loaders", + "Configuration"] def load(*files): @@ -79,13 +81,55 @@ return ini.load(filename) -_loaders = { +default_loaders = { ".yml": _load_yaml, ".yaml": _load_yaml, ".json": _load_json, ".py": _load_py, ".ini": _load_ini } +"""The builtin default associations of extensions with loaders""" + + +DEFAULT_LOADER = object() +"""Marker for the default loader for an extension. + +To be used in :func:`set_loader`. +""" + +_loaders = {} +"""All configured loader functions""" + + +def clear_loader(): + """Remove all configured loader associations. + + The :data:`default_loaders` are **not** changed. + + """ + _loaders.clear() + + +def set_loader(extension, loader): + """Associate a filename trailer `extension` with a callable `loader` that + will be called when :func:`load` encounters a file argument that ends + with `extension`. + + :param str extension: the extension to associate a loader with + :param callable loader: a callable that accepts a `filename` argument and + returns a parsed configuration from a given file + + If `loader` is :data:`DEFAULT_LOADER` then the default association + from :data:`default_loaders` will be used -- if any. + + """ + if loader is DEFAULT_LOADER: + try: + _loaders[extension] = default_loaders[extension] + except KeyError: + raise ValueError("no DEFAULT loader for extension: %r" % extension) + else: + _loaders[extension] = loader def _load_cfg_from_file(filename): @@ -194,3 +238,10 @@ else: user[k] = _safe_merge(user[k], v) return user + + +# +# Init loader defaults +# +for _extension in default_loaders: + set_loader(_extension, DEFAULT_LOADER) diff -r b883f4ef1967 -r c87b0dc54e1d doc/introduction.rst --- a/doc/introduction.rst Fri Apr 06 09:42:17 2018 +0200 +++ b/doc/introduction.rst Fri Apr 06 22:28:45 2018 +0200 @@ -156,7 +156,7 @@ value2 = config.getvar_s("tree1.tree2.key4") -The filenames of the configuration files must have the extensions +By default filenames of the configuration files must have the extensions (case-insensitively): ``.py`` @@ -325,3 +325,29 @@ expands to something like ``CPYTHON`` when using the standard Python interpreter written in C. + + +Custom filename extensions and custom loaders +--------------------------------------------- + +If you want to have custom configuration file extensions and/or custom loaders +for custom configuration files you have various possibilities: + + Associate an additional new extension (e.g. ".conf") with an + existing configuration file style (e.g. YAML):: + + configmix.set_loader(".conf", configmix.default_loaders[".yml"]) + + Allow only files with extension ".cfg" in INI-style:: + + configmix.clear_loader() + configmix.set_loader(".cfg", configmix.default_loders[".ini"]) + + Just a new configuration file style:: + + def my_custom_loader(filename): + ... + return some_dict_alike + + configmix.clear_loader() + configmix.set_loader(".my.configuration", my_custom_loader)