Compare commits

..

30 commits

Author SHA1 Message Date
Christopher Broderick
3d0d4216ca
Merge pull request #399 from jazzband/dependabot/github_actions/github-actions-4ed8cc3c8a
Bump the github-actions group across 1 directory with 2 updates
2024-11-18 23:53:09 +00:00
dependabot[bot]
007161adb0
Bump the github-actions group across 1 directory with 2 updates
Bumps the github-actions group with 2 updates in the / directory: [actions/cache](https://github.com/actions/cache) and [codecov/codecov-action](https://github.com/codecov/codecov-action).


Updates `actions/cache` from 3 to 4
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v3...v4)

Updates `codecov/codecov-action` from 4 to 5
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-18 21:33:41 +00:00
Christopher Broderick
57b6827ae3
Merge pull request #386 from adamchainz/pep_451
Move to PEP-451 style loader
2024-11-18 17:34:02 +00:00
Adam Johnson
b8f66f76ee Move to PEP-451 style loader 2024-11-18 16:52:55 +00:00
Christopher Broderick
fcd03ada0f
Merge pull request #398 from adamchainz/remove_env_loaded
Remove ENV_LOADED from test class
2024-11-18 16:08:45 +00:00
Christopher Broderick
0bf416155e
Merge pull request #384 from adamchainz/deprecated_form_setting
Prevent FORMS_URLFIELD_ASSUME_HTTPS warning on Django 5.0
2024-11-18 13:30:24 +00:00
Adam Johnson
cec5f7492a Remove ENV_LOADED from test class 2024-11-18 12:04:04 +00:00
Adam Johnson
b8e94fd796 Prevent FORMS_URLFIELD_ASSUME_HTTPS warning on Django 5.0 2024-11-18 11:37:17 +00:00
Christopher Broderick
f37ed87d6e
Merge pull request #393 from cclauss/patch-2
Keep GitHub Actions up to date with GitHub's Dependabot
2024-11-09 08:44:30 +00:00
Christopher Broderick
7f0f29d161
Merge pull request #389 from cclauss/patch-1
GitHub Actions: Upgrade the release workflow to Python 3.13
2024-11-08 19:31:36 +00:00
Christian Clauss
9688dae5e1
setup.py: Programming Language :: Python :: 3.13 2024-11-08 20:15:28 +01:00
Christian Clauss
4efa08f81a GitHub Actions: Upgrade to Python 3.12
https://github.com/actions/setup-python/releases
2024-11-08 18:25:41 +01:00
Christian Clauss
c67eab3507
Keep GitHub Actions up to date with GitHub's Dependabot
Fixes software supply chain safety warnings like at the bottom right of

https://github.com/jazzband/django-configurations/actions/runs/11743152917
* [Keeping your actions up to date with Dependabot](https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot)
* [Configuration options for the dependabot.yml file - package-ecosystem](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem)
2024-11-08 15:13:38 +01:00
Christopher Broderick
711fa66654
Merge pull request #392 from jazzband/fix_code_coverage_and_newly_failing_test
Fix code coverage and newly failing test
2024-11-08 13:36:11 +00:00
Christopher Broderick
448c5ab1b6 Address flake8 issues 2024-11-07 13:33:00 +00:00
Christopher Broderick
65d326a95a Add code coverage token param 2024-11-07 09:59:51 +00:00
Christopher Broderick
63cac8d54e Fix failing test due to external library change 2024-10-28 20:55:21 +00:00
Christopher Broderick
ba24d3c324 Fixes to accomodate v4 codecov/codecov-action changes 2024-10-28 20:54:40 +00:00
Christopher Broderick
e1091160b1
Merge pull request #383 from kloczek/master
really drop support for python<=3.7
2024-09-27 10:52:36 +01:00
Arkadiusz Adamski
880484d3b7 Update README.rst
To be consistent with previous examples.
2024-09-13 17:38:31 +02:00
Kamil Paduszyński
ef4f49d236 Fix #374 -- Fix URL in Configuration.load_dotenv docstring 2024-09-13 17:36:59 +02:00
Tomasz Kłoczko
6dc2340dfe really drop support for python<=3.7
Filer all code over `pyupgrade --py38`.

