Merge lp:~ricardokirkner/configglue/json-dicts into lp:configglue

Proposed by Ricardo Kirkner
Status: Merged
Approved by: Ricardo Kirkner
Approved revision: 87
Merged at revision: 83
Proposed branch: lp:~ricardokirkner/configglue/json-dicts
Merge into: lp:configglue
Diff against target: 314 lines (+172/-28)
5 files modified
configglue/glue.py (+4/-1)
configglue/parser.py (+0/-19)
configglue/schema.py (+31/-7)
configglue/tests/test_schema.py (+125/-1)
configglue/tests/test_schemaconfig.py (+12/-0)
To merge this branch: bzr merge lp:~ricardokirkner/configglue/json-dicts
Reviewer Review Type Date Requested Status
Ricardo Kirkner Approve
David Owen (community) Approve
Review via email: mp+69863@code.launchpad.net

Commit message

allow DictOption to be specified in the config using json

Description of the change

allow DictOptions to be specified in the config using json

To post a comment you must log in.
85. By Ricardo Kirkner

make DictOption serialize to json if enabled

86. By Ricardo Kirkner

support parsing values from the cmdline when so needed

Revision history for this message
David Owen (dsowen) wrote :

It would be nice to avoid double-parsing dicts passed on the command-line. At least this will preserve their original json-representation for that second parse.

review: Approve
87. By Ricardo Kirkner

renamed DictOption.json to DictOption.parse_json

Revision history for this message
David Owen (dsowen) :
review: Approve
Revision history for this message
Ricardo Kirkner (ricardokirkner) wrote :

