Merge lp:~ricardokirkner/configglue/declarative-syntax into lp:configglue

Proposed by Ricardo Kirkner
Status: Merged
Approved by: Ricardo Kirkner
Approved revision: 46
Merged at revision: 39
Proposed branch: lp:~ricardokirkner/configglue/declarative-syntax
Merge into: lp:configglue
Diff against target: 145 lines (+43/-34)
2 files modified
configglue/pyschema/schema.py (+13/-34)
tests/pyschema/test_schema.py (+30/-0)
To merge this branch: bzr merge lp:~ricardokirkner/configglue/declarative-syntax
Reviewer Review Type Date Requested Status
John Lenton Approve
Review via email: mp+55003@code.launchpad.net

Commit message

fixes proper schema inheritance using declarative syntax

Description of the change

This branch fixes proper schema inheritance using declarative syntax.

For example:

class SchemaA(Schema):
    foo = ConfigSection()
    foo.bar = IntConfigOption()

class SchemaB(SchemaA):
    foo = deepcopy(SchemaA.foo)
    foo.baz = BoolConfigOption()

In this code, the SchemaB class would have a section called 'foo' with *two* options, 'bar' and 'baz'. When initializing the class instance, all attributes get merged so that they get correctly overridden by the subclass' attributes.

It's necessary to have the section duplicate, as not having it results in a syntax error. By merging sections together at initialization, the end effect is the desired one, however.

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

merged in latest trunk

44. By Ricardo Kirkner

only iterate once over the schema section/options

fix failing tests as a side-effect

Revision history for this message
Łukasz Czyżykowski (lukasz-czyzykowski) wrote :

Wouldn't it be easier to just merge resulting _sections dictionaries instead of digging out all the information from classes?

Something like:
 - run superclass's configuration definition code, which creates internal structures (_sections?),
 - run current class configuration definition code but save it to different structure,
 - merge resulting structures.

This should be simpler and require less usage of __class__ and __mro__ attributes.

45. By Ricardo Kirkner

simplified schema inheritance so that no magic is needed

removed merge method as this is no longer need (will be added as a separate
branch later)

documentation will be added in a separate branch

46. By Ricardo Kirkner

removed conditional as it's not needed

get_config_objects will only return objects of type ConfigSection or
ConfigOption, so _add_item will not receive objects of a different type.

Revision history for this message
John Lenton (chipaca) wrote :

