import imp import logging import os import sys from optparse import OptionParser, make_option from django.conf import ENVIRONMENT_VARIABLE as SETTINGS_ENVIRONMENT_VARIABLE from django.core.exceptions import ImproperlyConfigured from django.core.management import base from .utils import uppercase_attributes, reraise from .values import Value, setup_value installed = False CONFIGURATION_ENVIRONMENT_VARIABLE = 'DJANGO_CONFIGURATION' CONFIGURATION_ARGUMENT = '--configuration' CONFIGURATION_ARGUMENT_HELP = ('The name of the configuration class to load, ' 'e.g. "Development". If this isn\'t provided, ' 'the DJANGO_CONFIGURATION environment ' 'variable will be used.') configuration_options = (make_option(CONFIGURATION_ARGUMENT, help=CONFIGURATION_ARGUMENT_HELP),) def install(check_options=False): global installed if not installed: orig_create_parser = base.BaseCommand.create_parser def create_parser(self, prog_name, subcommand): parser = orig_create_parser(self, prog_name, subcommand) if isinstance(parser, OptionParser): # in case the option_list is set the create_parser # will actually return a OptionParser for backward # compatibility. In that case we should tack our # options on to the end of the parser on the way out. for option in configuration_options: parser.add_option(option) else: # probably argparse, let's not import argparse though parser.add_argument(CONFIGURATION_ARGUMENT, help=CONFIGURATION_ARGUMENT_HELP) return parser base.BaseCommand.create_parser = create_parser importer = ConfigurationImporter(check_options=check_options) sys.meta_path.insert(0, importer) installed = True class ConfigurationImporter(object): modvar = SETTINGS_ENVIRONMENT_VARIABLE namevar = CONFIGURATION_ENVIRONMENT_VARIABLE error_msg = ("Configuration cannot be imported, " "environment variable {0} is undefined.") def __init__(self, check_options=False): self.argv = sys.argv[:] self.logger = logging.getLogger(__name__) self.logger.setLevel(logging.DEBUG) handler = logging.StreamHandler() self.logger.addHandler(handler) if check_options: self.check_options() self.validate() if check_options: self.announce() def __repr__(self): return "".format(self.module, self.name) @property def module(self): return os.environ.get(self.modvar) @property def name(self): return os.environ.get(self.namevar) def check_options(self): parser = base.CommandParser( usage="%(prog)s subcommand [options] [args]", add_help=False, ) parser.add_argument('--settings') parser.add_argument('--pythonpath') parser.add_argument(CONFIGURATION_ARGUMENT, help=CONFIGURATION_ARGUMENT_HELP) parser.add_argument('args', nargs='*') # catch-all try: options, args = parser.parse_known_args(self.argv[2:]) if options.configuration: os.environ[self.namevar] = options.configuration base.handle_default_options(options) except base.CommandError: pass # Ignore any option errors at this point. def validate(self): if self.name is None: raise ImproperlyConfigured(self.error_msg.format(self.namevar)) if self.module is None: raise ImproperlyConfigured(self.error_msg.format(self.modvar)) def announce(self): if len(self.argv) > 1: from . import __version__ from django.utils.termcolors import colorize from django.core.management.color import no_style if '--no-color' in self.argv: stylize = no_style() else: def stylize(text): return colorize(text, fg='green') if (self.argv[1] == 'runserver' and os.environ.get('RUN_MAIN') == 'true'): message = ("django-configurations version {0}, using " "configuration {1}".format(__version__ or "", self.name)) self.logger.debug(stylize(message)) def find_module(self, fullname, path=None): if fullname is not None and fullname == self.module: module = fullname.rsplit('.', 1)[-1] return ConfigurationLoader(self.name, imp.find_module(module, path)) return None class ConfigurationLoader(object): def __init__(self, name, location): self.name = name self.location = location def load_module(self, fullname): if fullname in sys.modules: mod = sys.modules[fullname] # pragma: no cover else: mod = imp.load_module(fullname, *self.location) cls_path = '{0}.{1}'.format(mod.__name__, self.name) try: cls = getattr(mod, self.name) except AttributeError as err: # pragma: no cover reraise(err, "Couldn't find configuration '{0}' " "in module '{1}'".format(self.name, mod.__package__)) try: cls.pre_setup() cls.setup() obj = cls() attributes = uppercase_attributes(obj).items() for name, value in attributes: if callable(value) and not getattr(value, 'pristine', False): value = value() # in case a method returns a Value instance we have # to do the same as the Configuration.setup method if isinstance(value, Value): setup_value(mod, name, value) continue setattr(mod, name, value) setattr(mod, 'CONFIGURATION', '{0}.{1}'.format(fullname, self.name)) cls.post_setup() except Exception as err: reraise(err, "Couldn't setup configuration '{0}'".format(cls_path)) return mod