Mercurial > hgrepos > Python > libs > ConfigMix
view docs/introduction.rst @ 428:090a25f36a3d
FIX: Allow jailed configurations to use correctly use base configurations that use a different "default" marker object.
Jailed configurations assumed that their "default" marker object is
identical to the "default" marker object in the unjailed base
configuration. This is not always true, especially if
"_JailedConfiguration.rebind()" is used.
Removed the explicit "default" keyword argument and passed the complete
keywords argument dictionary to the base instead. This triggers correct
default handling in the base.
| author | Franz Glasner <f.glasner@feldmann-mg.com> |
|---|---|
| date | Thu, 09 Dec 2021 13:02:17 +0100 |
| parents | 84d4f82ffe59 |
| children | 6102b767fc69 |
line wrap: on
line source
.. -*- coding: utf-8; indent-tabs-mode: nil; -*- .. _introduction: Introduction ============ .. contents:: :local: The configurations can be read from different types of files: - :ref:`YAML files <yaml-files>` - :ref:`JSON files <json-files>` - :ref:`INI files <ini-files>` - :ref:`TOML files <toml-files>` - :ref:`executable Python scripts <executable-python-scripts>` .. _yaml-files: YAML Files ---------- Need the :mod:`yaml` package (https://github.com/yaml/pyyaml) (e.g. ``pip install pyyaml``) .. note:: All strings are returned as Unicode text strings. .. note:: The root object must be a *mapping* and therefore decode into a Python :class:`dict` alike. This is checked by the implementation. An example is: .. literalinclude:: ../tests/data/conf10.yml :language: yaml .. _json-files: JSON files ---------- Read the JSON file with the help of Python's native :mod:`json` package. .. note:: All strings are returned as Unicode text strings. .. note:: The root object must be an *object* and therefore decode into a Python :class:`dict` alike. This is checked by the implementation. An example is: .. literalinclude:: ../tests/data/conf10.json :language: js For comments in JSON files see section :ref:`comments`. .. _ini-files: INI Files --------- Read the file and all sections named in parameter `extract` are flattened into the resulting dictionary. By default the section named ``config`` is used as root section. Normally all values are returned as Unicode text strings. But values can be annotated and therefore interpreted as other types: ``:int:`` The value is handled in the same way as a Python :class:`int` literal ``:float:`` The value is interpreted as :class:`float` ``:bool:`` The resulting value is a :class:`bool` where ``1``, ``true``, ``yes``, ``on`` yield a Python ``True`` ``0``, ``false``, ``no``, ``off`` yield a Python ``False`` The evaluation is done *case-insensitively*. .. note:: All strings are returned as Unicode text strings. .. note:: Contrary to the behaviour of the standard Python :mod:`configparser` module the INI file reader is *case-sensitive*. The example INI style configuration below yields an equivalent configuration to the YAML configuration above: .. literalinclude:: ../tests/data/conf10.ini :language: ini As can be seen in this example -- INI file internal value interpolation is done as in Python's standard :mod:`configparser` module. This example also illustrates how INI sections are used to build a tree-ish configuration dictionary. .. _toml-files: TOML Files ---------- Read the TOML file with the help of the pure Python :mod:`toml` package (https://github.com/uiri/toml) (e.g. ``pip install toml``). All TOML features map seamingless to "ConfigMix". The example TOML style configuration below yields an equivalent configuration to the YAML configuration above: .. literalinclude:: ../tests/data/conf10.toml :language: ini .. _executable-python-scripts: Executable Python Scripts ------------------------- What will be exported: 1. If loading is done with the `extract` parameter only the given keys are extracted from the script. 2. Otherwise it is checked if the scripts defines an ``__all__`` sequence. If there is one it's contents are the keys to be extracted. 3. If there is no ``__all__`` object all names not starting with an underscore ``_`` are found. This is analogous to as Python modules behave when importing them with ``from module import *``. .. note:: The Python configuration files are evaluated with ``exec`` and not imported. The example configuration by Python script below yields an equivalent configuration to the YAML configuration above: .. literalinclude:: ../tests/data/conf10.py :language: python .. _loading-and-merging: Loading and Merging ------------------- Basic usage of the API is as follows in this example:: import configmix # # Note: With conf10 merging is rather pointless because the tree # files # are really the same configuration. But is doesn't harm # also here. # config = configmix.load("conf10.yml", "conf10.ini", "conf10.py") # Get a -- possibly interpolated -- configuration variable's value value1 = config.getvar_s("key1") # Get a -- possibly interpolated -- variable from within the tree value2 = config.getvar_s("tree1.tree2.key4") By default filenames of the configuration files must have the extensions (case-sensitivety depends on your OS): ``.ini`` for INI configuration files ``.json`` for JSON configuration files ``.py`` for Python configuration files ``.toml`` for TOML configuration file ``.yml`` or ``.yaml`` for YAML configuration files .. _getting-values: Getting configuration variables ------------------------------- Get a -- possibly interpolated -- configuration variable's value with :: value1 = config.getvar_s("key1") value2 = config.getvar_s("key1.subkey2") or equivalently with :: value1 = config.getvarl_s("key1") value2 = config.getvarl_s("key1", "subkey2") Get a raw configuration variable's value with :: value1_raw = config.getvar("key1") value2_raw = config.getvarl("key1.subkey2") or equivalently with :: value1_raw = config.getvarl("key1") value2_raw = config.getvarl("key1", "subkey2") Because the configuration is not only a plain list of but a tree of key-value pairs you will want to fetch a nested configuration value using two access basic methods: :py:meth:`.Configuration.getvar` and :py:meth:`.Configuration.getvar_s` Use a single key variable where the invidual level keys are joined using a dot (``.``) :py:meth:`.Configuration.getvarl` and :py:meth:`.Configuration.getvarl_s` Use just positional Python arguments for each level key Also there exist variants of the basic access methods that coerce returned variables into :py:class:`int` or :py:class:`bool` types (:py:meth:`.Configuration.getintvar_s`, :py:meth:`.Configuration.getboolvar_s`) And with :py:meth:`.Configuration.getfirstvar`, :py:meth:`.Configuration.getfirstvar_s`, :py:meth:`.Configuration.getfirstintvar_s`, :py:meth:`.Configuration.getfirstboolvar_s` and :py:meth:`.Configuration.getfirstfloatvar_s` there exist variants that accept a *list* of possible variables names and return the first one that is found. And again --- with :py:meth:`.Configuration.getfirstvarl`, :py:meth:`.Configuration.getfirstvarl_s`, :py:meth:`.Configuration.getfirstintvarl_s`, :py:meth:`.Configuration.getfirstboolvarl_s` and :py:meth:`.Configuration.getfirstfloatvarl_s` there exist variants that accept a *list* of lists or tuples or dicts that describe possible variables names and return the first one that is found. For example -- these methods for retrieving the first found variables can be used and are equivalent (Note that a caller that wants to use variables from a non-default namespace must use a sequence of dicts here):: value1 = config.getfirstvar_s("key1.subkey2", "key3.subkey4", default=None, namespace=None) value2 = config.getfirstvarl_s(*[["key1", "subkey2"], ["key3", "subkey4"]], default=None) value3 = config.getfirstvarl_s(*(("key1", "subkey2"), ("key3", "subkey4")), default=None) value4 = config.getfirstvarl_s(*[{"namespace": None, "path": ["key1", "subkey2"]}, {"namespace": None, "path": ("key3", "subkey4")}], default=None) Looking at the example in chapter :ref:`yaml-files` -- when calling ``config.getvar_s("tree1.tree2.key4")`` you will get the value ``get this as `tree1.tree2.key4'``. Alternatively ``config.getvarl_s("tree1", "tree2", "key4")`` can be called with the very same result. All four methods also perform direct :ref:`variable-interpolation` and handle :ref:`variable-namespaces` -- yet in different ways. Filtering is not supported. So -- the variable name arguments of :py:meth:`.Configuration.getvar` and :py:meth:`.Configuration.getvar_s` are of the form ``[namespace:]variable`` where for :py:meth:`.Configuration.getvarl` and :py:meth:`.Configuration.getvarl_s` the namespace is given as optional keyword parameter `namespace`. .. note:: Special characters within namespace, key and filter names *must* be quoted (see :ref:`quoting`) when using :py:meth:`~.Configuration.getvar` or :py:meth:`~.Configuration.getvar_s` to retrieve variables. With :py:meth:`~.Configuration.getvarl` or :py:meth:`~.Configuration.getvarl_s` quoting is neither needed and not supported. .. _merging-deletions: Deletions --------- By using the special value ``{{::DEL::}}`` the corresponding key-value pair is deleted when merging is done. .. _comments: Comments -------- By default all keys beginning with ``__comment`` or ``__doc`` are filtered out and not given to the application. This allows comments in JSON files -- but is not restricted to JSON files only. For all types of configuration files their respective standard comments are allowed too. .. _variable-namespaces: Variable Namespaces ------------------- Currently there are 6 namespaces: 1. The unnamed namespace (which is also default). All the configuration variables are part of this namespace. .. seealso:: :ref:`quoting` 2. The namespace ``ref`` to be used for configuration references. This is a namespace that is handled special within "ConfigMix". Must be Filters are **not** supported. 3. The namespace ``OS`` Available functions: ``cwd`` Contains the current working directory of the process ``node`` Contains the current node's computername (or whatever :py:func:`platform.node` returns) 4. The namespace ``ENV`` This namespace contains all the environment variables as they are available from :py:data:`os.environ`. 5. The namespace ``PY`` Contains selected values from the running Python: ``version`` The return value of :py:func:`platform.python_version` ``version_maj_min`` Just the major and minor version of the running Python (``.`` separated) ``version_maj`` Just the major version of the running Python ``implementation`` The return value of :py:func:`platform.python_implementation` 6. The namespace ``AWS`` Contains some metadata for AWS instances when running from within AWS: ``metadata.instance-id`` ``metadata.placement.region`` ``metadata.placement.availability-zone`` ``dynamic.instance-identity.region`` and all other properties of the instance-identity document (e.g. ``instanceId``, ``instanceType``, ``imageId``, ``pendingTime``, ``architecture``, ``availabilityZone``, ``privateIp``, ``version`` et al.). Examples ~~~~~~~~ Both :: config.getvar("OS:cwd") or :: config.getvarl("cwd", namespace="OS") yield the current working directory -- just as :py:func:`os.getcwd` does. .. _variable-interpolation: Variable Interpolation ---------------------- Configuration variable values that are read with :py:meth:`.Configuration.getvar_s` or :py:meth:`.Configuration.getvarl_s` are subject to variable interpolation. The general syntactic pattern for this is:: {{[namespace:]variable[|filter[|filter...]]}} I.e.: between double curly braces an optional `namespace` name followed by a colon ``:``, the `variable` and then zero or more filters, each one introduced by a pipe symbol ``|``. Variables are expanded *lately* at runtime -- exactly when calling :py:meth:`.Configuration.getvar_s`, :py:meth:`.Configuration.getvarl_s`, :py:meth:`.Configuration.substitute_variables_in_obj` or :py:meth:`.Configuration.expand_variable` .. note:: Special characters within namespace, key and filter names *must* be quoted (see :ref:`quoting`) when using variable interpolation syntax. Filter functions ~~~~~~~~~~~~~~~~ Interpolated values can be processed through a series of filter functions:: {{my.variable|filter1|filter2}} Available filter functions are: ``urlquote`` ``urlquote_plus`` ``saslprep`` ``normpath`` ``abspath`` ``posixpath`` ``lower`` ``upper`` Also available are special filter functions ``None`` and ``Empty``. They are useful in variable interpolation context because they suppress possible lookup errors (aka :py:exc:`KeyError`) and instead return with :py:obj:`None` or an empty string. Examples ~~~~~~~~ :: {{OS:cwd|posixpath}} expands to the current working directory as POSIX path: on Windows all backslashes are replaced by forward slashes. :: {{ENV:PATH}} expands to the current search path from the process environment. :: {{PY:version}} expands to the current running Python version (e.g. ``3.6.4``). :: {{PY:implementation|upper}} expands to something like ``CPYTHON`` when using the standard Python interpreter written in C. Configuration tree references ----------------------------- With ``{{ref:#my.other.key}}`` - think of it as a sort of a symbolic link to other parts of the configuration tree - by employing the special namespace ``ref`` - can not be quoted currently in variable interpolation syntax - No special handling when merging is done -- merging is agnostic of tree references - Keys within :meth:`.Configuration.getvar_s`, :py:meth:`.Configuration.getvar`, :py:meth:`.Configuration.getvarl` and :py:meth:`.Configuration.getvarl_s` are handled - in :py:meth:`.Configuration.getvar` only, when it is the directly referenced value - recursive expansion in :py:meth:`.Configuration.getvar_s` and :py:meth:`.Configuration.getvarl_s`: beware of recursive (direct or indirect) tree references .. _quoting: Quoting ------- When using :py:meth:`.Configuration.getvar` and :py:meth:`.Configuration.getvar_s` and when retrieving values in the default namespace the namespace separator ``:`` or the hierarchy separator ``.`` are characters with a special meaning. When using :ref:`variable interpolation <variable-interpolation>` the filter separator ``|`` is also special. To use them in key names they must be quoted. Quoting is done with a variant of the well-known percent-encoding in URIs (:rfc:`3986`). A percent-encoded character consists of the percent character ``%``, followed by one of the characters ``x``, ``u`` or ``U``, followed by the two, four or eight hexadecimal digits of the unicode codepoint value of the character that is to be quoted. ``x`` must be followed by two hex digits, ``u`` by four and ``U`` by eight. Example: The character ``.`` with the Unicode (and ASCII) value 46 (hex 0x2e) can be encoded as ``%x2e`` or ``%u002e`` or ``%U0000002e``. .. note:: Filters neeed no quoting -- and quoting within filters is *not* supported. .. note:: Quoting the ``ref`` namespace name does not work currently when used in variable interpolation syntax. .. _jailed-configuration: Jailed Configurations --------------------- With :meth:`configmix.config.Configuration.jailed` you get a `jailed` (or `restricted`) configuration from a "normal" configuration. Restriction is two-fold: - The access to configuration variables in `config` is restricted to the configuration sub-tree that is configured in `path`. - Not all access-methods of :class:`Configuration` are implemented yet. This is somewhat analogous to a `chroot` environment for filesystems. .. note:: The word "jail" is shamelessly stolen from FreeBSD jails. Usage example:: import configmix config = configmix.load("conf10.py") assert not config.is_jail value = config.getvar_s("tree1.tree2.key4") jailed_config1 = config.jailed(rootpath="tree1.tree2") assert jailed_config1.is_jail assert jailed_config1.base is config jvalue1 = jailed_config1.getvar_s("key4") jailed_config2 = config.jailed(root=("tree1", "tree2")) assert jailed_config2.is_jail assert jailed_config2.base is config jvalue2 = jailed_config.getvarl_s("key4") assert value == jvalue1 == jvalue2 == "get this as `tree1.tree2.key4'" `jvalue1` and `jvalue2` (and `value`) yield the very same value ``get this as `tree1.tree2.key4'`` from the configuration. All access methods in a jailed configuration automatically prepend the given `root path` in order to get the effective key into the base configuration. It is possible to get a jailed configuration from an already jailed configuration. This sub-jail inherits the unjailed base configuration from the jailed configuration by default. :: import configmix config = configmix.load("conf10.py") assert not config.is_jail value = config.getvar_s("tree1.tree2.key4") jailed_config1 = config.jailed(rootpath="tree1") assert jailed_config1.is_jail assert jailed_config2.base is config jailed_config2 = jailed_config2.jailed(rootpath="tree2") assert jailed_config2.is_jail assert jailed_config2.base is config jvalue2 = jailed_config.getvarl_s("key4") assert value == jvalue2 == "get this as `tree1.tree2.key4'" .. note:: A jailed configuration holds a strong reference to the unjailed base configuration. 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_assoc("*.conf", configmix.get_assoc("*.yml")) Allow only files with extension ".cfg" in INI-style -- using the default loader for INI-files:: configmix.clear_assoc() configmix.set_assoc("*.cfg", configmix.get_default_assoc("*.ini")) Only a new configuration file style:: def my_custom_loader(filename): ... return some_dict_alike configmix.mode_loaders["myconfmode"] = my_custom_loader configmix.clear_assoc() configmix.set_assoc("*.my.configuration", "myconfmode") If :py:func:`~configmix.clear_assoc` will not be called then just a *new* configuration file style will be installed. To select the loader not by extension but by an Emacs-compatible mode declaration (e.g. ``mode: yaml``) in the first two lines of a file use:: configmix.set_assoc("*", configmix.try_determine_filemode)