Signed-off-by: Tomasz Kłoczko <kloczek@github.com>
2024-03-18 16:08:42 +00:00
Paolo Melchiorre
ffe979b63c Fix #372 -- Add support for Python 3.12 2023-11-30 10:53:39 +01:00
Paolo Melchiorre
eba6e2c6d9
Fix #375 -- Add Django 5.0 classifier (#376) 2023-11-28 09:26:45 +01:00
Paolo Melchiorre
38641a9dea Release v2.5 2023-10-20 21:15:30 +02:00
Camilo Nova
9b7bd34812
Merge pull request #369 from pauloxnet/ticket_368
Fix #368 -- Update Python and Django versions
2023-10-16 09:19:10 -05:00
Paolo Melchiorre
df2a7f18fd
Fix #368 -- Update Python and Django versions 2023-10-04 16:52:16 +02:00
Michal Szczesny
27f67a58a4 Replace imp with importlib
This project uses the imp module which has been deprecated since Python 3.4 and set for removal in 3.12:
• Raised PendingDeprecationWarning since 3.4 (2014)
• Raised DeprecationWarning since 3.5 (2015)
• Updated DeprecationWarning to say removal in 3.12 since 3.10 (2021)
• Removal planned for 3.12 (2023)

This change removes the dependency on imp in favour of importlib.

Co-authored-by: @jbkkd
Inspired by: @mgorny

https://github.com/jazzband/django-configurations/issues/358
2023-09-27 14:08:16 +03:00
Michal Szczesny
dce5f37a93
Merge pull request #366 from washeck/master
Add pypy 3.10 to the testing matrix
2023-09-27 11:31:26 +01:00
Vaclav Rehak
2278975744 Add pypy 3.10 to the testing matrix 2023-09-22 17:55:59 +02:00
20 changed files with 221 additions and 138 deletions

13
.github/dependabot.yml vendored Normal file
View 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

View file

@ -11,22 +11,22 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: 3.8
python-version: 3.x
- name: Get pip cache dir
id: pip-cache
run: |
echo "::set-output name=dir::$(pip cache dir)"
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') }}
@ -50,4 +50,4 @@ jobs:
with:
user: jazzband
password: ${{ secrets.JAZZBAND_RELEASE_KEY }}
repository_url: https://jazzband.co/projects/django-configurations/upload
repository-url: https://jazzband.co/projects/django-configurations/upload

View file

@ -11,15 +11,15 @@ jobs:
runs-on: ubuntu-latest
strategy:
fail-fast: false
max-parallel: 5
max-parallel: 6
matrix:
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', 'pypy-3.7', 'pypy-3.8', 'pypy-3.9']
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', 'pypy-3.10']
steps:
- uses: actions/checkout@v3
- 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 }}

View file

@ -1,9 +1,9 @@
---
version: 2
build:
os: ubuntu-20.04
os: ubuntu-22.04
tools:
python: "3.9"
python: "3.10"
python:
install:
- requirements: docs/requirements.txt

View file

@ -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

View file

@ -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 "

View file

@ -1,4 +1,4 @@
import imp
from importlib.machinery import PathFinder
import logging
import os
import sys
@ -46,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, "
@ -70,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
@ -121,58 +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_module(self, fullname, path=None):
def find_spec(self, fullname, path=None, target=None):
if fullname is not None and fullname == self.module:
module = fullname.rsplit('.', 1)[-1]
return ConfigurationLoader(self.name,
imp.find_module(module, path))
return None
class ConfigurationLoader:
def __init__(self, name, location):
self.name = name
self.location = location
def load_module(self, fullname):
if fullname in sys.modules:
mod = sys.modules[fullname] # pragma: no cover
spec = super().find_spec(fullname, path, target)
if spec is not None:
wrap_loader(spec.loader, self.name)
return spec
else:
mod = imp.load_module(fullname, *self.location)
cls_path = '{0}.{1}'.format(mod.__name__, self.name)
return None
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)
setattr(mod, 'CONFIGURATION', '{0}.{1}'.format(fullname,
self.name))
cls.post_setup()
def wrap_loader(loader, class_name):
class ConfigurationLoader(loader.__class__):
def exec_module(self, module):
super().exec_module(module)
except Exception as err:
reraise(err, "Couldn't setup configuration '{0}'".format(cls_path))
mod = module
return mod
cls_path = f'{mod.__name__}.{class_name}'
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

