Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
355 lines (248 sloc) 11.8 KB
Guide
=====
Sections
--------
A :class:`~profig.Config` object can be used directly without any initialization::
import profig
cfg = profig.Config()
cfg['server.host'] = '8.8.8.8'
cfg['server.port'] = 8181
Configuration keys are :samp:`.` delimited strings where each name
refers to a section. In the example above, `server` is a section name, and
`host` and `port` are sections which are assigned values.
For a quick look at the hierarchical structure of the options, you can easily
get the dict representation::
>>> cfg.as_dict()
{'server': {'host': '8.8.8.8', 'port': 8181}}
Section can also be organized and accessed in hierarchies::
>>> cfg['server.port']
8181
>>> cfg.section('server').as_dict()
{'host': 'localhost', 'port': 8181}
Initialization
--------------
One of the most useful features of the :mod:`profig` library is the ability
to initialize the options that are expected to be set on a
:class:`~profig.Config` object.
If you were to sync the following config file without any initialization:
.. code-block:: ini
[server]
host = 127.0.0.1
port = 8080
The port number would be interpreted as a string, and you would have to
convert it manually. You can avoid doing this each time you access the value
by using the :meth:`~profig.ConfigSection.init` method::
>>> cfg.init('server.host', '127.0.0.1')
>>> cfg.init('server.port', 8080, int)
The second argument to :meth:`~profig.ConfigSection.init` specifies a default
value for the option. The third argument is optional and is used by an internal
:class:`~profig.Coercer` to automatically (de)serialize any value that is set
for the option. If the third argument is not provided, the type of the default
value will be used to select the correct coercer.
Strict Mode
-----------
Strict mode can be enabled by passing *strict=True* to the
:class:`~profig.Config` constructor::
>>> cfg = profig.Config(strict=True)
# or
>>> cfg.strict = True
By default, :class:`~profig.Config` objects can be assigned a new value
simply by setting the value on a key, just as with the standard `dict`.
When strict mode is enabled, however, assignments are only allowed for keys
that have already been initialized using :meth:`~profig.ConfigSection.init`.
Strict mode prevents typos in the names of configuration options. In addition,
any sync performed while in strict mode will clear old or deprecated options
from the output file.
Automatic Typing (Coercion)
---------------------------
Automatic typing is referred to as "coercion" within the :mod:`profig` library.
Functions that adapt (object -> string) or convert (string -> object) values
are referred to as "coercers".
- Values are "adapted" into strings.
- Strings are "converted" into values.
Defining Types
~~~~~~~~~~~~~~
Type information can be assigned by initializing a key with a default value::
>>> cfg.init('server.port', 8080)
>>> cfg.sync()
>>> port = cfg['server.port']
>>> port
9090
>>> type(port)
<class 'int'>
When a type cannot be easily inferred, a specific type can by specified as
the third argument to :meth:`~profig.ConfigSection.init`::
>>> cfg.init('editor.open_files', [], 'path_list')
In this case we are using a custom coercer type that has been registered to
handle lists of paths.
Now, supposing we have the following in a config file:
.. code-block:: ini
[editor]
open_files = profig.py:test_profig.py
We can the sync the config file, and access the stored value::
>>> cfg.sync()
>>> cfg['editor.open_files']
['profig.py', 'test_profig.py']
We can look at how the value is stored in the config file by using the
section directly::
>>> sect = cfg.section('editor.open_files')
>>> sect.value(convert=False)
'profig.py:test_profig.py'
.. note::
When using coercion, it is important to take into account that, by
default, no validation of the values is performed. It is, however, simple
to have a coercer validate as well. See `Using Coercers as Validators`_
for details.
Adding Coercers
~~~~~~~~~~~~~~~
Functions that define how an object is coerced (adapted or converted) can be
defined using the :class:`~profig.Coercer` object that is accessible as an
attribute of the root :class:`~profig.Config` object.
Defining a new coercer (or overriding an existing one) is as simple as passing
two functions to :meth:`~profig.Coercer.register`. For example, a simple
coercer for a *bool* type could be defined like this::
>>> cfg.coercer.register(bool, lambda x: str(int(x)), lambda x: bool(int(x)))
The first argument to :meth:`~profig.Coercer.register` represents a type value
that will be used to determine the correct coercer to use. A class object,
when available, is most convenient, because it allows using a call to
`type(obj)` to determine which coercer to use. However, any value can be
used for the registration, including, e.g. strings or tuples.
The second argument specifies how to *adapt* a value to a string, while the
third argument specifies host to *convert* a string to a value.
Using Coercers as Validators
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
By default, a coercer will only raise exceptions if there is a fundamental
incompatibility in the values it is trying to coerce. What this means is that
even if you register a key with a type of `int`, no restriction is placed on
the value that can actually be set for the key. This can lead to unexpected
errors::
>>> cfg.init('default.value', 1) # sets the type of the key to 'int'
>>> cfg['default.value'] = 4.5 # a float value can be set
# sync the float value to the config file
>>> buf = io.BytesIO()
>>> cfg.sync(buf)
>>> buf.getvalue()
'[default]\nvalue: 4.5\n'
# here, we change the float value
>>> buf = io.BytesIO('[default]\nvalue: 5.6\n')
>>> cfg.sync(buf)
>>> cfg['default.value'] # this now raises an exception
Traceback (most recent call last):
...
ConvertError: invalid literal for int() with base 10: '5.6'
This behavior can be changed by defining or overriding coercers in order to,
for example, raise exceptions for unexpected ranges of inputs or other
restrictions that should be in place for a given configuration value.
Synchronization
---------------
A *sync* is a combination of the read and write operations, executed in the
following order:
1. Read in the changed values from all sources, except the values that have
changed on the config object since the last time it was synced.
2. Write any values changed on the config object back to the primary
source.
This is done using the :meth:`~profig.ConfigSection.sync` method::
>>> cfg.sync('app.cfg')
After a call to :meth:`~profig.ConfigSection.sync`, a new file with the following
contents will be created in the current working directory:
.. code-block:: ini
[server]
host = 127.0.0.1
port = 8080
Sources
-------
The sources a config object uses when it syncs can also be set in the
:class:`~profig.Config` constructor::
>>> cfg = profig.Config('app.cfg')
Sources can be either paths or file objects. Multiple sources can be provided::
>>> cfg = profig.Config('<appdir>/app.cfg', '<userdir>/app.cfg', '<sysdir>/app.cfg')
If more than one `source` is provided then a sync will update the config
object with values from each of the sources in the order given. It will
then write the values changed on the config object back to the first source.
Once sources have been provided, they will be used for any future syncs.
>>> cfg.sync()
Formats
-------
A :class:`~profig.Format` subclass defines how a configuration should be
read/written from/to a source.
:mod:`profig` provides :class:`~profig.Format` subclasses for both INI
formatted files, and the Windows registry.
INI
~~~
:class:`~profig.INIFormat`
Syncs configuration with a file based on the standard INI format.
Because :class:`~profig.INIFormat` is the default, an INI file can be sourced
as follows::
>>> cfg = profig.Config('~/.config/black_knight.cfg')
The INI file format has no strict standard_, however :mod:`profig` tries not
to stray far from the most commonly supported features. In particular, it
should read any INI file that the standard library's configparser_ can read.
If it does not, please file an issue_ so that the discrepency can be fixed.
The only deviation :mod:`profig` makes is to support the fact that it is
possible to set a value on any :class:`~profig.ConfigSection`, including
those at the top-level, which become section headers when output to an INI
file. This is represented in the INI files as values set on the header::
[server] = true
host = localhost
Windows Registry
~~~~~~~~~~~~~~~~
:class:`~profig.RegistryFormat`
Syncs configuration with the Windows registry.
To use the :class:`~profig.RegistryFormat`, you have to specify which format
to use and specify a path relative to *HKEY_CURRENT_USER* (configurable as a
class attribute)::
>>> cfg = profig.Config(r'Software\BlackKnight', format='registry')
:class:`~profig.RegistryFormat` is, of course, only available on Windows
machines.
Support for additional formats can be added easily by subclassing
:class:`~profig.Format`.
Defining a new Format
~~~~~~~~~~~~~~~~~~~~~
To add support for a new format you must subclass :meth:`~profig.Format` and
override the, :meth:`~profig.Format.read` and :meth:`~profig.Format.write`
methods.
:meth:`~profig.Format.read` should assign values to
:attr:`~profig.Format.config`, which is a local instance of the
:class:`~profig.Config` object. A *context* value can optionally be returned
which will be passed to :meth:`~profig.Format.write` during a sync. It can be
used, for example, to track comments and ordering of the source. `None` can be
returned if no context is needed.
:meth:`~profig.Format.write` accepts any context returned from a call to
:meth:`~profig.Format.read` and should read the values to write from
:attr:`~profig.Format.config`.
Unicode
-------
:mod:`profig` fully supports unicode. The encoding to use for all
encoding/decoding operations can be set in the :class:`~profig.Config`
constructor::
>>> cfg = profig.Config(encoding='utf-8')
The default is to use the system's preferred encoding.
Keys are handled in a special way. Both byte-string keys and unicode keys
are considered equivalent, but are stored internally as unicode keys. If
a byte-string key is used, it will be decoded using the configured encoding.
Values are coerced into a unicode representation before being output to a
config file. Note that this means that by specifically storing a byte-string
as a value, :mod:`profig` will interpret the value as binary data. The specifics
of how binary data is stored depends on the format being used. The default
format (:class:`~profig.INIFormat`) operates on binary files, therefore binary
data is written directly to its sources.
So, if we consider the following examples using a Python 2 interpreter,
where `str` objects are byte-strings::
>>> cfg['default.a'] = '\x00asdf'
Will be stored to a config file as binary data:
.. code-block:: ini
[default]
a = \x00asdf
If this is not the desired behavior, there are other options available when
calling :meth:`~profig.ConfigSection.init`. First, we can explicitely use
unicode values::
>>> cfg.init('a', u'asdf')
This ensures that only data matching the set encoding will be accepted.
If we expect the data to be binary data, but don't want to store it directly,
we can use one of the available encodings: 'hex', and 'base64'::
>>> cfg.init('a', 'asdf', 'hex')
Or third, we can register different coercers for byte-strings::
>>> cfg.coercer.register(bytes, compress, decompress)
.. _standard: http://en.wikipedia.org/wiki/INI_file
.. _configparser: https://docs.python.org/3/library/configparser.html
.. _issue: https://bitbucket.org/dhagrow/profig/issues
You can’t perform that action at this time.