mirror of
https://github.com/jazzband/django-configurations.git
synced 2026-05-24 07:03:46 +00:00
This avoids breaking the normal MRO for attributes, which searches the class hierarchy for attribute definitions. If attributes are copied onto every class in the hierarchy, the C3 MRO algorithm finds the attribute in a child class even though that class didn't actually define it, which results in surprising attribute values when classes in the hierarchy set the same attribute to different values.
147 lines
5.4 KiB
Python
147 lines
5.4 KiB
Python
import os
|
|
import re
|
|
|
|
from django.conf import global_settings
|
|
from django.core.exceptions import ImproperlyConfigured
|
|
|
|
from .utils import uppercase_attributes
|
|
from .values import Value, setup_value
|
|
|
|
__all__ = ['Configuration']
|
|
|
|
|
|
install_failure = ("django-configurations settings importer wasn't "
|
|
"correctly installed. Please use one of the starter "
|
|
"functions to install it as mentioned in the docs: "
|
|
"https://django-configurations.readthedocs.io/")
|
|
|
|
|
|
class ConfigurationBase(type):
|
|
|
|
def __new__(cls, name, bases, attrs):
|
|
if bases not in ((object,), ()) and bases[0].__name__ != 'NewBase':
|
|
# if this is actually a subclass in a settings module
|
|
# we better check if the importer was correctly installed
|
|
from . import importer
|
|
if not importer.installed:
|
|
raise ImproperlyConfigured(install_failure)
|
|
settings_vars = uppercase_attributes(global_settings)
|
|
deprecated_settings = {
|
|
# DEFAULT_HASHING_ALGORITHM is always deprecated, as it's a
|
|
# transitional setting
|
|
# https://docs.djangoproject.com/en/3.1/releases/3.1/#default-hashing-algorithm-settings
|
|
"DEFAULT_HASHING_ALGORITHM",
|
|
# DEFAULT_CONTENT_TYPE and FILE_CHARSET are deprecated in
|
|
# Django 2.2 and are removed in Django 3.0
|
|
"DEFAULT_CONTENT_TYPE",
|
|
"FILE_CHARSET",
|
|
# When DEFAULT_AUTO_FIELD is not explicitly set, Django's emits a
|
|
# system check warning models.W042. This warning should not be
|
|
# suppressed, as downstream users are expected to make a decision.
|
|
# https://docs.djangoproject.com/en/3.2/releases/3.2/#customizing-type-of-auto-created-primary-keys
|
|
"DEFAULT_AUTO_FIELD",
|
|
}
|
|
# PASSWORD_RESET_TIMEOUT_DAYS is deprecated in favor of
|
|
# PASSWORD_RESET_TIMEOUT in Django 3.1
|
|
# https://github.com/django/django/commit/226ebb17290b604ef29e82fb5c1fbac3594ac163#diff-ec2bed07bb264cb95a80f08d71a47c06R163-R170
|
|
if "PASSWORD_RESET_TIMEOUT" in settings_vars:
|
|
deprecated_settings.add("PASSWORD_RESET_TIMEOUT_DAYS")
|
|
# DEFAULT_FILE_STORAGE and STATICFILES_STORAGE are deprecated
|
|
# in favor of STORAGES.
|
|
# https://docs.djangoproject.com/en/dev/releases/4.2/#custom-file-storages
|
|
if "STORAGES" in settings_vars:
|
|
deprecated_settings.add("DEFAULT_FILE_STORAGE")
|
|
deprecated_settings.add("STATICFILES_STORAGE")
|
|
for deprecated_setting in deprecated_settings:
|
|
if deprecated_setting in settings_vars:
|
|
del settings_vars[deprecated_setting]
|
|
attrs = {**settings_vars, **attrs}
|
|
|
|
return super().__new__(cls, name, bases, attrs)
|
|
|
|
def __repr__(self):
|
|
return "<Configuration '{0}.{1}'>".format(self.__module__,
|
|
self.__name__)
|
|
|
|
|
|
class Configuration(metaclass=ConfigurationBase):
|
|
"""
|
|
The base configuration class to inherit from.
|
|
|
|
::
|
|
|
|
class Develop(Configuration):
|
|
EXTRA_AWESOME = True
|
|
|
|
@property
|
|
def SOMETHING(self):
|
|
return completely.different()
|
|
|
|
def OTHER(self):
|
|
if whatever:
|
|
return (1, 2, 3)
|
|
return (4, 5, 6)
|
|
|
|
The module this configuration class is located in will
|
|
automatically get the class and instance level attributes
|
|
with upper characters if the ``DJANGO_CONFIGURATION`` is set
|
|
to the name of the class.
|
|
|
|
"""
|
|
DOTENV_LOADED = None
|
|
|
|
@classmethod
|
|
def load_dotenv(cls):
|
|
"""
|
|
Pulled from Honcho code with minor updates, reads local default
|
|
environment variables from a .env file located in the project root
|
|
or provided directory.
|
|
|
|
http://www.wellfireinteractive.com/blog/easier-12-factor-django/
|
|
https://gist.github.com/bennylope/2999704
|
|
"""
|
|
# check if the class has DOTENV set whether with a path or None
|
|
dotenv = getattr(cls, 'DOTENV', None)
|
|
|
|
# if DOTENV is falsy we want to disable it
|
|
if not dotenv:
|
|
return
|
|
|
|
# now check if we can access the file since we know we really want to
|
|
try:
|
|
with open(dotenv, 'r') as f:
|
|
content = f.read()
|
|
except OSError as e:
|
|
raise ImproperlyConfigured("Couldn't read .env file "
|
|
"with the path {}. Error: "
|
|
"{}".format(dotenv, e)) from e
|
|
else:
|
|
for line in content.splitlines():
|
|
m1 = re.match(r'\A([A-Za-z_0-9]+)=(.*)\Z', line)
|
|
if not m1:
|
|
continue
|
|
key, val = m1.group(1), m1.group(2)
|
|
m2 = re.match(r"\A'(.*)'\Z", val)
|
|
if m2:
|
|
val = m2.group(1)
|
|
m3 = re.match(r'\A"(.*)"\Z', val)
|
|
if m3:
|
|
val = re.sub(r'\\(.)', r'\1', m3.group(1))
|
|
os.environ.setdefault(key, val)
|
|
|
|
cls.DOTENV_LOADED = dotenv
|
|
|
|
@classmethod
|
|
def pre_setup(cls):
|
|
if cls.DOTENV_LOADED is None:
|
|
cls.load_dotenv()
|
|
|
|
@classmethod
|
|
def post_setup(cls):
|
|
pass
|
|
|
|
@classmethod
|
|
def setup(cls):
|
|
for name, value in uppercase_attributes(cls).items():
|
|
if isinstance(value, Value):
|
|
setup_value(cls, name, value)
|