Merge lp:~ricardokirkner/django-configglue/770093-schema-introspection into lp:django-configglue
- 770093-schema-introspection
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Anthony Lenton | ||||
Approved revision: | 61 | ||||
Merged at revision: | 55 | ||||
Proposed branch: | lp:~ricardokirkner/django-configglue/770093-schema-introspection | ||||
Merge into: | lp:django-configglue | ||||
Diff against target: |
786 lines (+405/-118) 4 files modified
django_configglue/schema.py (+169/-55) django_configglue/tests/helpers.py (+57/-0) django_configglue/tests/test_configglue.py (+146/-59) django_configglue/tests/test_settings.py (+33/-4) |
||||
To merge this branch: | bzr merge lp:~ricardokirkner/django-configglue/770093-schema-introspection | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Anthony Lenton (community) | Approve | ||
Review via email: mp+67637@code.launchpad.net |
Commit message
changed default behaviour to automatically create a schema if the requested one is not found
Description of the change
Overview
========
changed default behaviour to automatically create a schema if none is found
Details
=======
The DjangoSchemaFactory class has been given a utility method to build a
schema out of a dictionary of options. Using this utility, the .get method can
dynamically create a new schema when the requested one is not found, instead
of having to fallback to previous schema versions.
The default behaviour has thus been changed to allow the DjangoSchemaFactory
to create a schema dynamically if the requested one is not found.
- 56. By Ricardo Kirkner
-
handle inner item type for complex types
DictOption and ListOption can be passed an item parameter to parse individual
items. Detect this item type when building a schema dynamically. - 57. By Ricardo Kirkner
-
maintain existing default behaviour on DjangoSchemaFac
tory.get - 58. By Ricardo Kirkner
-
moved helpers for tests schemas to a separate testcase
- 59. By Ricardo Kirkner
-
make sure schema is generated correctly
- added a test for comparing the generated schema for a known version against the
defined schema for that version.
- triggered a cascade of errors which resulted in several updates to the tests
and schema options defaults.
- fixed incorrect propagation of deprecated options due to schema inheritance - 60. By Ricardo Kirkner
-
properly remove deprecated CACHE_BACKEND setting
- 61. By Ricardo Kirkner
-
added missing tests
Preview Diff
1 | === modified file 'django_configglue/schema.py' |
2 | --- django_configglue/schema.py 2011-06-27 20:05:32 +0000 |
3 | +++ django_configglue/schema.py 2011-07-14 03:16:30 +0000 |
4 | @@ -1,5 +1,6 @@ |
5 | # Copyright 2010 Canonical Ltd. This software is licensed under the |
6 | # GNU Lesser General Public License version 3 (see the file LICENSE). |
7 | +import inspect |
8 | import logging |
9 | |
10 | from configglue.pyschema.schema import ( |
11 | @@ -13,6 +14,8 @@ |
12 | TupleOption, |
13 | ) |
14 | from django import get_version |
15 | +from django.conf import global_settings |
16 | +from django.conf.project_template import settings as project_settings |
17 | |
18 | |
19 | # As in django.conf.global_settings: |
20 | @@ -32,6 +35,32 @@ |
21 | return result |
22 | |
23 | |
24 | +def derivate_django_schema(schema, exclude=None): |
25 | + """Return a modified version of a schema. |
26 | + |
27 | + The source schema *must* have a 'version' attribute and |
28 | + a 'django' section. |
29 | + |
30 | + The resulting schema is almost a copy of the original one, except |
31 | + for excluded options in the 'django' section. |
32 | + """ |
33 | + if not exclude: |
34 | + return schema |
35 | + |
36 | + # create the schema class |
37 | + cls = type(schema.__name__, (schema,), {'version': schema.version}) |
38 | + # include all non-excluded options |
39 | + options = {} |
40 | + for option in schema().django.options(): |
41 | + if option.name in exclude: |
42 | + continue |
43 | + options[option.name] = option |
44 | + # create the 'django' section |
45 | + django_section = type('django', (Section,), options) |
46 | + setattr(cls, 'django', django_section) |
47 | + return cls |
48 | + |
49 | + |
50 | class BaseDjangoSchema(Schema): |
51 | version = '1.0.2 final' |
52 | |
53 | @@ -53,7 +82,7 @@ |
54 | help="Whether to use the 'Etag' header. This saves bandwidth but " |
55 | "slows down performance.") |
56 | |
57 | - admins = ListOption(item=TupleOption(2), default=[], |
58 | + admins = ListOption(item=TupleOption(length=2), default=[], |
59 | help="People who get code error notifications. In the format " |
60 | "(('Full Name', 'email@domain.com'), " |
61 | "('Full Name', 'anotheremail@domain.com'))") |
62 | @@ -136,7 +165,7 @@ |
63 | locale_paths = ListOption(item=StringOption()) |
64 | language_cookie_name = StringOption(default='django_language') |
65 | |
66 | - managers = ListOption(item=TupleOption(2), default=[], |
67 | + managers = ListOption(item=TupleOption(length=2), default=[], |
68 | help="Not-necessarily-technical managers of the site. They get broken " |
69 | "link notifications and other various e-mails") |
70 | |
71 | @@ -464,14 +493,18 @@ |
72 | #################### |
73 | |
74 | site_id = IntOption(default=1) |
75 | - root_urlconf = StringOption(default='urls') |
76 | - |
77 | - |
78 | -class Django112Schema(BaseDjangoSchema): |
79 | + root_urlconf = StringOption(default='{{ project_name }}.urls') |
80 | + |
81 | + |
82 | +Django112Base = derivate_django_schema( |
83 | + BaseDjangoSchema, exclude=['jing_path']) |
84 | + |
85 | + |
86 | +class Django112Schema(Django112Base): |
87 | version = '1.1.2' |
88 | |
89 | # sections |
90 | - class django(BaseDjangoSchema.django): |
91 | + class django(Django112Base.django): |
92 | |
93 | ################ |
94 | # CORE # |
95 | @@ -483,7 +516,7 @@ |
96 | help="Languages we provide translations for, out of the box. " |
97 | "The language name should be the utf-8 encoded local name " |
98 | "for the language", |
99 | - default = [ |
100 | + default=[ |
101 | ('ar', gettext_noop('Arabic')), |
102 | ('bg', gettext_noop('Bulgarian')), |
103 | ('bn', gettext_noop('Bengali')), |
104 | @@ -558,7 +591,7 @@ |
105 | help="Languages we provide translations for, out of the box. " |
106 | "The language name should be the utf-8 encoded local name " |
107 | "for the language", |
108 | - default = [ |
109 | + default=[ |
110 | ('ar', gettext_noop('Arabic')), |
111 | ('bg', gettext_noop('Bulgarian')), |
112 | ('bn', gettext_noop('Bengali')), |
113 | @@ -637,6 +670,16 @@ |
114 | 'host': StringOption(), |
115 | 'port': StringOption(), |
116 | }), |
117 | + default={ |
118 | + 'default': { |
119 | + 'ENGINE': 'django.db.backends.', |
120 | + 'NAME': '', |
121 | + 'USER': '', |
122 | + 'PASSWORD': '', |
123 | + 'HOST': '', |
124 | + 'PORT': '', |
125 | + } |
126 | + } |
127 | ) |
128 | database_routers = ListOption( |
129 | item=StringOption(), |
130 | @@ -651,7 +694,7 @@ |
131 | |
132 | installed_apps = ListOption(item=StringOption(), |
133 | help="List of strings representing installed apps", |
134 | - default = [ |
135 | + default=[ |
136 | 'django.contrib.auth', |
137 | 'django.contrib.contenttypes', |
138 | 'django.contrib.sessions', |
139 | @@ -662,7 +705,7 @@ |
140 | template_loaders = ListOption(item=StringOption(), |
141 | help="List of callables that know how to import templates from " |
142 | "various sources", |
143 | - default = [ |
144 | + default=[ |
145 | 'django.template.loaders.filesystem.Loader', |
146 | 'django.template.loaders.app_directories.Loader', |
147 | ]) |
148 | @@ -673,7 +716,7 @@ |
149 | "context. Each one should be a callable that takes the request " |
150 | "object as its only parameter and returns a dictionary to add to " |
151 | "the context", |
152 | - default = [ |
153 | + default=[ |
154 | 'django.contrib.auth.context_processors.auth', |
155 | 'django.core.context_processors.debug', |
156 | 'django.core.context_processors.i18n', |
157 | @@ -696,34 +739,34 @@ |
158 | date_input_formats = ListOption( |
159 | item=StringOption(), |
160 | default=[ |
161 | - '%%Y-%%m-%%d', '%%m/%%d/%%Y', '%%m/%%d/%%y', # '2006-10-25', '10/25/2006', '10/25/06' |
162 | - '%%b %%d %%Y', '%%b %%d, %%Y', # 'Oct 25 2006', 'Oct 25, 2006' |
163 | - '%%d %%b %%Y', '%%d %%b, %%Y', # '25 Oct 2006', '25 Oct, 2006' |
164 | - '%%B %%d %%Y', '%%B %%d, %%Y', # 'October 25 2006', 'October 25, 2006' |
165 | - '%%d %%B %%Y', '%%d %%B, %%Y', # '25 October 2006', '25 October, 2006' |
166 | + '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06' |
167 | + '%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006' |
168 | + '%d %b %Y', '%d %b, %Y', # '25 Oct 2006', '25 Oct, 2006' |
169 | + '%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006' |
170 | + '%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006' |
171 | ], |
172 | help="Default formats to be used when parsing dates from input " |
173 | "boxes, in order") |
174 | time_input_formats = ListOption( |
175 | item=StringOption(), |
176 | default=[ |
177 | - '%%H:%%M:%%S', # '14:30:59' |
178 | - '%%H:%%M', # '14:30' |
179 | + '%H:%M:%S', # '14:30:59' |
180 | + '%H:%M', # '14:30' |
181 | ], |
182 | help="Default formats to be used when parsing times from input " |
183 | "boxes, in order") |
184 | datetime_input_formats = ListOption( |
185 | item=StringOption(), |
186 | default=[ |
187 | - '%%Y-%%m-%%d %%H:%%M:%%S', # '2006-10-25 14:30:59' |
188 | - '%%Y-%%m-%%d %%H:%%M', # '2006-10-25 14:30' |
189 | - '%%Y-%%m-%%d', # '2006-10-25' |
190 | - '%%m/%%d/%%Y %%H:%%M:%%S', # '10/25/2006 14:30:59' |
191 | - '%%m/%%d/%%Y %%H:%%M', # '10/25/2006 14:30' |
192 | - '%%m/%%d/%%Y', # '10/25/2006' |
193 | - '%%m/%%d/%%y %%H:%%M:%%S', # '10/25/06 14:30:59' |
194 | - '%%m/%%d/%%y %%H:%%M', # '10/25/06 14:30' |
195 | - '%%m/%%d/%%y', # '10/25/06' |
196 | + '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' |
197 | + '%Y-%m-%d %H:%M', # '2006-10-25 14:30' |
198 | + '%Y-%m-%d', # '2006-10-25' |
199 | + '%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59' |
200 | + '%m/%d/%Y %H:%M', # '10/25/2006 14:30' |
201 | + '%m/%d/%Y', # '10/25/2006' |
202 | + '%m/%d/%y %H:%M:%S', # '10/25/06 14:30:59' |
203 | + '%m/%d/%y %H:%M', # '10/25/06 14:30' |
204 | + '%m/%d/%y', # '10/25/06' |
205 | ], |
206 | help="Default formats to be used when parsing dates and times " |
207 | "from input boxes, in order") |
208 | @@ -757,7 +800,7 @@ |
209 | "request phase, these middleware classes will be applied in the " |
210 | "order given, and in the response phase the middleware will be " |
211 | "applied in reverse order", |
212 | - default = [ |
213 | + default=[ |
214 | 'django.middleware.common.CommonMiddleware', |
215 | 'django.contrib.sessions.middleware.SessionMiddleware', |
216 | 'django.middleware.csrf.CsrfViewMiddleware', |
217 | @@ -798,11 +841,15 @@ |
218 | help="The name of the class to use to run the test suite") |
219 | |
220 | |
221 | -class Django13Schema(Django125Schema): |
222 | +Django13Base = derivate_django_schema( |
223 | + Django125Schema, exclude=['cache_backend']) |
224 | + |
225 | + |
226 | +class Django13Schema(Django13Base): |
227 | version = '1.3' |
228 | |
229 | # sections |
230 | - class django(Django125Schema.django): |
231 | + class django(Django13Base.django): |
232 | |
233 | ################ |
234 | # CORE # |
235 | @@ -814,7 +861,7 @@ |
236 | help="Languages we provide translations for, out of the box. " |
237 | "The language name should be the utf-8 encoded local name " |
238 | "for the language", |
239 | - default = [ |
240 | + default=[ |
241 | ('ar', gettext_noop('Arabic')), |
242 | ('az', gettext_noop('Azerbaijani')), |
243 | ('bg', gettext_noop('Bulgarian')), |
244 | @@ -884,13 +931,24 @@ |
245 | ('zh-tw', gettext_noop('Traditional Chinese')), |
246 | ]) |
247 | |
248 | + installed_apps = ListOption(item=StringOption(), |
249 | + help="List of strings representing installed apps", |
250 | + default=[ |
251 | + 'django.contrib.auth', |
252 | + 'django.contrib.contenttypes', |
253 | + 'django.contrib.sessions', |
254 | + 'django.contrib.sites', |
255 | + 'django.contrib.messages', |
256 | + 'django.contrib.staticfiles', |
257 | + ]) |
258 | + |
259 | template_context_processors = ListOption( |
260 | item=StringOption(), |
261 | help="List of processors used by RequestContext to populate the " |
262 | "context. Each one should be a callable that takes the request " |
263 | "object as its only parameter and returns a dictionary to add to " |
264 | "the context", |
265 | - default = [ |
266 | + default=[ |
267 | 'django.contrib.auth.context_processors.auth', |
268 | 'django.core.context_processors.debug', |
269 | 'django.core.context_processors.i18n', |
270 | @@ -904,7 +962,7 @@ |
271 | help='Absolute path to the directory that holds static files.') |
272 | |
273 | static_url = StringOption( |
274 | - null=True, default=None, |
275 | + null=True, default='/static/', |
276 | help='URL that handles the static files served from STATIC_ROOT.') |
277 | |
278 | ############ |
279 | @@ -919,9 +977,6 @@ |
280 | # CACHE # |
281 | ######### |
282 | |
283 | - # remove obsoleted setting |
284 | - cache_backend = None |
285 | - |
286 | caches = DictOption() |
287 | cache_middleware_alias = StringOption(default='default') |
288 | |
289 | @@ -1010,25 +1065,84 @@ |
290 | self._schemas[version] = schema_cls |
291 | |
292 | def get(self, version, strict=True): |
293 | - if version not in self._schemas: |
294 | - msg = "No schema registered for version %r" % version |
295 | - if strict: |
296 | - raise ValueError(msg) |
297 | + if version in self._schemas: |
298 | + return self._schemas[version] |
299 | + |
300 | + msg = "No schema registered for version %r" % version |
301 | + if strict: |
302 | + raise ValueError(msg) |
303 | + else: |
304 | + logging.warn(msg) |
305 | + |
306 | + logging.warn("Dynamically creating schema for version %r" % version) |
307 | + schema = self.build(version) |
308 | + return schema |
309 | + |
310 | + def build(self, version_string=None, options=None): |
311 | + if version_string is None: |
312 | + version_string = get_version() |
313 | + if options is None: |
314 | + options = dict([(name.lower(), value) for (name, value) in |
315 | + inspect.getmembers(global_settings) if name.isupper()]) |
316 | + project_options = dict([(name.lower(), value) for (name, value) in |
317 | + inspect.getmembers(project_settings) if name.isupper()]) |
318 | + options.update(project_options) |
319 | + |
320 | + class DjangoSchema(Schema): |
321 | + version = version_string |
322 | + |
323 | + class django(Section): |
324 | + pass |
325 | + |
326 | + def get_option_type(name, value): |
327 | + type_mappings = { |
328 | + int: IntOption, |
329 | + bool: BoolOption, |
330 | + list: ListOption, |
331 | + tuple: TupleOption, |
332 | + dict: DictOption, |
333 | + str: StringOption, |
334 | + unicode: StringOption, |
335 | + } |
336 | + if value is None: |
337 | + return StringOption(name=name, default=value, null=True) |
338 | else: |
339 | - logging.warn(msg) |
340 | - |
341 | - versions = sorted(self._schemas.keys()) |
342 | - if not versions: |
343 | - raise ValueError("No schemas registered") |
344 | - |
345 | - last = versions[0] |
346 | - for v in sorted(self._schemas.keys()): |
347 | - if version < v: |
348 | - break |
349 | - last = v |
350 | - version = last |
351 | - logging.warn("Falling back to schema for version %r" % version) |
352 | - return self._schemas[version] |
353 | + option_type = type_mappings[type(value)] |
354 | + kwargs = {'name': name, 'default': value} |
355 | + |
356 | + if option_type in (DictOption, ListOption): |
357 | + # get inner item type |
358 | + if option_type == DictOption: |
359 | + items = value.values() |
360 | + else: |
361 | + items = value |
362 | + |
363 | + item_type = None |
364 | + if items: |
365 | + item_type = type_mappings.get(type(items[0]), None) |
366 | + # make sure all items have a consistent type |
367 | + for item in items: |
368 | + current_item_type = type_mappings.get( |
369 | + type(item), None) |
370 | + if current_item_type != item_type: |
371 | + item_type = None |
372 | + # mismatching types found. fallback to default |
373 | + # item type |
374 | + break |
375 | + if item_type is not None: |
376 | + kwargs['item'] = item_type() |
377 | + |
378 | + return option_type(**kwargs) |
379 | + |
380 | + for name, value in options.items(): |
381 | + if name == '__CONFIGGLUE_PARSER__': |
382 | + continue |
383 | + option = get_option_type(name, value) |
384 | + setattr(DjangoSchema.django, name, option) |
385 | + |
386 | + # register schema for it to be available during next query |
387 | + self.register(DjangoSchema, version_string) |
388 | + return DjangoSchema |
389 | |
390 | |
391 | schemas = DjangoSchemaFactory() |
392 | |
393 | === modified file 'django_configglue/tests/helpers.py' |
394 | --- django_configglue/tests/helpers.py 2011-04-13 17:06:30 +0000 |
395 | +++ django_configglue/tests/helpers.py 2011-07-14 03:16:30 +0000 |
396 | @@ -11,6 +11,12 @@ |
397 | from django.conf import settings |
398 | from django.test import TestCase |
399 | |
400 | +from configglue.pyschema.schema import ( |
401 | + ListOption, |
402 | + TupleOption, |
403 | + Schema, |
404 | +) |
405 | + |
406 | |
407 | class ConfigGlueDjangoCommandTestCase(TestCase): |
408 | COMMAND = '' |
409 | @@ -118,3 +124,54 @@ |
410 | finally: |
411 | self.end_capture() |
412 | |
413 | + |
414 | +class SchemaHelperTestCase(TestCase): |
415 | + def assert_schema_structure(self, schema_cls, version, options): |
416 | + self.assertTrue(issubclass(schema_cls, Schema)) |
417 | + self.assertEqual(schema_cls.version, 'bogus') |
418 | + |
419 | + schema = schema_cls() |
420 | + |
421 | + # assert sections |
422 | + section_names = [section.name for section in schema.sections()] |
423 | + self.assertEqual(section_names, ['django']) |
424 | + # assert options for django section |
425 | + option_names = [option.name for option in schema.options('django')] |
426 | + self.assertEqual(option_names, options.keys()) |
427 | + # assert options |
428 | + for name, value in options.items(): |
429 | + option = schema.section('django').option(name) |
430 | + self.assertEqual(type(option), type(value)) |
431 | + self.assertEqual(option.default, value.default) |
432 | + |
433 | + def assert_schemas_equal(self, this, other): |
434 | + # compare schemas |
435 | + this_sections = sorted( |
436 | + [section.name for section in this.sections()]) |
437 | + other_sections = sorted( |
438 | + [section.name for section in other.sections()]) |
439 | + self.assertEqual(this_sections, other_sections) |
440 | + self.assertEqual(this_sections, ['django']) |
441 | + |
442 | + # compare options |
443 | + section = this.section('django') |
444 | + expected_section = other.section('django') |
445 | + options = section.options() |
446 | + |
447 | + this_options = sorted( |
448 | + [option.name for option in options]) |
449 | + other_options = sorted( |
450 | + [option.name for option in expected_section.options()]) |
451 | + self.assertEqual(this_options, other_options) |
452 | + |
453 | + for option in options: |
454 | + expected_option = expected_section.option(option.name) |
455 | + # handle special case for list/tuple |
456 | + # django defaults to tuples for options it defines as lists |
457 | + if (isinstance(option, (ListOption, TupleOption)) or |
458 | + isinstance(expected_option, (ListOption, TupleOption))): |
459 | + self.assertEqual(list(option.default), |
460 | + list(expected_option.default)) |
461 | + else: |
462 | + self.assertEqual(option.default, |
463 | + expected_option.default) |
464 | |
465 | === modified file 'django_configglue/tests/test_configglue.py' |
466 | --- django_configglue/tests/test_configglue.py 2011-07-10 15:16:26 +0000 |
467 | +++ django_configglue/tests/test_configglue.py 2011-07-14 03:16:30 +0000 |
468 | @@ -2,22 +2,29 @@ |
469 | # Copyright 2010 Canonical Ltd. This software is licensed under the |
470 | # GNU Lesser General Public License version 3 (see the file LICENSE). |
471 | |
472 | +import inspect |
473 | import textwrap |
474 | from cStringIO import StringIO |
475 | from unittest import TestCase |
476 | |
477 | import django |
478 | from configglue.pyschema.schema import ( |
479 | + BoolOption, |
480 | DictOption, |
481 | IntOption, |
482 | + ListOption, |
483 | Schema, |
484 | + Section, |
485 | StringOption, |
486 | + TupleOption, |
487 | ) |
488 | from configglue.pyschema.parser import ( |
489 | CONFIG_FILE_ENCODING, |
490 | SchemaConfigParser, |
491 | ) |
492 | from django.conf import settings |
493 | +from django.conf import global_settings |
494 | +from django.conf.project_template import settings as project_settings |
495 | from mock import patch |
496 | |
497 | from django_configglue.management import GlueManagementUtility |
498 | @@ -32,11 +39,15 @@ |
499 | DjangoSchemaFactory, |
500 | UpperCaseDictOption, |
501 | schemas, |
502 | -) |
503 | -from django_configglue.tests.helpers import ConfigGlueDjangoCommandTestCase |
504 | - |
505 | - |
506 | -class DjangoSupportTestCase(TestCase): |
507 | + derivate_django_schema, |
508 | +) |
509 | +from django_configglue.tests.helpers import ( |
510 | + ConfigGlueDjangoCommandTestCase, |
511 | + SchemaHelperTestCase, |
512 | +) |
513 | + |
514 | + |
515 | +class DjangoSupportTestCase(SchemaHelperTestCase): |
516 | def test_get_django_settings(self): |
517 | class MySchema(Schema): |
518 | foo = IntOption() |
519 | @@ -79,62 +90,11 @@ |
520 | |
521 | def test_schemafactory_get(self): |
522 | # test get valid version |
523 | - self.assertEqual(schemas.get('1.0.2 final'), BaseDjangoSchema) |
524 | + self.assertEqual(schemas.get('1.0.2 final', strict=True), |
525 | + BaseDjangoSchema) |
526 | |
527 | # test get invalid version |
528 | - self.assertRaises(ValueError, schemas.get, '1.1') |
529 | - |
530 | - @patch('django_configglue.schema.logging') |
531 | - def test_schemafactory_get_nonexisting_too_old(self, mock_logging): |
532 | - schema = schemas.get('0.96', strict=False) |
533 | - |
534 | - django_102 = schemas.get('1.0.2 final') |
535 | - self.assertEqual(schema, django_102) |
536 | - self.assertRaises(ValueError, schemas.get, '0.96') |
537 | - |
538 | - self.assertEqual(mock_logging.warn.call_args_list[0][0][0], |
539 | - "No schema registered for version '0.96'") |
540 | - self.assertEqual(mock_logging.warn.call_args_list[1][0][0], |
541 | - "Falling back to schema for version '1.0.2 final'") |
542 | - |
543 | - @patch('django_configglue.schema.logging') |
544 | - def test_schemafactory_get_nonexisting(self, mock_logging): |
545 | - schema = schemas.get('1.0.3', strict=False) |
546 | - |
547 | - django_102 = schemas.get('1.0.2 final') |
548 | - self.assertEqual(schema, django_102) |
549 | - self.assertRaises(ValueError, schemas.get, '1.0.3') |
550 | - |
551 | - self.assertEqual(mock_logging.warn.call_args_list[0][0][0], |
552 | - "No schema registered for version '1.0.3'") |
553 | - self.assertEqual(mock_logging.warn.call_args_list[1][0][0], |
554 | - "Falling back to schema for version '1.0.2 final'") |
555 | - |
556 | - @patch('django_configglue.schema.logging') |
557 | - def test_schemafactory_get_nonexisting_too_new(self, mock_logging): |
558 | - schema = schemas.get('1.2.0', strict=False) |
559 | - |
560 | - django_112 = schemas.get('1.1.4') |
561 | - self.assertEqual(schema, django_112) |
562 | - self.assertRaises(ValueError, schemas.get, '1.2.0') |
563 | - |
564 | - self.assertEqual(mock_logging.warn.call_args_list[0][0][0], |
565 | - "No schema registered for version '1.2.0'") |
566 | - self.assertEqual(mock_logging.warn.call_args_list[1][0][0], |
567 | - "Falling back to schema for version '1.1.4'") |
568 | - |
569 | - @patch('django_configglue.schema.logging') |
570 | - def test_schemafactory_get_no_versions_registered(self, mock_logging): |
571 | - schemas = DjangoSchemaFactory() |
572 | - try: |
573 | - schemas.get('1.0.2 final', strict=False) |
574 | - except ValueError, e: |
575 | - self.assertEqual(str(e), "No schemas registered") |
576 | - else: |
577 | - self.fail("ValueError not raised") |
578 | - |
579 | - mock_logging.warn.assert_called_with( |
580 | - "No schema registered for version '1.0.2 final'") |
581 | + self.assertRaises(ValueError, schemas.get, '1.1', strict=True) |
582 | |
583 | def test_schema_versions(self): |
584 | django_102 = schemas.get('1.0.2 final')() |
585 | @@ -176,6 +136,133 @@ |
586 | mock_update_settings.assert_called_with( |
587 | MockSchemaConfigParser.return_value, target) |
588 | |
589 | + def test_schemafactory_build(self): |
590 | + schemas = DjangoSchemaFactory() |
591 | + |
592 | + data = [ |
593 | + (IntOption, 1), |
594 | + (BoolOption, True), |
595 | + (TupleOption, (1, 2)), |
596 | + (ListOption, [1, 2]), |
597 | + (DictOption, {'a': 1}), |
598 | + (StringOption, 'foo'), |
599 | + ] |
600 | + |
601 | + for opt_type, default in data: |
602 | + schema_cls = schemas.build('bogus', {'foo': default}) |
603 | + # do common checks |
604 | + self.assert_schema_structure(schema_cls, 'bogus', |
605 | + {'foo': opt_type(name='foo', default=default)}) |
606 | + |
607 | + def test_schemafactory_build_django(self): |
608 | + schemas = DjangoSchemaFactory() |
609 | + |
610 | + schema = schemas.build() |
611 | + # get known django schema |
612 | + # fail for unknown versions of django |
613 | + django_schema = schemas.get(django.get_version(), strict=True) |
614 | + |
615 | + self.assert_schemas_equal(schema(), django_schema()) |
616 | + |
617 | + def test_schemafactory_get_django(self): |
618 | + schemas = DjangoSchemaFactory() |
619 | + |
620 | + # build a django schema |
621 | + # get only django settings |
622 | + options = dict([(name, value) for (name, value) in |
623 | + inspect.getmembers(settings) if name.isupper()]) |
624 | + schema = schemas.build(django.get_version(), options) |
625 | + # get known django schema |
626 | + # fail for unknown versions of django |
627 | + django_schema = schemas.get(django.get_version(), strict=True) |
628 | + |
629 | + self.assert_schemas_equal(schema(), django_schema()) |
630 | + |
631 | + def test_schemafactory_build_complex_options(self): |
632 | + schemas = DjangoSchemaFactory() |
633 | + |
634 | + options = { |
635 | + 'dict1': {'foo': 1, 'bar': '2'}, |
636 | + 'dict2': {'foo': {'bar': 2, 'baz': 3}}, |
637 | + 'list1': ['1', '2', '3'], |
638 | + 'list2': [1, 2, 3], |
639 | + } |
640 | + expected = { |
641 | + 'dict1': DictOption(name='dict1', default=options['dict1'], |
642 | + item=StringOption()), |
643 | + 'dict2': DictOption(name='dict2', default=options['dict2'], |
644 | + item=DictOption()), |
645 | + 'list1': ListOption(name='list1', default=options['list1'], |
646 | + item=StringOption()), |
647 | + 'list2': ListOption(name='list2', default=options['list2'], |
648 | + item=IntOption()), |
649 | + } |
650 | + |
651 | + schema = schemas.build('foo', options)() |
652 | + |
653 | + sections = sorted( |
654 | + [section.name for section in schema.sections()]) |
655 | + section = schema.section('django') |
656 | + self.assertEqual(sections, ['django']) |
657 | + |
658 | + for name in options: |
659 | + option = expected[name] |
660 | + option.section = section |
661 | + self.assertEqual(section.option(name), option) |
662 | + self.assertEqual(section.option(name).item, option.item) |
663 | + |
664 | + @patch('django_configglue.schema.logging') |
665 | + def test_schemafactory_get_unknown_build(self, mock_logging): |
666 | + schemas = DjangoSchemaFactory() |
667 | + version_string = django.get_version() |
668 | + self.assertFalse(version_string in schemas._schemas) |
669 | + |
670 | + # build schema |
671 | + schema = schemas.get(version_string, strict=False)() |
672 | + # get expected schema |
673 | + options = dict([(name.lower(), value) for (name, value) in |
674 | + inspect.getmembers(global_settings) if name.isupper()]) |
675 | + project_options = dict([(name.lower(), value) for (name, value) in |
676 | + inspect.getmembers(project_settings) if name.isupper()]) |
677 | + options.update(project_options) |
678 | + expected = schemas.build(django.get_version(), options)() |
679 | + |
680 | + # compare schemas |
681 | + self.assert_schemas_equal(schema, expected) |
682 | + |
683 | + self.assertEqual(mock_logging.warn.call_args_list[0][0][0], |
684 | + "No schema registered for version '{0}'".format(version_string)) |
685 | + self.assertEqual(mock_logging.warn.call_args_list[1][0][0], |
686 | + "Dynamically creating schema for version '{0}'".format( |
687 | + version_string)) |
688 | + |
689 | + def test_derivate_django_schema(self): |
690 | + class Parent(Schema): |
691 | + version = 'parent' |
692 | + |
693 | + class django(Section): |
694 | + foo = IntOption() |
695 | + bar = IntOption() |
696 | + |
697 | + class Child(Parent): |
698 | + version = 'parent' |
699 | + |
700 | + class django(Section): |
701 | + bar = IntOption() |
702 | + |
703 | + derivated = derivate_django_schema(Parent, exclude=['foo']) |
704 | + self.assert_schemas_equal(derivated(), Child()) |
705 | + |
706 | + def test_derivate_django_schema_no_exclude(self): |
707 | + class Parent(Schema): |
708 | + version = 'parent' |
709 | + |
710 | + class django(Section): |
711 | + foo = IntOption() |
712 | + |
713 | + derivated = derivate_django_schema(Parent) |
714 | + self.assertEqual(derivated, Parent) |
715 | + |
716 | |
717 | class GlueManagementUtilityTestCase(ConfigGlueDjangoCommandTestCase): |
718 | def setUp(self): |
719 | |
720 | === modified file 'django_configglue/tests/test_settings.py' |
721 | --- django_configglue/tests/test_settings.py 2011-04-13 17:06:30 +0000 |
722 | +++ django_configglue/tests/test_settings.py 2011-07-14 03:16:30 +0000 |
723 | @@ -9,7 +9,10 @@ |
724 | from django.core.management import ManagementUtility |
725 | |
726 | from django_configglue.utils import SETTINGS_ENCODING |
727 | -from django_configglue.tests.helpers import ConfigGlueDjangoCommandTestCase |
728 | +from django_configglue.tests.helpers import ( |
729 | + ConfigGlueDjangoCommandTestCase, |
730 | + SchemaHelperTestCase, |
731 | +) |
732 | |
733 | |
734 | class SettingsCommandTestCase(ConfigGlueDjangoCommandTestCase): |
735 | @@ -31,7 +34,7 @@ |
736 | |
737 | def test_show(self): |
738 | expected_values = [ |
739 | - "ROOT_URLCONF = 'urls'", |
740 | + "ROOT_URLCONF = '{{ project_name }}.urls'", |
741 | "SITE_ID = 1", |
742 | "SETTINGS_MODULE = 'django_configglue.tests.settings'", |
743 | "SETTINGS_ENCODING = '%s'" % SETTINGS_ENCODING, |
744 | @@ -39,8 +42,6 @@ |
745 | django_version = django.VERSION[:2] |
746 | if django_version == (1, 1): |
747 | expected_values.append("DATABASE_SUPPORTS_TRANSACTIONS = True") |
748 | - if django_version >= (1, 1): |
749 | - expected_values.append("JING_PATH = '/usr/bin/jing'") |
750 | self.call_command(show_current=True) |
751 | output_lines = self.capture['stdout'].strip().split('\n') |
752 | self.assertEqual(set(expected_values), set(output_lines)) |
753 | @@ -108,6 +109,34 @@ |
754 | self.assertEqual(self.wrapped_settings, expected) |
755 | |
756 | |
757 | +class GeneratedSettingsTestCase(ConfigGlueDjangoCommandTestCase, |
758 | + SchemaHelperTestCase): |
759 | + def setUp(self): |
760 | + from django_configglue.schema import schemas |
761 | + from mock import patch, Mock |
762 | + |
763 | + self.expected_schema = schemas.get( |
764 | + django.get_version(), strict=True)() |
765 | + |
766 | + mock_get_version = Mock(return_value='foo') |
767 | + self.patch = patch( |
768 | + 'django_configglue.tests.settings.django.get_version', |
769 | + mock_get_version) |
770 | + self.patch.start() |
771 | + super(GeneratedSettingsTestCase, self).setUp() |
772 | + |
773 | + def tearDown(self): |
774 | + self.patch.stop() |
775 | + super(GeneratedSettingsTestCase, self).tearDown() |
776 | + |
777 | + def test_generated_schema(self): |
778 | + # import here so that the necessary modules can be mocked before |
779 | + # being required |
780 | + from django.conf import settings |
781 | + schema = settings.__CONFIGGLUE_PARSER__.schema |
782 | + self.assert_schemas_equal(schema, self.expected_schema) |
783 | + |
784 | + |
785 | class ValidateCommandTestCase(ConfigGlueDjangoCommandTestCase): |
786 | COMMAND = 'settings' |
787 |
Neat!