Make tarmac happy.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'configglue/glue.py'
2--- configglue/glue.py 2011-07-28 01:31:18 +0000
3+++ configglue/glue.py 2011-07-29 22:24:33 +0000
4@@ -81,7 +81,10 @@
5 def set_value(section, option, value):
6 # if value is not of the right type, cast it
7 if not option.validate(value):
8- value = option.parse(value)
9+ kwargs = {}
10+ if option.require_parser:
11+ kwargs['parser'] = parser
12+ value = option.parse(value, **kwargs)
13 parser.set(section.name, option.name, value)
14
15 for section in schema.sections():
16
17=== modified file 'configglue/parser.py'
18--- configglue/parser.py 2011-07-28 23:10:26 +0000
19+++ configglue/parser.py 2011-07-29 22:24:33 +0000
20@@ -347,25 +347,6 @@
21 if option_obj.require_parser:
22 kwargs = {'parser': self}
23
24- # hook to save extra sections
25- is_dict_option = isinstance(option_obj, DictOption)
26- is_dict_lines_option = (hasattr(option_obj, 'item') and
27- isinstance(option_obj.item, DictOption))
28-
29- # avoid adding implicit sections for dict default value
30- if (is_dict_option or is_dict_lines_option):
31- sections = value.split()
32- self.extra_sections.update(set(sections))
33-
34- if is_dict_option:
35- base = option_obj
36- else:
37- base = option_obj.item
38-
39- for name in sections:
40- nested = base.get_extra_sections(name, self)
41- self.extra_sections.update(set(nested))
42-
43 try:
44 value = option_obj.parse(value, **kwargs)
45 except ValueError, e:
46
47=== modified file 'configglue/schema.py'
48--- configglue/schema.py 2011-07-23 21:11:55 +0000
49+++ configglue/schema.py 2011-07-29 22:24:33 +0000
50@@ -14,6 +14,7 @@
51 #
52 ###############################################################################
53
54+import json
55 from ConfigParser import (
56 NoSectionError,
57 NoOptionError,
58@@ -578,7 +579,7 @@
59
60 def __init__(self, name='', spec=None, strict=False, raw=False,
61 default=NO_DEFAULT, fatal=False, help='', action='store',
62- item=None, short_name=''):
63+ item=None, short_name='', parse_json=True):
64 if spec is None:
65 spec = {}
66 if item is None:
67@@ -586,6 +587,7 @@
68 self.spec = spec
69 self.strict = strict
70 self.item = item
71+ self.parse_json = parse_json
72 super(DictOption, self).__init__(name=name, raw=raw,
73 default=default, fatal=fatal, help=help, action=action,
74 short_name=short_name)
75@@ -606,21 +608,37 @@
76 default[key] = value.default
77 return default
78
79- def parse(self, section, parser=None, raw=False):
80+ def parse(self, value, parser, raw=False):
81 """Parse the given value.
82
83 A *parser* object is used to parse individual dict items.
84 If *raw* is *True*, return the value unparsed.
85
86 """
87- parsed = dict(parser.items(section))
88+ is_json = self.parse_json
89+ if is_json:
90+ try:
91+ parsed = json.loads(value)
92+ is_json = isinstance(parsed, dict)
93+ except Exception:
94+ is_json = False
95+
96+ if not is_json:
97+ # process extra sections
98+ sections = value.split()
99+ parser.extra_sections.update(set(sections))
100+ for name in sections:
101+ nested = self.get_extra_sections(name, parser)
102+ parser.extra_sections.update(set(nested))
103+
104+ parsed = dict(parser.items(value))
105+
106 result = {}
107-
108 # parse config items according to spec
109 for key, value in parsed.items():
110 if self.strict and not key in self.spec:
111- raise ValueError("Invalid key %s in section %s" % (key,
112- section))
113+ raise ValueError("Invalid key %s in section %s" % (
114+ key, value))
115 option = self.spec.get(key, None)
116 if option is None:
117 # option not part of spec, but we are in non-strict mode
118@@ -641,7 +659,7 @@
119 option = self.spec[key]
120 if option.fatal:
121 raise ValueError("No option '%s' in section '%s'" %
122- (key, section))
123+ (key, value))
124 else:
125 if not raw:
126 value = option.default
127@@ -653,6 +671,12 @@
128 def validate(self, value):
129 return isinstance(value, dict)
130
131+ def to_string(self, value):
132+ if self.parse_json:
133+ return json.dumps(value)
134+ else:
135+ return super(DictOption, self).to_string(value)
136+
137 def get_extra_sections(self, section, parser):
138 """Return the list of implicit sections.
139
140
141=== modified file 'configglue/tests/test_schema.py'
142--- configglue/tests/test_schema.py 2011-07-23 21:11:55 +0000
143+++ configglue/tests/test_schema.py 2011-07-29 22:24:33 +0000
144@@ -15,8 +15,12 @@
145 #
146 ###############################################################################
147
148+import textwrap
149 import unittest
150-from ConfigParser import NoOptionError
151+from ConfigParser import (
152+ NoOptionError,
153+ NoSectionError,
154+)
155 from StringIO import StringIO
156
157 from configglue.parser import (
158@@ -737,6 +741,116 @@
159 parser.readfp(config)
160 self.assertEqual(parser.values(), expected_values)
161
162+ def test_parse_dict_no_json(self):
163+ """Test DictOption parse a dict when json is disabled."""
164+ class MySchema(Schema):
165+ foo = self.cls(spec={
166+ 'bar': StringOption(),
167+ 'baz': IntOption(),
168+ 'bla': BoolOption(),
169+ }, parse_json=False)
170+
171+ config = StringIO("""[__main__]
172+foo = mydict
173+[mydict]
174+bar=baz
175+baz=42
176+bla=Yes
177+""")
178+ expected_values = {
179+ '__main__': {
180+ 'foo': {'bar': 'baz', 'baz': 42, 'bla': True}}}
181+
182+ schema = MySchema()
183+ parser = SchemaConfigParser(schema)
184+ parser.readfp(config)
185+ self.assertEqual(parser.values(), expected_values)
186+
187+ def test_parse_dict_json(self):
188+ """Test DictOption parse a json dict."""
189+ class MySchema(Schema):
190+ foo = self.cls(spec={
191+ 'bar': StringOption(),
192+ 'baz': IntOption(),
193+ 'bla': BoolOption(),
194+ })
195+
196+ config = StringIO(textwrap.dedent("""
197+ [__main__]
198+ foo = {
199+ "bar": "baz",
200+ "baz": "42",
201+ "bla": "Yes"}
202+ """))
203+ expected_values = {
204+ '__main__': {
205+ 'foo': {'bar': 'baz', 'baz': 42, 'bla': True}}}
206+
207+ schema = MySchema()
208+ parser = SchemaConfigParser(schema)
209+ parser.readfp(config)
210+ self.assertEqual(parser.values(), expected_values)
211+
212+ def test_parse_dict_json_invalid_json(self):
213+ """Test DictOption parse invalid json."""
214+ class MySchema(Schema):
215+ foo = self.cls(spec={
216+ 'bar': StringOption(),
217+ 'baz': IntOption(),
218+ 'bla': BoolOption(),
219+ })
220+
221+ config = StringIO(textwrap.dedent("""
222+ [__main__]
223+ foo = {'bar': 23}
224+ """))
225+
226+ schema = MySchema()
227+ parser = SchemaConfigParser(schema)
228+ parser.readfp(config)
229+ self.assertRaises(NoSectionError, parser.values)
230+
231+ def test_parse_dict_json_non_dict_json(self):
232+ """Test DictOption parse json not representing a dict."""
233+ class MySchema(Schema):
234+ foo = self.cls(spec={
235+ 'bar': StringOption(),
236+ 'baz': IntOption(),
237+ 'bla': BoolOption(),
238+ })
239+
240+ config = StringIO(textwrap.dedent("""
241+ [__main__]
242+ foo = [1, 2, 3]
243+ """))
244+
245+ schema = MySchema()
246+ parser = SchemaConfigParser(schema)
247+ parser.readfp(config)
248+ self.assertRaises(NoSectionError, parser.values)
249+
250+ def test_parse_dict_no_json_with_json(self):
251+ """Test DictOption parse json when json is disabled."""
252+ class MySchema(Schema):
253+ foo = self.cls(spec={
254+ 'bar': StringOption(),
255+ 'baz': IntOption(),
256+ 'bla': BoolOption(),
257+ }, parse_json=False)
258+
259+ config = StringIO(textwrap.dedent("""
260+ [__main__]
261+ foo = {
262+ "bar": "baz",
263+ "baz": "42",
264+ "bla": "Yes"}
265+ """))
266+
267+ schema = MySchema()
268+ parser = SchemaConfigParser(schema)
269+ parser.readfp(config)
270+ self.assertRaises(NoSectionError, parser.values)
271+
272 def test_parse_raw(self):
273 """Test DictOption parse using raw=True."""
274 class MySchema(Schema):
275@@ -889,6 +1003,16 @@
276 self.assertEqual(option1, option4)
277 self.assertNotEqual(option1, option5)
278
279+ def test_to_string_when_json(self):
280+ option = DictOption()
281+ result = option.to_string({'foo': '1'})
282+ self.assertEqual(result, '{"foo": "1"}')
283+
284+ def test_to_string_when_no_json(self):
285+ option = DictOption(parse_json=False)
286+ result = option.to_string({'foo': '1'})
287+ self.assertEqual(result, str({'foo': '1'}))
288+
289
290 class TestListOfDictOption(unittest.TestCase):
291 def test_parse_lines_of_dict(self):
292
293=== modified file 'configglue/tests/test_schemaconfig.py'
294--- configglue/tests/test_schemaconfig.py 2011-07-28 01:31:18 +0000
295+++ configglue/tests/test_schemaconfig.py 2011-07-29 22:24:33 +0000
296@@ -201,6 +201,18 @@
297 # there is no value for 'foo' due to the missing section
298 self.assertEqual(options, {'foo': None})
299
300+ def test_glue_json_dict(self):
301+ class MySchema(Schema):
302+ foo = DictOption()
303+
304+ parser = SchemaConfigParser(MySchema())
305+ op, options, args = schemaconfigglue(parser,
306+ argv=['--foo', '{"bar": "baz"}'])
307+
308+ self.assertEqual(options, {'foo': '{"bar": "baz"}'})
309+ self.assertEqual(parser.values(),
310+ {'__main__': {'foo': {'bar': 'baz'}}})
311+
312 @patch('configglue.glue.os')
313 def test_glue_environ(self, mock_os):
314 mock_os.environ = {'CONFIGGLUE_FOO_BAR': '42', 'CONFIGGLUE_BAZ': 3}

Subscribers

People subscribed via source and target branches