mirror of
https://github.com/jazzband/django-configurations.git
synced 2026-03-16 22:20:27 +00:00
157 lines
5.9 KiB
Python
157 lines
5.9 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)
|
|
parents = [base for base in bases if isinstance(base,
|
|
ConfigurationBase)]
|
|
if parents:
|
|
for base in bases[::-1]:
|
|
settings_vars.update(uppercase_attributes(base))
|
|
|
|
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",
|
|
# FORMS_URLFIELD_ASSUME_HTTPS is a transitional setting introduced
|
|
# in Django 5.0.
|
|
# https://docs.djangoproject.com/en/5.0/releases/5.0/#id2
|
|
"FORMS_URLFIELD_ASSUME_HTTPS"
|
|
}
|
|
# 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 '{}.{}'>".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.
|
|
|
|
https://wellfire.co/learn/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) 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)
|