mirror of
https://github.com/jazzband/django-configurations.git
synced 2026-03-16 22:20:27 +00:00
Compare commits
22 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d0d4216ca | ||
|
|
007161adb0 | ||
|
|
57b6827ae3 | ||
|
|
b8f66f76ee | ||
|
|
fcd03ada0f | ||
|
|
0bf416155e | ||
|
|
cec5f7492a | ||
|
|
b8e94fd796 | ||
|
|
f37ed87d6e | ||
|
|
7f0f29d161 | ||
|
|
9688dae5e1 | ||
|
|
4efa08f81a | ||
|
|
c67eab3507 | ||
|
|
711fa66654 | ||
|
|
448c5ab1b6 | ||
|
|
65d326a95a | ||
|
|
63cac8d54e | ||
|
|
ba24d3c324 | ||
|
|
e1091160b1 | ||
|
|
880484d3b7 | ||
|
|
ef4f49d236 | ||
|
|
6dc2340dfe |
18 changed files with 181 additions and 115 deletions
13
.github/dependabot.yml
vendored
Normal file
13
.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Keep GitHub Actions up to date with GitHub's Dependabot...
|
||||
# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot
|
||||
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
groups:
|
||||
github-actions:
|
||||
patterns:
|
||||
- "*" # Group all Actions updates into a single larger pull request
|
||||
schedule:
|
||||
interval: weekly
|
||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
|
|
@ -16,9 +16,9 @@ jobs:
|
|||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.11
|
||||
python-version: 3.x
|
||||
|
||||
- name: Get pip cache dir
|
||||
id: pip-cache
|
||||
|
|
@ -26,7 +26,7 @@ jobs:
|
|||
echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.pip-cache.outputs.dir }}
|
||||
key: release-${{ hashFiles('**/setup.py') }}
|
||||
|
|
|
|||
16
.github/workflows/test.yml
vendored
16
.github/workflows/test.yml
vendored
|
|
@ -11,15 +11,15 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 5
|
||||
max-parallel: 6
|
||||
matrix:
|
||||
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', 'pypy-3.10']
|
||||
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', 'pypy-3.10']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ jobs:
|
|||
echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.pip-cache.outputs.dir }}
|
||||
key:
|
||||
|
|
@ -47,7 +47,11 @@ jobs:
|
|||
tox --verbose
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
name: Python ${{ matrix.python-version }}
|
||||
name: coverage-data-${{ matrix.python-version }}
|
||||
path: ".coverage.*"
|
||||
include-hidden-files: true
|
||||
merge-multiple: true
|
||||
fail_ci_if_error: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ Or if you are not serving your app via WSGI but ASGI instead, you need to modify
|
|||
import os
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
|
||||
os.environ.setdefault('DJANGO_CONFIGURATION', 'DEV')
|
||||
os.environ.setdefault('DJANGO_CONFIGURATION', 'Dev')
|
||||
|
||||
from configurations.asgi import get_asgi_application
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,10 @@ class ConfigurationBase(type):
|
|||
# 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
|
||||
|
|
@ -66,7 +70,7 @@ class ConfigurationBase(type):
|
|||
return super().__new__(cls, name, bases, attrs)
|
||||
|
||||
def __repr__(self):
|
||||
return "<Configuration '{0}.{1}'>".format(self.__module__,
|
||||
return "<Configuration '{}.{}'>".format(self.__module__,
|
||||
self.__name__)
|
||||
|
||||
|
||||
|
|
@ -103,7 +107,7 @@ class Configuration(metaclass=ConfigurationBase):
|
|||
environment variables from a .env file located in the project root
|
||||
or provided directory.
|
||||
|
||||
http://www.wellfireinteractive.com/blog/easier-12-factor-django/
|
||||
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
|
||||
|
|
@ -115,7 +119,7 @@ class Configuration(metaclass=ConfigurationBase):
|
|||
|
||||
# now check if we can access the file since we know we really want to
|
||||
try:
|
||||
with open(dotenv, 'r') as f:
|
||||
with open(dotenv) as f:
|
||||
content = f.read()
|
||||
except OSError as e:
|
||||
raise ImproperlyConfigured("Couldn't read .env file "
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import importlib.util
|
||||
from importlib.machinery import PathFinder
|
||||
import logging
|
||||
import os
|
||||
|
|
@ -47,12 +46,12 @@ def install(check_options=False):
|
|||
return parser
|
||||
|
||||
base.BaseCommand.create_parser = create_parser
|
||||
importer = ConfigurationImporter(check_options=check_options)
|
||||
importer = ConfigurationFinder(check_options=check_options)
|
||||
sys.meta_path.insert(0, importer)
|
||||
installed = True
|
||||
|
||||
|
||||
class ConfigurationImporter:
|
||||
class ConfigurationFinder(PathFinder):
|
||||
modvar = SETTINGS_ENVIRONMENT_VARIABLE
|
||||
namevar = CONFIGURATION_ENVIRONMENT_VARIABLE
|
||||
error_msg = ("Configuration cannot be imported, "
|
||||
|
|
@ -71,7 +70,7 @@ class ConfigurationImporter:
|
|||
self.announce()
|
||||
|
||||
def __repr__(self):
|
||||
return "<ConfigurationImporter for '{0}.{1}'>".format(self.module,
|
||||
return "<ConfigurationFinder for '{}.{}'>".format(self.module,
|
||||
self.name)
|
||||
|
||||
@property
|
||||
|
|
@ -122,63 +121,60 @@ class ConfigurationImporter:
|
|||
if (self.argv[1] == 'runserver'
|
||||
and os.environ.get('RUN_MAIN') == 'true'):
|
||||
|
||||
message = ("django-configurations version {0}, using "
|
||||
"configuration {1}".format(__version__ or "",
|
||||
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)
|
||||
spec = super().find_spec(fullname, path, target)
|
||||
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
|
||||
wrap_loader(spec.loader, self.name)
|
||||
return spec
|
||||
else:
|
||||
mod = importlib.util.module_from_spec(self.spec)
|
||||
sys.modules[fullname] = mod
|
||||
self.spec.loader.exec_module(mod)
|
||||
return None
|
||||
|
||||
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)
|
||||
def wrap_loader(loader, class_name):
|
||||
class ConfigurationLoader(loader.__class__):
|
||||
def exec_module(self, module):
|
||||
super().exec_module(module)
|
||||
|
||||
setattr(mod, 'CONFIGURATION', '{0}.{1}'.format(fullname,
|
||||
self.name))
|
||||
cls.post_setup()
|
||||
mod = module
|
||||
|
||||
except Exception as err:
|
||||
reraise(err, "Couldn't setup configuration '{0}'".format(cls_path))
|
||||
cls_path = f'{mod.__name__}.{class_name}'
|
||||
|
||||
return mod
|
||||
try:
|
||||
cls = getattr(mod, class_name)
|
||||
except AttributeError as err: # pragma: no cover
|
||||
reraise(
|
||||
err,
|
||||
(
|
||||
f"Couldn't find configuration '{class_name}' in "
|
||||
f"module '{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(module.__name__,
|
||||
class_name))
|
||||
cls.post_setup()
|
||||
|
||||
except Exception as err:
|
||||
reraise(err, f"Couldn't setup configuration '{cls_path}'")
|
||||
|
||||
loader.__class__ = ConfigurationLoader
|
||||
|
|
|
|||
|
|
@ -29,21 +29,21 @@ def import_by_path(dotted_path, error_prefix=''):
|
|||
try:
|
||||
module_path, class_name = dotted_path.rsplit('.', 1)
|
||||
except ValueError:
|
||||
raise ImproperlyConfigured("{0}{1} doesn't look like "
|
||||
raise ImproperlyConfigured("{}{} doesn't look like "
|
||||
"a module path".format(error_prefix,
|
||||
dotted_path))
|
||||
try:
|
||||
module = import_module(module_path)
|
||||
except ImportError as err:
|
||||
msg = '{0}Error importing module {1}: "{2}"'.format(error_prefix,
|
||||
msg = '{}Error importing module {}: "{}"'.format(error_prefix,
|
||||
module_path,
|
||||
err)
|
||||
raise ImproperlyConfigured(msg).with_traceback(sys.exc_info()[2])
|
||||
try:
|
||||
attr = getattr(module, class_name)
|
||||
except AttributeError:
|
||||
raise ImproperlyConfigured('{0}Module "{1}" does not define a '
|
||||
'"{2}" attribute/class'.format(error_prefix,
|
||||
raise ImproperlyConfigured('{}Module "{}" does not define a '
|
||||
'"{}" attribute/class'.format(error_prefix,
|
||||
module_path,
|
||||
class_name))
|
||||
return attr
|
||||
|
|
@ -61,7 +61,7 @@ def reraise(exc, prefix=None, suffix=None):
|
|||
suffix = ''
|
||||
elif not (suffix.startswith('(') and suffix.endswith(')')):
|
||||
suffix = '(' + suffix + ')'
|
||||
exc.args = ('{0} {1} {2}'.format(prefix, args[0], suffix),) + args[1:]
|
||||
exc.args = (f'{prefix} {args[0]} {suffix}',) + args[1:]
|
||||
raise exc
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ class Value:
|
|||
else:
|
||||
environ_name = name.upper()
|
||||
if self.environ_prefix:
|
||||
environ_name = '{0}_{1}'.format(self.environ_prefix, environ_name)
|
||||
environ_name = f'{self.environ_prefix}_{environ_name}'
|
||||
return environ_name
|
||||
|
||||
def setup(self, name):
|
||||
|
|
@ -102,8 +102,8 @@ class Value:
|
|||
if full_environ_name in os.environ:
|
||||
value = self.to_python(os.environ[full_environ_name])
|
||||
elif self.environ_required:
|
||||
raise ValueError('Value {0!r} is required to be set as the '
|
||||
'environment variable {1!r}'
|
||||
raise ValueError('Value {!r} is required to be set as the '
|
||||
'environment variable {!r}'
|
||||
.format(name, full_environ_name))
|
||||
self.value = value
|
||||
return value
|
||||
|
|
@ -128,7 +128,7 @@ class BooleanValue(Value):
|
|||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.default not in (True, False):
|
||||
raise ValueError('Default value {0!r} is not a '
|
||||
raise ValueError('Default value {!r} is not a '
|
||||
'boolean value'.format(self.default))
|
||||
|
||||
def to_python(self, value):
|
||||
|
|
@ -139,7 +139,7 @@ class BooleanValue(Value):
|
|||
return False
|
||||
else:
|
||||
raise ValueError('Cannot interpret '
|
||||
'boolean value {0!r}'.format(value))
|
||||
'boolean value {!r}'.format(value))
|
||||
|
||||
|
||||
class CastingMixin:
|
||||
|
|
@ -152,12 +152,12 @@ class CastingMixin:
|
|||
try:
|
||||
self._caster = import_string(self.caster)
|
||||
except ImportError as err:
|
||||
msg = "Could not import {!r}".format(self.caster)
|
||||
msg = f"Could not import {self.caster!r}"
|
||||
raise ImproperlyConfigured(msg) from err
|
||||
elif callable(self.caster):
|
||||
self._caster = self.caster
|
||||
else:
|
||||
error = 'Cannot use caster of {0} ({1!r})'.format(self,
|
||||
error = 'Cannot use caster of {} ({!r})'.format(self,
|
||||
self.caster)
|
||||
raise ValueError(error)
|
||||
try:
|
||||
|
|
@ -345,13 +345,13 @@ class ValidationMixin:
|
|||
try:
|
||||
self._validator = import_string(self.validator)
|
||||
except ImportError as err:
|
||||
msg = "Could not import {!r}".format(self.validator)
|
||||
msg = f"Could not import {self.validator!r}"
|
||||
raise ImproperlyConfigured(msg) from err
|
||||
elif callable(self.validator):
|
||||
self._validator = self.validator
|
||||
else:
|
||||
raise ValueError('Cannot use validator of '
|
||||
'{0} ({1!r})'.format(self, self.validator))
|
||||
'{} ({!r})'.format(self, self.validator))
|
||||
if self.default:
|
||||
self.to_python(self.default)
|
||||
|
||||
|
|
@ -397,7 +397,7 @@ class PathValue(Value):
|
|||
value = super().setup(name)
|
||||
value = os.path.expanduser(value)
|
||||
if self.check_exists and not os.path.exists(value):
|
||||
raise ValueError('Path {0!r} does not exist.'.format(value))
|
||||
raise ValueError(f'Path {value!r} does not exist.')
|
||||
return os.path.abspath(value)
|
||||
|
||||
|
||||
|
|
@ -414,7 +414,7 @@ class SecretValue(Value):
|
|||
def setup(self, name):
|
||||
value = super().setup(name)
|
||||
if not value:
|
||||
raise ValueError('Secret value {0!r} is not set'.format(name))
|
||||
raise ValueError(f'Secret value {name!r} is not set')
|
||||
return value
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,11 @@
|
|||
Changelog
|
||||
---------
|
||||
|
||||
Unreleased
|
||||
^^^^^^^^^^
|
||||
|
||||
- Prevent warning about ``FORMS_URLFIELD_ASSUME_HTTPS`` on Django 5.0.
|
||||
|
||||
v2.5.1 (2023-11-30)
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
|
|
|||
5
setup.py
5
setup.py
|
|
@ -32,7 +32,7 @@ setup(
|
|||
install_requires=[
|
||||
'django>=3.2',
|
||||
],
|
||||
python_requires='>=3.8, <4.0',
|
||||
python_requires='>=3.9, <4.0',
|
||||
extras_require={
|
||||
'cache': ['django-cache-url'],
|
||||
'database': ['dj-database-url'],
|
||||
|
|
@ -52,17 +52,18 @@ setup(
|
|||
'Framework :: Django :: 4.1',
|
||||
'Framework :: Django :: 4.2',
|
||||
'Framework :: Django :: 5.0',
|
||||
'Framework :: Django :: 5.1',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3 :: Only',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
'Programming Language :: Python :: 3.12',
|
||||
'Programming Language :: Python :: 3.13',
|
||||
'Programming Language :: Python :: Implementation :: PyPy',
|
||||
'Topic :: Utilities',
|
||||
],
|
||||
|
|
|
|||
|
|
@ -6,3 +6,6 @@ class DotEnvConfiguration(Configuration):
|
|||
DOTENV = 'test_project/.env'
|
||||
|
||||
DOTENV_VALUE = values.Value()
|
||||
|
||||
def DOTENV_VALUE_METHOD(self):
|
||||
return values.Value(environ_name="DOTENV_VALUE")
|
||||
|
|
|
|||
8
tests/settings/error.py
Normal file
8
tests/settings/error.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
from configurations import Configuration
|
||||
|
||||
|
||||
class ErrorConfiguration(Configuration):
|
||||
|
||||
@classmethod
|
||||
def pre_setup(cls):
|
||||
raise ValueError("Error in pre_setup")
|
||||
|
|
@ -2,7 +2,6 @@ import os
|
|||
import uuid
|
||||
|
||||
from configurations import Configuration, pristinemethod
|
||||
from configurations.values import BooleanValue
|
||||
|
||||
|
||||
class Test(Configuration):
|
||||
|
|
@ -10,8 +9,6 @@ class Test(Configuration):
|
|||
os.path.join(os.path.dirname(
|
||||
os.path.abspath(__file__)), os.pardir))
|
||||
|
||||
ENV_LOADED = BooleanValue(False)
|
||||
|
||||
DEBUG = True
|
||||
|
||||
SITE_ID = 1
|
||||
|
|
|
|||
|
|
@ -11,4 +11,5 @@ class DotEnvLoadingTests(TestCase):
|
|||
def test_env_loaded(self):
|
||||
from tests.settings import dot_env
|
||||
self.assertEqual(dot_env.DOTENV_VALUE, 'is set')
|
||||
self.assertEqual(dot_env.DOTENV_VALUE_METHOD, 'is set')
|
||||
self.assertEqual(dot_env.DOTENV_LOADED, dot_env.DOTENV)
|
||||
|
|
|
|||
22
tests/test_error.py
Normal file
22
tests/test_error.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import os
|
||||
from django.test import TestCase
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
class ErrorTests(TestCase):
|
||||
|
||||
@patch.dict(os.environ, clear=True,
|
||||
DJANGO_CONFIGURATION='ErrorConfiguration',
|
||||
DJANGO_SETTINGS_MODULE='tests.settings.error')
|
||||
def test_env_loaded(self):
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
from tests.settings import error # noqa: F401
|
||||
|
||||
self.assertIsInstance(cm.exception, ValueError)
|
||||
self.assertEqual(
|
||||
cm.exception.args,
|
||||
(
|
||||
"Couldn't setup configuration "
|
||||
"'tests.settings.error.ErrorConfiguration': Error in pre_setup ",
|
||||
)
|
||||
)
|
||||
|
|
@ -7,7 +7,7 @@ from django.core.exceptions import ImproperlyConfigured
|
|||
|
||||
from unittest.mock import patch
|
||||
|
||||
from configurations.importer import ConfigurationImporter
|
||||
from configurations.importer import ConfigurationFinder
|
||||
|
||||
ROOT_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||
TEST_PROJECT_DIR = os.path.join(ROOT_DIR, 'test_project')
|
||||
|
|
@ -42,12 +42,14 @@ class MainTests(TestCase):
|
|||
|
||||
@patch.dict(os.environ, clear=True, DJANGO_CONFIGURATION='Test')
|
||||
def test_empty_module_var(self):
|
||||
self.assertRaises(ImproperlyConfigured, ConfigurationImporter)
|
||||
with self.assertRaises(ImproperlyConfigured):
|
||||
ConfigurationFinder()
|
||||
|
||||
@patch.dict(os.environ, clear=True,
|
||||
DJANGO_SETTINGS_MODULE='tests.settings.main')
|
||||
def test_empty_class_var(self):
|
||||
self.assertRaises(ImproperlyConfigured, ConfigurationImporter)
|
||||
with self.assertRaises(ImproperlyConfigured):
|
||||
ConfigurationFinder()
|
||||
|
||||
def test_global_settings(self):
|
||||
from configurations.base import Configuration
|
||||
|
|
@ -70,21 +72,21 @@ class MainTests(TestCase):
|
|||
DJANGO_SETTINGS_MODULE='tests.settings.main',
|
||||
DJANGO_CONFIGURATION='Test')
|
||||
def test_initialization(self):
|
||||
importer = ConfigurationImporter()
|
||||
self.assertEqual(importer.module, 'tests.settings.main')
|
||||
self.assertEqual(importer.name, 'Test')
|
||||
finder = ConfigurationFinder()
|
||||
self.assertEqual(finder.module, 'tests.settings.main')
|
||||
self.assertEqual(finder.name, 'Test')
|
||||
self.assertEqual(
|
||||
repr(importer),
|
||||
"<ConfigurationImporter for 'tests.settings.main.Test'>")
|
||||
repr(finder),
|
||||
"<ConfigurationFinder for 'tests.settings.main.Test'>")
|
||||
|
||||
@patch.dict(os.environ, clear=True,
|
||||
DJANGO_SETTINGS_MODULE='tests.settings.inheritance',
|
||||
DJANGO_CONFIGURATION='Inheritance')
|
||||
def test_initialization_inheritance(self):
|
||||
importer = ConfigurationImporter()
|
||||
self.assertEqual(importer.module,
|
||||
finder = ConfigurationFinder()
|
||||
self.assertEqual(finder.module,
|
||||
'tests.settings.inheritance')
|
||||
self.assertEqual(importer.name, 'Inheritance')
|
||||
self.assertEqual(finder.name, 'Inheritance')
|
||||
|
||||
@patch.dict(os.environ, clear=True,
|
||||
DJANGO_SETTINGS_MODULE='tests.settings.main',
|
||||
|
|
@ -93,12 +95,12 @@ class MainTests(TestCase):
|
|||
'--settings=tests.settings.main',
|
||||
'--configuration=Test'])
|
||||
def test_configuration_option(self):
|
||||
importer = ConfigurationImporter(check_options=False)
|
||||
self.assertEqual(importer.module, 'tests.settings.main')
|
||||
self.assertEqual(importer.name, 'NonExisting')
|
||||
importer = ConfigurationImporter(check_options=True)
|
||||
self.assertEqual(importer.module, 'tests.settings.main')
|
||||
self.assertEqual(importer.name, 'Test')
|
||||
finder = ConfigurationFinder(check_options=False)
|
||||
self.assertEqual(finder.module, 'tests.settings.main')
|
||||
self.assertEqual(finder.name, 'NonExisting')
|
||||
finder = ConfigurationFinder(check_options=True)
|
||||
self.assertEqual(finder.module, 'tests.settings.main')
|
||||
self.assertEqual(finder.name, 'Test')
|
||||
|
||||
def test_configuration_argument_in_cli(self):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class ValueTests(TestCase):
|
|||
|
||||
def test_value_with_default(self):
|
||||
value = Value('default', environ=False)
|
||||
self.assertEqual(type(value), type('default'))
|
||||
self.assertEqual(type(value), str)
|
||||
self.assertEqual(value, 'default')
|
||||
self.assertEqual(str(value), 'default')
|
||||
|
||||
|
|
@ -44,17 +44,17 @@ class ValueTests(TestCase):
|
|||
with env(DJANGO_TEST='override'):
|
||||
self.assertEqual(value.setup('TEST'), 'default')
|
||||
value = Value(environ_name='TEST')
|
||||
self.assertEqual(type(value), type('override'))
|
||||
self.assertEqual(type(value), str)
|
||||
self.assertEqual(value, 'override')
|
||||
self.assertEqual(str(value), 'override')
|
||||
self.assertEqual('{0}'.format(value), 'override')
|
||||
self.assertEqual(f'{value}', 'override')
|
||||
self.assertEqual('%s' % value, 'override')
|
||||
|
||||
value = Value(environ_name='TEST', late_binding=True)
|
||||
self.assertEqual(type(value), Value)
|
||||
self.assertEqual(value.value, 'override')
|
||||
self.assertEqual(str(value), 'override')
|
||||
self.assertEqual('{0}'.format(value), 'override')
|
||||
self.assertEqual(f'{value}', 'override')
|
||||
self.assertEqual('%s' % value, 'override')
|
||||
|
||||
self.assertEqual(repr(value), repr('override'))
|
||||
|
|
@ -373,17 +373,23 @@ class ValueTests(TestCase):
|
|||
value = DatabaseURLValue()
|
||||
self.assertEqual(value.default, {})
|
||||
with env(DATABASE_URL='sqlite://'):
|
||||
self.assertEqual(value.setup('DATABASE_URL'), {
|
||||
'default': {
|
||||
settings_value = value.setup('DATABASE_URL')
|
||||
# Compare the embedded dicts in the "default" entry so that the difference can be seen if
|
||||
# it fails ... DatabaseURLValue(|) uses an external app that can add additional entries
|
||||
self.assertDictEqual(
|
||||
{
|
||||
'CONN_HEALTH_CHECKS': False,
|
||||
'CONN_MAX_AGE': 0,
|
||||
'DISABLE_SERVER_SIDE_CURSORS': False,
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'HOST': '',
|
||||
'NAME': ':memory:',
|
||||
'PASSWORD': '',
|
||||
'PORT': '',
|
||||
'USER': '',
|
||||
}})
|
||||
},
|
||||
settings_value['default']
|
||||
)
|
||||
|
||||
def test_database_url_additional_args(self):
|
||||
|
||||
|
|
|
|||
14
tox.ini
14
tox.ini
|
|
@ -5,18 +5,19 @@ minversion = 1.8
|
|||
envlist =
|
||||
py311-checkqa
|
||||
docs
|
||||
py{38,39}-dj{32,41,42}
|
||||
py{39}-dj{32,41,42}
|
||||
py{310,py310}-dj{32,41,42,50,main}
|
||||
py{311}-dj{41,42,50,main}
|
||||
py{312}-dj{50,main}
|
||||
py{311}-dj{41,42,50,51,main}
|
||||
py{312}-dj{50,51,main}
|
||||
py{313}-dj{50,51,main}
|
||||
|
||||
[gh-actions]
|
||||
python =
|
||||
3.8: py38
|
||||
3.9: py39
|
||||
3.10: py310
|
||||
3.11: py311,flake8,readme
|
||||
3.12: py312
|
||||
3.13: py313
|
||||
pypy-3.10: pypy310
|
||||
|
||||
[testenv]
|
||||
|
|
@ -29,10 +30,13 @@ deps =
|
|||
dj32: django~=3.2.9
|
||||
dj41: django~=4.1.3
|
||||
dj42: django~=4.2.0
|
||||
dj50: django~=5.0.0rc1
|
||||
dj50: django~=5.0.0
|
||||
dj51: django~=5.1.0
|
||||
djmain: https://github.com/django/django/archive/main.tar.gz
|
||||
py312: setuptools
|
||||
py312: wheel
|
||||
py313: setuptools
|
||||
py313: wheel
|
||||
coverage
|
||||
coverage_enable_subprocess
|
||||
extras = testing
|
||||
|
|
|
|||
Loading…
Reference in a new issue