1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
|
====================
INI File Inheritance
====================
Adds INI-file inheritance to ConfigParser. Note that although it
effectively behaves very similarly to passing multiple files to
ConfigParser's `read()` method, that requires changing the code at
that point. If that is not feasible, or the INI files should dictate
inheritance themselves, then the `iniherit` package is a better
alternative.
Oh, `iniherit` also adds support for environment variable expansion
via ``%(ENV:VARNAME)s``. This really shouldn't be here, but since
`iniherit` supports the ``%(SUPER)s`` expansion, it was "just too
easy" to add envvar expansion as well...
Project Info
============
* Project Page: https://github.com/cadithealth/iniherit
* Bug Tracking: https://github.com/cadithealth/iniherit/issues
TL;DR
=====
Given the following two files, ``base.ini``:
.. code:: ini
[app:main]
name = My Application Name
and ``config.ini``:
.. code:: ini
[DEFAULT]
# the following will cause both "base.ini" and "override.ini" to be
# inherited. the "?" indicates that "override.ini" will be ignored
# if not found; otherwise an error would occur.
%inherit = base.ini ?override.ini
Then the following code will pass the assert:
.. code:: python
import iniherit
cfg = iniherit.SafeConfigParser()
cfg.read('config.ini')
assert cfg.get('app:main', 'name') == 'My Application Name'
Alternatively, the global ConfigParser can be altered to
support inheritance:
.. code:: python
import iniherit
iniherit.mixin.install_globally()
import ConfigParser
cfg = ConfigParser.SafeConfigParser()
cfg.read('config.ini')
assert cfg.get('app:main', 'name') == 'My Application Name'
Note that the call to `install_globally()` must be invoked **before**
any other module imports ConfigParser for the global override to have
an effect.
The command-line program `iniherit` allows flattening of INI files
(i.e. collapsing all inheritance rules), optionally in "watch" mode:
.. code:: bash
$ iniherit --watch --interval 2 --verbose input.ini output.ini
INFO:iniherit.cli:"source.ini" changed; updating output...
INFO:iniherit.cli:"inherited-file.ini" changed; updating output...
^C
Installation
============
Install iniherit via your favorite PyPI package manager works as
follows:
.. code:: bash
# if using python 3+, upgrade your `distribute` package *first*
$ pip install "distribute>=0.7.3"
# then istall with pip:
$ pip install iniherit
Inheritance Mechanism
=====================
INI file inheritance with the `iniherit` package:
* To add inheritance to an INI file, add an ``%inherit`` option to the
"DEFAULT" section of the INI file to inherit all sections of the
specified files. Example:
.. code:: ini
[DEFAULT]
%inherit = base.ini
def_var = Overrides the "def_var" setting, if present,
in the "DEFAULT" section of "base.ini".
[my-app]
sect_var = Overrides the "sect_var" setting, if present,
in the "my-app" section of "base.ini". Other sections in
"base.ini" will also be inherited, even if not specified
here.
* The ``%inherit`` option points to a space-separated, URL-encoded,
list of files to inherit values from, whose values are loaded
depth-first, left-to-right. For example:
.. code:: ini
[DEFAULT]
%inherit = base.ini file-with%20space.ini
* To inherit only a specific section, add the ``%inherit`` option
directly to the applicable section. By default, the section by the
same name will be loaded from the other files, unless the filename
is suffixed with square-bracket enclosed ("[" ... "]"), URL-encoded,
section name. Example:
.. code:: ini
[section]
%inherit = base.ini override.ini[other%20section]
In this case, the "section" section in "base.ini" will be inherited,
followed by the "other section" from "override.ini".
Note that if the inherited section includes interpolation references
to the DEFAULT section, these will **NOT** be carried over! In other
words, inheritance currently ONLY inherits the actual values, not
the interpreted values. Be warned, as this can lead to surprising
results!
If a filename has "[" in the actual name, it can be URL-encoded.
* Filenames, if specified relatively, are taken to be relative to the
current INI file.
* If a filename is prefixed with "?", then it will be loaded
optionally: i.e. if the file does not exist, it will be silently
ignored. If the file does NOT have a "?" prefixed and cannot be
found, then an ``IOError`` will be raised. Note that this is unlike
standard ConfigParser.read() behavior, which silently ignores any
files that cannot be found.
If a filename has "?" as its first character, it can be URL-encoded.
* Note that the actual name of the inherit option can be changed by
changing either ``iniherit.parser.DEFAULT_INHERITTAG`` for a global
effect, or ``ConfigParser.IM_INHERITTAG`` for a per-instance effect.
Substitutions
=============
The `iniherit` package adds the following additional substitution
options:
* ``%(SUPER[:-DEFAULT])s``
Evaluates to the inherited value of the current section/key value.
If the inherited INI does not specify a value and no default is
provided, then an `InterpolationMissingSuperError` is raised. The
"inherited value" is evaluated depth-first. Note that "SUPER" must
be all upper case.
For example, given the following INI files:
.. code:: ini
# base.ini
[loggers]
keys = root, app
.. code:: ini
# config.ini
[DEFAULT]
%inherit = base.ini
[loggers]
keys = %(SUPER)s, auth
wdef = %(SUPER:-more)s or less
nada = %(SUPER)s boom!
Then the following Python will result:
.. code:: python
import iniherit
iniherit.mixin.install_globally()
import ConfigParser
cfg = ConfigParser.SafeConfigParser()
cfg.read('config.ini')
cfg.get('loggers', 'keys') # ==> 'root, app, auth'
cfg.get('loggers', 'wdef') # ==> 'more or less'
cfg.get('loggers', 'nada') # ==> raises InterpolationMissingSuperError
As with standard interpolation errors, the
InterpolationMissingSuperError exception is only raised if/when the
value is requested from the config (with `raw` set to falsy).
* ``%(ENV:VARNAME[:-DEFAULT])s``
Evaluates to the value of the environment variable name "VARNAME".
If the environment variable is not defined and no default is
provided, then an `InterpolationMissingEnvError` is raised. Note
that environment variable names are always case sensitive.
For example, given the following INI file:
.. code:: ini
# config.ini
[section]
home = %(ENV:HOME)s
rdir = %(ENV:RDIR:-/var/run)s
nada = %(ENV:RDIR)s
Then the following Python will result:
.. code:: python
import iniherit
iniherit.mixin.install_globally()
import ConfigParser
cfg = ConfigParser.SafeConfigParser()
cfg.read('config.ini')
import os
os.environ['home'] = '/home/user' # ensure "HOME" envvar exists
os.environ.pop('RDIR', None) # ensure "RDIR" envvar does NOT exist
cfg.get('section', 'home') # ==> '/home/user'
cfg.get('section', 'rdir') # ==> '/var/run'
cfg.get('section', 'nada') # ==> raises InterpolationMissingEnvError
As with standard interpolation errors, the
InterpolationMissingEnvError exception is only raised if/when the
value is requested from the config (with `raw` set to falsy).
Gotchas
=======
* After an inherit-enabled INI file is loaded, the ConfigParser no
longer has knowledge of where a particular option was loaded from or
how it was derived. For this reason, when the `write` method is
called, the ConfigParser generates an INI file without inheritance.
In other words, it flattens the inheritance tree.
.. _ConfigParser: http://docs.python.org/2/library/configparser.html
|