View file

@ -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

View file

@ -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

View file

@ -1,7 +1,4 @@
try:
from importlib.metadata import PackageNotFoundError, version
except ImportError:
from importlib_metadata import PackageNotFoundError, version
from importlib.metadata import PackageNotFoundError, version
try:
__version__ = version("django-configurations")

View file

@ -3,6 +3,30 @@
Changelog
---------
Unreleased
^^^^^^^^^^
- Prevent warning about ``FORMS_URLFIELD_ASSUME_HTTPS`` on Django 5.0.
v2.5.1 (2023-11-30)
^^^^^^^^^^^^^^^^^^^
- Add compatibility with Python 3.12
v2.5 (2023-10-20)
^^^^^^^^^^^^^^^^^
- Update Github actions and fix pipeline warnings
- Add compatibility with Django 5.0
- **BACKWARD INCOMPATIBLE** Drop compatibility for Django 4.0
- **BACKWARD INCOMPATIBLE** Drop compatibility for Python 3.7 and PyPy < 3.10
v2.4.2 (2023-09-27)
^^^^^^^^^^^^^^^^^^^
- Replace imp (due for removal in Python 3.12) with importlib
- Test on PyPy 3.10.
v2.4.1 (2023-04-04)
^^^^^^^^^^^^^^^^^^^

View file

@ -31,9 +31,8 @@ setup(
},
install_requires=[
'django>=3.2',
'importlib-metadata;python_version<"3.8"',
],
python_requires='>=3.7, <4.0',
python_requires='>=3.9, <4.0',
extras_require={
'cache': ['django-cache-url'],
'database': ['dj-database-url'],
@ -50,20 +49,21 @@ setup(
'Development Status :: 5 - Production/Stable',
'Framework :: Django',
'Framework :: Django :: 3.2',
'Framework :: Django :: 4.0',
'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.7',
'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',
],

View file

@ -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
View file

@ -0,0 +1,8 @@
from configurations import Configuration
class ErrorConfiguration(Configuration):
@classmethod
def pre_setup(cls):
raise ValueError("Error in pre_setup")

View file

@ -1,9 +1,7 @@
import os
import uuid
import django
from configurations import Configuration, pristinemethod
from configurations.values import BooleanValue
class Test(Configuration):
@ -11,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
@ -36,9 +32,6 @@ class Test(Configuration):
ROOT_URLCONF = 'tests.urls'
if django.VERSION[:2] < (1, 6):
TEST_RUNNER = 'discover_runner.DiscoverRunner'
@property
def ALLOWED_HOSTS(self):
allowed_hosts = super().ALLOWED_HOSTS[:]

View file

@ -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
View 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 ",
)
)

View file

@ -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):
"""

View file

@ -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):

30
tox.ini
View file

@ -3,23 +3,22 @@ skipsdist = true
usedevelop = true
minversion = 1.8
envlist =
py37-checkqa
py311-checkqa
docs
py{37,py37}-dj{32}
py{38,py38,39,py39}-dj{32,40,41,42}
py{310}-dj{32,40,41,42,main}
py{311}-dj{41,42,main}
py{39}-dj{32,41,42}
py{310,py310}-dj{32,41,42,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.7: py37,flake8,readme
3.8: py38
3.9: py39
3.10: py310
3.11: py311
pypy-3.7: pypy37
pypy-3.8: pypy38
pypy-3.9: pypy39
3.11: py311,flake8,readme
3.12: py312
3.13: py313
pypy-3.10: pypy310
[testenv]
usedevelop = true
@ -29,10 +28,15 @@ setenv =
COVERAGE_PROCESS_START = {toxinidir}/setup.cfg
deps =
dj32: django~=3.2.9
dj40: django~=4.0.0
dj41: django~=4.1.3
dj42: django~=4.2.0
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
@ -43,7 +47,7 @@ commands =
coverage report -m --skip-covered
coverage xml
[testenv:py37-checkqa]
[testenv:py311-checkqa]
commands =
flake8 {toxinidir}
check-manifest -v