mirror of
https://github.com/jazzband/django-configurations.git
synced 2026-03-16 22:20:27 +00:00
add environ_prefix class decorator (#240)
- allows to configure environ_prefix on a per-class basis
This commit is contained in:
parent
3d0d4216ca
commit
14776c83ac
7 changed files with 143 additions and 8 deletions
|
|
@ -1,9 +1,9 @@
|
|||
from .base import Configuration # noqa
|
||||
from .decorators import pristinemethod # noqa
|
||||
from .decorators import environ_prefix, pristinemethod # noqa
|
||||
from .version import __version__ # noqa
|
||||
|
||||
|
||||
__all__ = ['Configuration', 'pristinemethod']
|
||||
__all__ = ['Configuration', 'environ_prefix', 'pristinemethod']
|
||||
|
||||
|
||||
def _setup():
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import re
|
|||
from django.conf import global_settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
from .utils import uppercase_attributes
|
||||
from .utils import uppercase_attributes, UNSET
|
||||
from .values import Value, setup_value
|
||||
|
||||
__all__ = ['Configuration']
|
||||
|
|
@ -99,6 +99,7 @@ class Configuration(metaclass=ConfigurationBase):
|
|||
|
||||
"""
|
||||
DOTENV_LOADED = None
|
||||
_environ_prefix = UNSET
|
||||
|
||||
@classmethod
|
||||
def load_dotenv(cls):
|
||||
|
|
@ -154,4 +155,5 @@ class Configuration(metaclass=ConfigurationBase):
|
|||
def setup(cls):
|
||||
for name, value in uppercase_attributes(cls).items():
|
||||
if isinstance(value, Value):
|
||||
value._class_environ_prefix = cls._environ_prefix
|
||||
setup_value(cls, name, value)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
|
||||
def pristinemethod(func):
|
||||
"""
|
||||
A decorator for handling pristine settings like callables.
|
||||
|
|
@ -17,3 +20,30 @@ def pristinemethod(func):
|
|||
"""
|
||||
func.pristine = True
|
||||
return staticmethod(func)
|
||||
|
||||
|
||||
def environ_prefix(prefix):
|
||||
"""
|
||||
A class Configuration class decorator that prefixes ``prefix``
|
||||
to environment names.
|
||||
|
||||
Use it like this::
|
||||
|
||||
@environ_prefix("MYAPP")
|
||||
class Develop(Configuration):
|
||||
SOMETHING = values.Value()
|
||||
|
||||
To remove the prefix from environment names::
|
||||
|
||||
@environ_prefix(None)
|
||||
class Develop(Configuration):
|
||||
SOMETHING = values.Value()
|
||||
|
||||
"""
|
||||
if not isinstance(prefix, (type(None), str)):
|
||||
raise ImproperlyConfigured("environ_prefix accepts only str and None values.")
|
||||
|
||||
def decorator(conf_cls):
|
||||
conf_cls._environ_prefix = prefix
|
||||
return conf_cls
|
||||
return decorator
|
||||
|
|
|
|||
|
|
@ -99,3 +99,11 @@ def getargspec(func):
|
|||
if not inspect.isfunction(func):
|
||||
raise TypeError('%r is not a Python function' % func)
|
||||
return inspect.getfullargspec(func)
|
||||
|
||||
|
||||
class Unset:
|
||||
def __repr__(self): # pragma: no cover
|
||||
return "UNSET"
|
||||
|
||||
|
||||
UNSET = Unset()
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@ import sys
|
|||
|
||||
from django.core import validators
|
||||
from django.core.exceptions import ValidationError, ImproperlyConfigured
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
from .utils import getargspec
|
||||
from .utils import getargspec, UNSET
|
||||
|
||||
|
||||
def setup_value(target, name, value):
|
||||
|
|
@ -58,16 +59,14 @@ class Value:
|
|||
return instance
|
||||
|
||||
def __init__(self, default=None, environ=True, environ_name=None,
|
||||
environ_prefix='DJANGO', environ_required=False,
|
||||
environ_prefix=UNSET, environ_required=False,
|
||||
*args, **kwargs):
|
||||
if isinstance(default, Value) and default.default is not None:
|
||||
self.default = copy.copy(default.default)
|
||||
else:
|
||||
self.default = default
|
||||
self.environ = environ
|
||||
if environ_prefix and environ_prefix.endswith('_'):
|
||||
environ_prefix = environ_prefix[:-1]
|
||||
self.environ_prefix = environ_prefix
|
||||
self._environ_prefix = environ_prefix
|
||||
self.environ_name = environ_name
|
||||
self.environ_required = environ_required
|
||||
|
||||
|
|
@ -116,6 +115,19 @@ class Value:
|
|||
"""
|
||||
return value
|
||||
|
||||
@cached_property
|
||||
def environ_prefix(self):
|
||||
prefix = UNSET
|
||||
if self._environ_prefix is not UNSET:
|
||||
prefix = self._environ_prefix
|
||||
elif (class_prefix := getattr(self, "_class_environ_prefix", UNSET)) is not UNSET:
|
||||
prefix = class_prefix
|
||||
if prefix is not UNSET:
|
||||
if isinstance(prefix, str) and prefix.endswith("_"):
|
||||
return prefix[:-1]
|
||||
return prefix
|
||||
return "DJANGO"
|
||||
|
||||
|
||||
class MultipleMixin:
|
||||
multiple = True
|
||||
|
|
|
|||
26
tests/settings/prefix_decorator.py
Normal file
26
tests/settings/prefix_decorator.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
from configurations import Configuration, environ_prefix, values
|
||||
|
||||
|
||||
@environ_prefix("ACME")
|
||||
class PrefixDecoratorConf1(Configuration):
|
||||
FOO = values.Value()
|
||||
|
||||
|
||||
@environ_prefix("ACME")
|
||||
class PrefixDecoratorConf2(Configuration):
|
||||
FOO = values.BooleanValue(False)
|
||||
|
||||
|
||||
@environ_prefix("ACME")
|
||||
class PrefixDecoratorConf3(Configuration):
|
||||
FOO = values.Value(environ_prefix="ZEUS")
|
||||
|
||||
|
||||
@environ_prefix("")
|
||||
class PrefixDecoratorConf4(Configuration):
|
||||
FOO = values.Value()
|
||||
|
||||
|
||||
@environ_prefix(None)
|
||||
class PrefixDecoratorConf5(Configuration):
|
||||
FOO = values.Value()
|
||||
57
tests/test_prefix_decorator.py
Normal file
57
tests/test_prefix_decorator.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import os
|
||||
import importlib
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.test import TestCase
|
||||
from unittest.mock import patch
|
||||
|
||||
from configurations import environ_prefix
|
||||
from tests.settings import prefix_decorator
|
||||
|
||||
|
||||
class EnvironPrefixDecoratorTests(TestCase):
|
||||
@patch.dict(os.environ, clear=True,
|
||||
DJANGO_CONFIGURATION="PrefixDecoratorConf1",
|
||||
DJANGO_SETTINGS_MODULE="tests.settings.prefix_decorator",
|
||||
ACME_FOO="bar")
|
||||
def test_prefix_decorator_with_value(self):
|
||||
importlib.reload(prefix_decorator)
|
||||
self.assertEqual(prefix_decorator.FOO, "bar")
|
||||
|
||||
@patch.dict(os.environ, clear=True,
|
||||
DJANGO_CONFIGURATION="PrefixDecoratorConf2",
|
||||
DJANGO_SETTINGS_MODULE="tests.settings.prefix_decorator",
|
||||
ACME_FOO="True")
|
||||
def test_prefix_decorator_for_value_subclasses(self):
|
||||
importlib.reload(prefix_decorator)
|
||||
self.assertIs(prefix_decorator.FOO, True)
|
||||
|
||||
@patch.dict(os.environ, clear=True,
|
||||
DJANGO_CONFIGURATION="PrefixDecoratorConf3",
|
||||
DJANGO_SETTINGS_MODULE="tests.settings.prefix_decorator",
|
||||
ZEUS_FOO="bar")
|
||||
def test_value_prefix_takes_precedence(self):
|
||||
importlib.reload(prefix_decorator)
|
||||
self.assertEqual(prefix_decorator.FOO, "bar")
|
||||
|
||||
@patch.dict(os.environ, clear=True,
|
||||
DJANGO_CONFIGURATION="PrefixDecoratorConf4",
|
||||
DJANGO_SETTINGS_MODULE="tests.settings.prefix_decorator",
|
||||
FOO="bar")
|
||||
def test_prefix_decorator_empty_string_value(self):
|
||||
importlib.reload(prefix_decorator)
|
||||
self.assertEqual(prefix_decorator.FOO, "bar")
|
||||
|
||||
@patch.dict(os.environ, clear=True,
|
||||
DJANGO_CONFIGURATION="PrefixDecoratorConf5",
|
||||
DJANGO_SETTINGS_MODULE="tests.settings.prefix_decorator",
|
||||
FOO="bar")
|
||||
def test_prefix_decorator_none_value(self):
|
||||
importlib.reload(prefix_decorator)
|
||||
self.assertEqual(prefix_decorator.FOO, "bar")
|
||||
|
||||
def test_prefix_value_must_be_none_or_str(self):
|
||||
class Conf:
|
||||
pass
|
||||
|
||||
self.assertRaises(ImproperlyConfigured, lambda: environ_prefix(1)(Conf))
|
||||
Loading…
Reference in a new issue