Merge lp:~ricardokirkner/configglue/environ-vars into lp:configglue
- environ-vars
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 58 |
Proposed branch: | lp:~ricardokirkner/configglue/environ-vars |
Merge into: | lp:configglue |
Diff against target: |
292 lines (+146/-20) 6 files modified
configglue/pyschema/glue.py (+20/-7) configglue/pyschema/parser.py (+18/-0) configglue/pyschema/schema.py (+1/-1) configglue/tests/pyschema/test_parser.py (+49/-2) configglue/tests/pyschema/test_schemaconfig.py (+57/-9) doc/topics/config-file.rst (+1/-1) |
To merge this branch: | bzr merge lp:~ricardokirkner/configglue/environ-vars |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ricardo Kirkner | Approve | ||
Review via email: mp+62584@code.launchpad.net |
Commit message
add support for environment variables
Description of the change
This branch adds support for environment variables.
Environment variables are supported as follows:
1. In the commandline glue
Variable interpolation is done according to the precedence:
a. commandline parameter (aka --foo-bar=4)
b. environment variable (aka CONFIGGLUE_
c. configuration files
d. defaults
2. In the configuration files
Variable interpolation is supported for environment variables using the standard notation
$VAR
${VAR}
If the variable is undefined, it resolves to the default value.
Ubuntu One Auto Pilot (otto-pilot) wrote : | # |
The attempt to merge lp:~ricardokirkner/configglue/environ-vars into lp:configglue failed. Below is the output from the failed tests.
tests.inischema
TestAttributed
test_
test_
test_
tests.inischema
TestCrazyGlue
test_args_win ... [OK]
test_
test_
TestGlue
test_args_win ... [OK]
test_
test_
TestGlue2
test_main ... [OK]
TestGlue3
test_
test_empty ... [OK]
TestGlueBool
test_
test_store_true ... [OK]
TestGlueLines
test_append ... [OK]
test_
test_no_append ... [OK]
test_nothing ... [OK]
TestNoValue
test_args_win ... [OK]
test_
test_
tests.inischema
TestGlueConvertor
test_empty ... [OK]
test_main ... [OK]
test_
test_parser_int ... [OK]
test_
test_
test_simple ... [OK]
tests.inischema
TestParsers
test_bool ... [OK]
test_
test_
test_lines ... [OK]
test_
tests.inischema
TestBackwards
test_
TestParserd
test_
Ubuntu One Auto Pilot (otto-pilot) wrote : | # |
The attempt to merge lp:~ricardokirkner/configglue/environ-vars into lp:configglue failed. Below is the output from the failed tests.
tests.inischema
TestAttributed
test_
test_
test_
tests.inischema
TestCrazyGlue
test_args_win ... [OK]
test_
test_
TestGlue
test_args_win ... [OK]
test_
test_
TestGlue2
test_main ... [OK]
TestGlue3
test_
test_empty ... [OK]
TestGlueBool
test_
test_store_true ... [OK]
TestGlueLines
test_append ... [OK]
test_
test_no_append ... [OK]
test_nothing ... [OK]
TestNoValue
test_args_win ... [OK]
test_
test_
tests.inischema
TestGlueConvertor
test_empty ... [OK]
test_main ... [OK]
test_
test_parser_int ... [OK]
test_
test_
test_simple ... [OK]
tests.inischema
TestParsers
test_bool ... [OK]
test_
test_
test_lines ... [OK]
test_
tests.inischema
TestBackwards
test_
TestParserd
test_
Preview Diff
1 | === modified file 'configglue/pyschema/glue.py' |
2 | --- configglue/pyschema/glue.py 2011-06-13 12:57:26 +0000 |
3 | +++ configglue/pyschema/glue.py 2011-06-19 16:52:32 +0000 |
4 | @@ -15,6 +15,7 @@ |
5 | # |
6 | ############################################################################### |
7 | |
8 | +import os |
9 | import sys |
10 | from optparse import OptionParser |
11 | from collections import namedtuple |
12 | @@ -67,15 +68,27 @@ |
13 | og.add_option('--' + long_name(option), **kwargs) |
14 | options, args = op.parse_args(argv) |
15 | |
16 | + def set_value(section, option, value): |
17 | + # if value is not of the right type, cast it |
18 | + if not option.validate(value): |
19 | + value = option.parse(value) |
20 | + parser.set(section.name, option.name, value) |
21 | + |
22 | for section in schema.sections(): |
23 | for option in section.options(): |
24 | - value = getattr(options, opt_name(option)) |
25 | - if parser.get(section.name, option.name) != value: |
26 | - # the value has been overridden by an argument |
27 | - if isinstance(value, basestring): |
28 | - # parse it to the right type if it's a string |
29 | - value = option.parse(value) |
30 | - parser.set(section.name, option.name, value) |
31 | + # 1. op value != parser value |
32 | + # 2. op value == parser value != env value |
33 | + # 3. op value == parser value == env value or not env value |
34 | + |
35 | + op_value = getattr(options, opt_name(option)) |
36 | + parser_value = parser.get(section.name, option.name) |
37 | + env_value = os.environ.get("CONFIGGLUE_{0}".format( |
38 | + long_name(option).upper())) |
39 | + |
40 | + if op_value != parser_value: |
41 | + set_value(section, option, op_value) |
42 | + elif env_value is not None and env_value != parser_value: |
43 | + set_value(section, option, env_value) |
44 | |
45 | return op, options, args |
46 | |
47 | |
48 | === modified file 'configglue/pyschema/parser.py' |
49 | --- configglue/pyschema/parser.py 2011-06-13 18:22:12 +0000 |
50 | +++ configglue/pyschema/parser.py 2011-06-19 16:52:32 +0000 |
51 | @@ -19,6 +19,7 @@ |
52 | import collections |
53 | import copy |
54 | import os |
55 | +import re |
56 | import string |
57 | |
58 | from ConfigParser import ( |
59 | @@ -435,6 +436,15 @@ |
60 | assert isinstance(result, basestring) |
61 | return result |
62 | |
63 | + def interpolate_environment(self, rawval, raw=False): |
64 | + if raw: |
65 | + return rawval |
66 | + # interpolate environment variables |
67 | + pattern = re.sub(r'\${([A-Z_]+)}', r'%(\1)s', rawval) |
68 | + pattern = re.sub(r'\$([A-Z_]+)', r'%(\1)s', pattern) |
69 | + interpolated = pattern % os.environ |
70 | + return interpolated |
71 | + |
72 | def _get_default(self, section, option): |
73 | # mark the value as not initialized to be able to have a None default |
74 | marker = object() |
75 | @@ -508,6 +518,14 @@ |
76 | self._sections[section] = {} |
77 | self.set(section, option, value) |
78 | |
79 | + # interpolate environment variables |
80 | + if isinstance(value, basestring): |
81 | + try: |
82 | + value = self.interpolate_environment(value, raw=raw) |
83 | + except KeyError: |
84 | + # interpolation failed, fallback to default value |
85 | + value = self._get_default(section, option) |
86 | + |
87 | if parse: |
88 | value = self.parse(section, option, value) |
89 | return value |
90 | |
91 | === modified file 'configglue/pyschema/schema.py' |
92 | --- configglue/pyschema/schema.py 2011-06-18 15:17:49 +0000 |
93 | +++ configglue/pyschema/schema.py 2011-06-19 16:52:32 +0000 |
94 | @@ -106,7 +106,7 @@ |
95 | |
96 | To define your own configuration schema you should: |
97 | 1- Inherit from Schema |
98 | - 2- Add Option and Sections as class attributes. |
99 | + 2- Add Options and Sections as class attributes. |
100 | |
101 | With that your whole configuration schema is defined, and you can now |
102 | load configuration files. |
103 | |
104 | === modified file 'configglue/tests/pyschema/test_parser.py' |
105 | --- configglue/tests/pyschema/test_parser.py 2011-06-13 21:11:16 +0000 |
106 | +++ configglue/tests/pyschema/test_parser.py 2011-06-19 16:52:32 +0000 |
107 | @@ -32,6 +32,7 @@ |
108 | |
109 | from mock import ( |
110 | Mock, |
111 | + patch, |
112 | patch_object, |
113 | ) |
114 | |
115 | @@ -261,6 +262,53 @@ |
116 | self.assertRaises(InterpolationMissingOptionError, parser.get, |
117 | 'foo', 'bar') |
118 | |
119 | + @patch('configglue.pyschema.parser.os') |
120 | + def test_interpolate_environment_basic_syntax(self, mock_os): |
121 | + mock_os.environ = {'PATH': 'foo'} |
122 | + parser = SchemaConfigParser(Schema()) |
123 | + result = parser.interpolate_environment("$PATH") |
124 | + self.assertEqual(result, 'foo') |
125 | + |
126 | + @patch('configglue.pyschema.parser.os') |
127 | + def test_interpolate_environment_extended_syntax(self, mock_os): |
128 | + mock_os.environ = {'PATH': 'foo'} |
129 | + parser = SchemaConfigParser(Schema()) |
130 | + result = parser.interpolate_environment("${PATH}") |
131 | + self.assertEqual(result, 'foo') |
132 | + |
133 | + @patch('configglue.pyschema.parser.os') |
134 | + def test_interpolate_environment_in_config(self, mock_os): |
135 | + mock_os.environ = {'PYTHONPATH': 'foo', 'PATH': 'bar'} |
136 | + class MySchema(Schema): |
137 | + pythonpath = StringOption() |
138 | + path = StringOption() |
139 | + |
140 | + config = StringIO("[__main__]\npythonpath=${PYTHONPATH}\npath=$PATH") |
141 | + parser = SchemaConfigParser(MySchema()) |
142 | + parser.readfp(config) |
143 | + self.assertEqual(parser.values('__main__'), |
144 | + {'pythonpath': 'foo', 'path': 'bar'}) |
145 | + |
146 | + @patch('configglue.pyschema.parser.os') |
147 | + def test_get_with_environment_var(self, mock_os): |
148 | + mock_os.environ = {'FOO': '42'} |
149 | + class MySchema(Schema): |
150 | + foo = IntOption() |
151 | + |
152 | + config = StringIO("[__main__]\nfoo=$FOO") |
153 | + parser = SchemaConfigParser(MySchema()) |
154 | + parser.readfp(config) |
155 | + self.assertEqual(parser.get('__main__', 'foo'), 42) |
156 | + |
157 | + def test_get_without_environment_var(self): |
158 | + class MySchema(Schema): |
159 | + foo = IntOption() |
160 | + |
161 | + config = StringIO("[__main__]\nfoo=$FOO") |
162 | + parser = SchemaConfigParser(MySchema()) |
163 | + parser.readfp(config) |
164 | + self.assertEqual(parser.get('__main__', 'foo'), 0) |
165 | + |
166 | def test_get_interpolation_keys_string(self): |
167 | """Test get_interpolation_keys for a string.""" |
168 | class MySchema(Schema): |
169 | @@ -1043,8 +1091,7 @@ |
170 | def test_extra_sections_with_nested_dicts_strict(self): |
171 | """Test parser.is_valid w/ extra sections in a nested dict (strict).""" |
172 | class MySchema(Schema): |
173 | - foo = DictOption(spec={'bar': DictOption()}, |
174 | - strict=True) |
175 | + foo = DictOption(spec={'bar': DictOption()}, strict=True) |
176 | |
177 | config = StringIO(""" |
178 | [__main__] |
179 | |
180 | === modified file 'configglue/tests/pyschema/test_schemaconfig.py' |
181 | --- configglue/tests/pyschema/test_schemaconfig.py 2011-06-13 21:21:16 +0000 |
182 | +++ configglue/tests/pyschema/test_schemaconfig.py 2011-06-19 16:52:32 +0000 |
183 | @@ -17,10 +17,15 @@ |
184 | ############################################################################### |
185 | |
186 | import unittest |
187 | +import os |
188 | import sys |
189 | from StringIO import StringIO |
190 | |
191 | -from mock import patch, Mock |
192 | +from mock import ( |
193 | + Mock, |
194 | + patch, |
195 | + patch_object, |
196 | +) |
197 | |
198 | from configglue.pyschema.glue import ( |
199 | configglue, |
200 | @@ -32,6 +37,8 @@ |
201 | ConfigSection, |
202 | IntOption, |
203 | Option, |
204 | + Section, |
205 | + IntOption, |
206 | Schema, |
207 | Section, |
208 | StringOption, |
209 | @@ -154,14 +161,13 @@ |
210 | self.assertEqual(self.parser.values(), |
211 | {'foo': {'bar': 0}, '__main__': {'baz': 1}}) |
212 | |
213 | - _argv = sys.argv |
214 | - sys.argv = [] |
215 | - |
216 | - op, options, args = schemaconfigglue(self.parser) |
217 | - self.assertEqual(self.parser.values(), |
218 | - {'foo': {'bar': 0}, '__main__': {'baz': 1}}) |
219 | - |
220 | - sys.argv = _argv |
221 | + _argv, sys.argv = sys.argv, [] |
222 | + try: |
223 | + op, options, args = schemaconfigglue(self.parser) |
224 | + self.assertEqual(self.parser.values(), |
225 | + {'foo': {'bar': 0}, '__main__': {'baz': 1}}) |
226 | + finally: |
227 | + sys.argv = _argv |
228 | |
229 | def test_glue_section_option(self): |
230 | """Test schemaconfigglue overriding one option.""" |
231 | @@ -175,6 +181,48 @@ |
232 | self.assertEqual(self.parser.values(), |
233 | {'foo': {'bar': 2}, '__main__': {'baz': 0}}) |
234 | |
235 | + @patch('configglue.pyschema.glue.os') |
236 | + def test_glue_environ(self, mock_os): |
237 | + mock_os.environ = {'CONFIGGLUE_FOO_BAR': '42', 'CONFIGGLUE_BAZ': 3} |
238 | + config = StringIO("[foo]\nbar=1") |
239 | + self.parser.readfp(config) |
240 | + |
241 | + _argv, sys.argv = sys.argv, ['prognam'] |
242 | + try: |
243 | + op, options, args = schemaconfigglue(self.parser) |
244 | + self.assertEqual(self.parser.values(), |
245 | + {'foo': {'bar': 42}, '__main__': {'baz': 3}}) |
246 | + finally: |
247 | + sys.argv = _argv |
248 | + |
249 | + @patch('configglue.pyschema.glue.os') |
250 | + def test_glue_environ_bad_name(self, mock_os): |
251 | + mock_os.environ = {'FOO_BAR': 2, 'BAZ': 3} |
252 | + config = StringIO("[foo]\nbar=1") |
253 | + self.parser.readfp(config) |
254 | + |
255 | + _argv, sys.argv = sys.argv, ['prognam'] |
256 | + try: |
257 | + op, options, args = schemaconfigglue(self.parser) |
258 | + self.assertEqual(self.parser.values(), |
259 | + {'foo': {'bar': 1}, '__main__': {'baz': 0}}) |
260 | + finally: |
261 | + sys.argv = _argv |
262 | + |
263 | + def test_glue_environ_precedence(self): |
264 | + with patch_object(os, 'environ', |
265 | + {'CONFIGGLUE_FOO_BAR': '42', 'BAR': '1'}): |
266 | + |
267 | + config = StringIO("[foo]\nbar=$BAR") |
268 | + self.parser.readfp(config) |
269 | + |
270 | + _argv, sys.argv = sys.argv, ['prognam'] |
271 | + try: |
272 | + op, options, args = schemaconfigglue(self.parser) |
273 | + self.assertEqual(self.parser.get('foo', 'bar'), 42) |
274 | + finally: |
275 | + sys.argv = _argv |
276 | + |
277 | def test_ambiguous_option(self): |
278 | """Test schemaconfigglue when an ambiguous option is specified.""" |
279 | class MySchema(Schema): |
280 | |
281 | === modified file 'doc/topics/config-file.rst' |
282 | --- doc/topics/config-file.rst 2011-06-13 19:21:19 +0000 |
283 | +++ doc/topics/config-file.rst 2011-06-19 16:52:32 +0000 |
284 | @@ -35,7 +35,7 @@ |
285 | |
286 | A few special considerations have to be kept in mind while working with these |
287 | configuration files. As ConfigParser requires a config file to have at least |
288 | -one section defined, any top-level Option are added to an implicitely |
289 | +one section defined, any top-level Options are added to an implicitely |
290 | defined section called ``__main__``. |
291 | |
292 | Therefore, if you have a schema like:: |
Yes, I like it :)