Merge lp:~ricardokirkner/django-configglue/770093-schema-introspection into lp:django-configglue

Proposed by Ricardo Kirkner
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
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.

To post a comment you must log in.
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 DjangoSchemaFactory.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

Revision history for this message
Anthony Lenton (elachuni) wrote :

Neat!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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

Subscribers

People subscribed via source and target branches

to all changes: