django-configurations/configurations/importer.py
John R Dietrick cd0f1b4d0c Fix handling of fallback to optparse.OptionParser
By the time the code here runs, it's too late to be modifying
base.BaseCommand.option_list; in fact doing so causes an OptionConflictError if
you later call another management command "recursively" while you're running
the first one.

We want to leave BaseCommand's option_list untouched (it's `()` by default in
Django 1.8+) in case the command we are wrapping has already upgraded to
argparse. BUT, if it hasn't, we'll get an OptionParser back, and can tack our
argument on at the last minute.
2015-12-19 18:25:53 +08:00

199 lines
7.7 KiB
Python

import imp
import logging
import os
import sys
from optparse import OptionParser, make_option
from django import VERSION as DJANGO_VERSION
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:
if DJANGO_VERSION >= (1, 8):
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
else:
# add the configuration option to all management commands
base.BaseCommand.option_list += configuration_options
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 "<ConfigurationImporter for '{0}.{1}'>".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):
# django switched to argparse in version 1.8
if DJANGO_VERSION >= (1, 8):
parser = base.CommandParser(None,
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.
# django < 1.7 did use optparse
else:
from django.core.management import LaxOptionParser
parser = LaxOptionParser(option_list=configuration_options,
add_help_option=False)
try:
options, args = parser.parse_args(self.argv)
if options.configuration:
os.environ[self.namevar] = options.configuration
except:
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
# Django >= 1.7 supports hiding the colorization in the shell
try:
from django.core.management.color import no_style
except ImportError:
no_style = None
if no_style is not None and '--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__,
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