\o/

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'configglue/pyschema/schema.py'
2--- configglue/pyschema/schema.py 2011-03-31 23:15:40 +0000
3+++ configglue/pyschema/schema.py 2011-04-09 15:59:29 +0000
4@@ -16,10 +16,10 @@
5 ###############################################################################
6
7 from copy import deepcopy
8+from inspect import getmembers
9
10
11 __all__ = [
12- 'super_vars',
13 'BoolConfigOption',
14 'ConfigOption',
15 'ConfigSection',
16@@ -36,15 +36,10 @@
17 _internal = object.__dict__.keys() + ['__module__']
18
19
20-def super_vars(obj):
21- """An extended version of vars() that walks all base classes."""
22- items = {}
23- if hasattr(obj, '__mro__'):
24- bases = map(vars, obj.__mro__)
25- map(items.update, bases)
26- else:
27- items = vars(obj)
28- return items
29+def get_config_objects(obj):
30+ objects = ((n, o) for (n, o) in getmembers(obj)
31+ if isinstance(o, (ConfigSection, ConfigOption)))
32+ return objects
33
34
35 class Schema(object):
36@@ -65,45 +60,28 @@
37 configuration files.
38 """
39
40- def __new__(cls):
41- instance = super(Schema, cls).__new__(cls)
42-
43- # override class attributes with instance attributes to correctly
44- # handle schema inheritance
45- schema_attributes = filter(
46- lambda x: x not in _internal and
47- isinstance(getattr(cls, x), (ConfigSection, ConfigOption)),
48- super_vars(cls))
49- for attr in schema_attributes:
50- setattr(instance, attr, deepcopy(getattr(cls, attr)))
51-
52- return instance
53-
54 def __init__(self):
55 self.includes = LinesConfigOption(item=StringConfigOption())
56 self._sections = {}
57 # add section and options to the schema
58- for key in super_vars(self.__class__):
59- value = getattr(self, key)
60- self._add_item(key, value)
61+ for name, item in get_config_objects(self.__class__):
62+ self._add_item(name, item)
63
64 def _add_item(self, name, item):
65 """Add a top-level item to the schema."""
66- if not isinstance(item, (ConfigSection, ConfigOption)):
67- return
68-
69 item.name = name
70 if isinstance(item, ConfigSection):
71 self._add_section(name, item)
72 elif isinstance(item, ConfigOption):
73 self._add_option(name, item)
74+ # override class attributes with instance attributes to correctly
75+ # handle schema inheritance
76+ setattr(self, name, deepcopy(item))
77
78 def _add_section(self, name, section):
79 """Add a top-level section to the schema."""
80 self._sections[name] = section
81- items = super_vars(section).items()
82- options = ((k, v) for (k, v) in items if isinstance(v, ConfigOption))
83- for opt_name, opt in options:
84+ for opt_name, opt in get_config_objects(section):
85 opt.name = opt_name
86 opt.section = section
87
88@@ -151,7 +129,8 @@
89 for s in self.sections():
90 options += self.options(s)
91 elif section.name == '__main__':
92- options = [getattr(self, att) for att in super_vars(self.__class__)
93+ class_config_objects = get_config_objects(self.__class__)
94+ options = [getattr(self, att) for att, _ in class_config_objects
95 if isinstance(getattr(self, att), ConfigOption)]
96 else:
97 options = section.options()
98
99=== modified file 'tests/pyschema/test_schema.py'
100--- tests/pyschema/test_schema.py 2011-03-31 23:15:40 +0000
101+++ tests/pyschema/test_schema.py 2011-04-09 15:59:29 +0000
102@@ -17,6 +17,7 @@
103 ###############################################################################
104
105 import unittest
106+from copy import deepcopy
107 from StringIO import StringIO
108
109 from configglue.pyschema.parser import SchemaConfigParser
110@@ -154,6 +155,35 @@
111 # test on the other schema
112 self.assertFalse(hasattr(self.other.foo, 'baz'))
113
114+ def test_merge_inherited(self):
115+ class SchemaA(Schema):
116+ foo = ConfigSection()
117+ foo.bar = IntConfigOption()
118+ bar = IntConfigOption()
119+
120+ class SchemaB(SchemaA):
121+ foo = deepcopy(SchemaA.foo)
122+ foo.baz = IntConfigOption()
123+
124+ # SchemaB inherits attributes from SchemaA and merges its own
125+ # attributes into
126+ schema = SchemaB()
127+ section_names = set(s.name for s in schema.sections())
128+ option_names = set(o.name for o in schema.options('__main__'))
129+ foo_option_names = set(o.name for o in schema.options('foo'))
130+ self.assertEqual(section_names, set(['__main__', 'foo']))
131+ self.assertEqual(option_names, set(['bar']))
132+ self.assertEqual(foo_option_names, set(['bar', 'baz']))
133+
134+ # SchemaB inheritance does not affect SchemaA
135+ schema = SchemaA()
136+ section_names = set(s.name for s in schema.sections())
137+ option_names = set(o.name for o in schema.options('__main__'))
138+ foo_option_names = set(o.name for o in schema.options('foo'))
139+ self.assertEqual(section_names, set(['__main__', 'foo']))
140+ self.assertEqual(option_names, set(['bar']))
141+ self.assertEqual(foo_option_names, set(['bar']))
142+
143
144 class TestStringConfigOption(unittest.TestCase):
145 def setUp(self):

Subscribers

People subscribed via source and target branches