Skip to content

Instantly share code, notes, and snippets.

@benoitbryon
Created December 6, 2013 16:27
Show Gist options
  • Save benoitbryon/7827678 to your computer and use it in GitHub Desktop.
Save benoitbryon/7827678 to your computer and use it in GitHub Desktop.
Experimental utilities about local settings management with Django. Inspired by django-configglue... but using colander.
# -*- coding: utf-8 -*-
"""Helpers to manage local (project-level, environment-level) settings."""
from django.conf import global_settings
import colander
import json
import yaml
def settings_from_string_mapping(input):
"""Convert mapping of {key: string} to {key: complex type}.
Simple key-value stores (flat mappings) are supported:
>>> flat_mapping = {'DEBUG': 'True', 'SECRET_KEY': 'not a secret'}
>>> output = settings_from_string_mapping(flat_mapping)
>>> output == flat_mapping
True
Values can be complex types (sequences, mappings) using JSON or YAML.
Keys using ".json" or ".yaml" suffix are automatically decoded:
>>> nested_mapping = {
... 'DATABASES.yaml': 'ENGINE: sqlite3',
... }
>>> output = settings_from_string_mapping(nested_mapping)
>>> output['DATABASES'] == {'ENGINE': 'sqlite3'}
True
"""
output = {}
for key, value in input.iteritems():
if key.endswith('.json'):
output[key[:-5]] = json.loads(value)
elif key.endswith('.yaml'):
output[key[:-5]] = yaml.load(value)
else:
output[key] = value
return output
def settings_from_file(file_obj):
"""Return mapping from filename.
Supported file formats are JSON and YAML. The lowercase extension is used
to guess the file type.
>>> from StringIO import StringIO
>>> file_obj = StringIO('SOME_LIST: [a, b, c]')
>>> file_obj.name = 'something.yaml'
>>> settings_from_file(file_obj) == {
... 'SOME_LIST': ['a', 'b', 'c'],
... }
True
"""
file_name = file_obj.name
if file_name.endswith('.yaml'):
return yaml.load(file_obj)
elif file_name.endswith('.json'):
return json.load(file_obj)
else:
raise ValueError(
'Cannot guess format of configuration file "{name}". '
'Expected one of these extensions: "{extensions}".'.format(
name=file_name,
extensions='", "'.join('.yaml', '.json')))
def settings_from_module(module_path):
"""Import settings from module's globals and return them as a dict.
>>> settings = settings_from_module('django.conf.global_settings')
>>> settings['DATABASES']
{}
>>> '__name__' in settings
False
"""
module = __import__(module_path, fromlist='*', level=0)
is_uppercase = lambda x: x.upper() == x
is_special = lambda x: x.startswith('_')
return dict([(key, value) for key, value in module.__dict__.items()
if is_uppercase(key) and not is_special(key)])
class Django155ConfigurationSchema(colander.MappingSchema):
"""Schema for Django 1.5.5 built-in settings."""
ABSOLUTE_URL_OVERRIDES = colander.SchemaNode(
colander.Mapping(unknown='preserve'),
missing=None,
)
ADMIN_FOR = colander.SchemaNode(
colander.Sequence(),
missing=None,
*[
colander.SchemaNode(colander.String()),
]
)
ADMINS = colander.SchemaNode(
colander.Sequence(),
missing=None,
*[
colander.SchemaNode(
colander.Tuple(),
*[
colander.SchemaNode(colander.String()),
colander.SchemaNode(
colander.String(),
validator=colander.Email(),
),
]
),
]
)
ALLOWED_HOSTS = colander.SchemaNode(
colander.Sequence(),
missing=None,
*[
colander.SchemaNode(colander.String()),
]
)
ALLOWED_INCLUDE_ROOTS = colander.SchemaNode(
colander.Sequence(),
missing=None,
*[
colander.SchemaNode(colander.String()),
]
)
APPEND_SLASH = colander.SchemaNode(
colander.Boolean(),
missing=None,
)
AUTHENTICATION_BACKENDS = colander.SchemaNode(
colander.Sequence(),
missing=None,
*[
colander.SchemaNode(colander.String()),
]
)
AUTH_USER_MODEL = colander.SchemaNode(
colander.String(),
missing=None,
)
# The settings above have been from Django documentation...
# ... The following settings are settings which are required in project.
# The list is not complete yet.
# The idea is to complete the list above, moving items below and creating
# new items.
DATABASES = colander.SchemaNode(
colander.Mapping(unknown='preserve'),
missing=colander.required,
*[
colander.SchemaNode(
colander.Mapping(unknown='raise'),
name='default',
missing=None,
*[
colander.SchemaNode(
colander.String(),
name='ENGINE',
missing=colander.required,
default='',
),
colander.SchemaNode(
colander.String(),
name='HOST',
missing='',
default='',
),
colander.SchemaNode(
colander.String(),
name='NAME',
missing=colander.required,
default='',
),
colander.SchemaNode(
colander.Mapping(unknown='preserve'),
name='OPTIONS',
missing={},
default={},
),
colander.SchemaNode(
colander.String(),
name='PASSWORD',
missing='',
default='',
),
colander.SchemaNode(
colander.String(),
name='PORT',
missing='',
default='',
),
colander.SchemaNode(
colander.String(),
name='USER',
missing='',
default='',
),
colander.SchemaNode(
colander.String(),
name='TEST_CHARSET',
missing=None,
default=None,
),
colander.SchemaNode(
colander.String(),
name='TEST_COLLATION',
missing=None,
default=None,
),
colander.SchemaNode(
colander.Sequence(),
name='TEST_DEPENDENCIES',
missing=['default'],
default=['default'],
*[
colander.SchemaNode(colander.String()),
]
),
colander.SchemaNode(
colander.String(),
name='TEST_MIRROR',
missing=None,
default=None,
),
colander.SchemaNode(
colander.String(),
name='TEST_NAME',
missing=None,
default=None,
),
colander.SchemaNode(
colander.Boolean(),
name='TEST_CREATE',
missing=True,
default=True,
),
colander.SchemaNode(
colander.String(),
name='TEST_USER',
missing=None,
default=None,
),
colander.SchemaNode(
colander.Boolean(),
name='TEST_USER_CREATE',
missing=True,
default=True,
),
colander.SchemaNode(
colander.String(),
name='TEST_PASSWD',
missing=None,
default=None,
),
colander.SchemaNode(
colander.String(),
name='TEST_TBLSPACE',
missing=None,
default=None,
),
colander.SchemaNode(
colander.String(),
name='TEST_TBLSPACE_TMP',
missing=None,
default=None,
),
]
),
]
)
DEBUG = colander.SchemaNode(
colander.Boolean(),
missing=None,
)
DEFAULT_FROM_EMAIL = colander.SchemaNode(
colander.String(),
missing=None,
validator=colander.Email(),
)
INTERNAL_IPS = colander.SchemaNode(
colander.Sequence(),
missing=None,
*[
colander.SchemaNode(colander.String()),
]
)
INSTALLED_APPS = colander.SchemaNode(
colander.Sequence(),
missing=colander.required,
default=colander.null,
*[
colander.SchemaNode(colander.String()),
]
)
LANGUAGE_CODE = colander.SchemaNode(
colander.String(),
missing=None,
)
LANGUAGES = colander.SchemaNode(
colander.Sequence(),
missing=None,
*[
colander.SchemaNode(
colander.Tuple(),
*[
colander.SchemaNode(colander.String()),
colander.SchemaNode(colander.String()),
]
),
]
)
LOGIN_URL = colander.SchemaNode(
colander.String(),
missing=None,
)
MEDIA_ROOT = colander.SchemaNode(
colander.String(),
missing=None,
)
MEDIA_URL = colander.SchemaNode(
colander.String(),
missing=None,
)
MIDDLEWARE_CLASSES = colander.SchemaNode(
colander.Sequence(),
missing=None,
*[
colander.SchemaNode(colander.String()),
]
)
ROOT_URLCONF = colander.SchemaNode(
colander.String(),
missing=colander.required,
default=colander.null,
)
SECRET_KEY = colander.SchemaNode(
colander.String(),
missing=colander.required,
default=colander.null,
)
STATIC_ROOT = colander.SchemaNode(
colander.String(),
missing=None,
)
STATICFILES_STORAGE = colander.SchemaNode(
colander.String(),
missing=None,
)
STATIC_URL = colander.SchemaNode(
colander.String(),
missing=None,
)
TEMPLATE_CONTEXT_PROCESSORS = colander.SchemaNode(
colander.Sequence(),
missing=None,
*[
colander.SchemaNode(colander.String()),
]
)
TEMPLATE_DEBUG = colander.SchemaNode(
colander.Boolean(),
missing=None,
)
TIME_ZONE = colander.SchemaNode(
colander.String(),
missing=None,
)
USE_I18N = colander.SchemaNode(
colander.Boolean(),
missing=None,
)
USE_L10N = colander.SchemaNode(
colander.Boolean(),
missing=None,
)
USE_TZ = colander.SchemaNode(
colander.Boolean(),
missing=None,
)
#: Schema for Django settings (current/installed version)."""
DjangoConfigurationSchema = Django155ConfigurationSchema
# Automatically assign default values from django.conf.global_settings.
for child in DjangoConfigurationSchema():
if not child.default is colander.null:
child.default = getattr(global_settings, child.name)
if child.missing is None:
child.missing = getattr(global_settings, child.name)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment