forked from jazzband/django-configurations
-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathimporter.py
184 lines (151 loc) · 6.71 KB
/
importer.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
import importlib.util
from importlib.machinery import PathFinder
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:
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 '{}.{}'>".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 {}, using "
"configuration {}".format(__version__ or "",
self.name))
self.logger.debug(stylize(message))
def find_spec(self, fullname, path=None, target=None):
if fullname is not None and fullname == self.module:
spec = PathFinder.find_spec(fullname, path)
if spec is not None:
return importlib.machinery.ModuleSpec(spec.name,
ConfigurationLoader(self.name, spec),
origin=spec.origin)
return None
class ConfigurationLoader:
def __init__(self, name, spec):
self.name = name
self.spec = spec
def load_module(self, fullname):
if fullname in sys.modules:
mod = sys.modules[fullname] # pragma: no cover
else:
mod = importlib.util.module_from_spec(self.spec)
sys.modules[fullname] = mod
self.spec.loader.exec_module(mod)
cls_path = f'{mod.__name__}.{self.name}'
try:
cls = getattr(mod, self.name)
except AttributeError as err: # pragma: no cover
reraise(err, "Couldn't find configuration '{}' "
"in module '{}'".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', '{}.{}'.format(fullname,
self.name))
cls.post_setup()
except Exception as err:
reraise(err, f"Couldn't setup configuration '{cls_path}'")
return mod