mirror of
https://github.com/jazzband/django-configurations.git
synced 2026-05-19 04:51:10 +00:00
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.
199 lines
7.7 KiB
Python
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
|