mirror of
https://github.com/jazzband/django-configurations.git
synced 2026-03-16 22:20:27 +00:00
Compare commits
41 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 | ||
|
|
ffe979b63c | ||
|
|
eba6e2c6d9 | ||
|
|
38641a9dea | ||
|
|
9b7bd34812 | ||
|
|
df2a7f18fd | ||
|
|
27f67a58a4 | ||
|
|
dce5f37a93 | ||
|
|
2278975744 | ||
|
|
cad6dcb7f0 | ||
|
|
adaf92085f | ||
|
|
a1f072ebf3 | ||
|
|
6f47271526 | ||
|
|
befe7f1e0d | ||
|
|
352d95b2ab | ||
|
|
1f8bac5ba4 | ||
|
|
17ca033d33 | ||
|
|
7520ae4123 | ||
|
|
ac5408d7eb | ||
|
|
e2861e6327 |
27 changed files with 279 additions and 173 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
|
||||||
18
.github/workflows/release.yml
vendored
18
.github/workflows/release.yml
vendored
|
|
@ -11,22 +11,22 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: 3.x
|
||||||
|
|
||||||
- name: Get pip cache dir
|
- name: Get pip cache dir
|
||||||
id: pip-cache
|
id: pip-cache
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=dir::$(pip cache dir)"
|
echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Cache
|
- name: Cache
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pip-cache.outputs.dir }}
|
path: ${{ steps.pip-cache.outputs.dir }}
|
||||||
key: release-${{ hashFiles('**/setup.py') }}
|
key: release-${{ hashFiles('**/setup.py') }}
|
||||||
|
|
@ -35,8 +35,8 @@ jobs:
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install -U pip
|
python -m pip install --upgrade pip
|
||||||
python -m pip install -U setuptools twine wheel
|
python -m pip install --upgrade setuptools twine wheel
|
||||||
|
|
||||||
- name: Build package
|
- name: Build package
|
||||||
run: |
|
run: |
|
||||||
|
|
@ -46,8 +46,8 @@ jobs:
|
||||||
|
|
||||||
- name: Upload packages to Jazzband
|
- name: Upload packages to Jazzband
|
||||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
|
||||||
uses: pypa/gh-action-pypi-publish@master
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
with:
|
with:
|
||||||
user: jazzband
|
user: jazzband
|
||||||
password: ${{ secrets.JAZZBAND_RELEASE_KEY }}
|
password: ${{ secrets.JAZZBAND_RELEASE_KEY }}
|
||||||
repository_url: https://jazzband.co/projects/django-configurations/upload
|
repository-url: https://jazzband.co/projects/django-configurations/upload
|
||||||
|
|
|
||||||
24
.github/workflows/test.yml
vendored
24
.github/workflows/test.yml
vendored
|
|
@ -11,25 +11,25 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
max-parallel: 5
|
max-parallel: 6
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ['3.7', '3.8', '3.9', '3.10', '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:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
- name: Get pip cache dir
|
- name: Get pip cache dir
|
||||||
id: pip-cache
|
id: pip-cache
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=dir::$(pip cache dir)"
|
echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Cache
|
- name: Cache
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pip-cache.outputs.dir }}
|
path: ${{ steps.pip-cache.outputs.dir }}
|
||||||
key:
|
key:
|
||||||
|
|
@ -40,14 +40,18 @@ jobs:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
python -m pip install --upgrade tox tox-gh-actions
|
python -m pip install --upgrade "tox<4" "tox-gh-actions<3"
|
||||||
|
|
||||||
- name: Tox tests
|
- name: Tox tests
|
||||||
run: |
|
run: |
|
||||||
tox -v
|
tox --verbose
|
||||||
|
|
||||||
- name: Upload coverage
|
- name: Upload coverage
|
||||||
uses: codecov/codecov-action@v1
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
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
|
fail_ci_if_error: true
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
---
|
---
|
||||||
version: 2
|
version: 2
|
||||||
build:
|
build:
|
||||||
os: ubuntu-20.04
|
os: ubuntu-22.04
|
||||||
tools:
|
tools:
|
||||||
python: "3.9"
|
python: "3.10"
|
||||||
python:
|
python:
|
||||||
install:
|
install:
|
||||||
- requirements: docs/requirements.txt
|
- requirements: docs/requirements.txt
|
||||||
|
|
|
||||||
2
LICENSE
2
LICENSE
|
|
@ -1,4 +1,4 @@
|
||||||
Copyright (c) 2012-2022, Jannis Leidel and other contributors.
|
Copyright (c) 2012-2023, Jannis Leidel and other contributors.
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
|
|
||||||
12
README.rst
12
README.rst
|
|
@ -47,13 +47,13 @@ Install django-configurations:
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
pip install django-configurations
|
$ python -m pip install django-configurations
|
||||||
|
|
||||||
or, alternatively, if you want to use URL-based values:
|
or, alternatively, if you want to use URL-based values:
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
pip install django-configurations[cache,database,email,search]
|
$ python -m pip install django-configurations[cache,database,email,search]
|
||||||
|
|
||||||
Then subclass the included ``configurations.Configuration`` class in your
|
Then subclass the included ``configurations.Configuration`` class in your
|
||||||
project's **settings.py** or any other module you're using to store the
|
project's **settings.py** or any other module you're using to store the
|
||||||
|
|
@ -73,14 +73,14 @@ you just created, e.g. in bash:
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
export DJANGO_CONFIGURATION=Dev
|
$ export DJANGO_CONFIGURATION=Dev
|
||||||
|
|
||||||
and the ``DJANGO_SETTINGS_MODULE`` environment variable to the module
|
and the ``DJANGO_SETTINGS_MODULE`` environment variable to the module
|
||||||
import path as usual, e.g. in bash:
|
import path as usual, e.g. in bash:
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
export DJANGO_SETTINGS_MODULE=mysite.settings
|
$ export DJANGO_SETTINGS_MODULE=mysite.settings
|
||||||
|
|
||||||
*Alternatively* supply the ``--configuration`` option when using Django
|
*Alternatively* supply the ``--configuration`` option when using Django
|
||||||
management commands along the lines of Django's default ``--settings``
|
management commands along the lines of Django's default ``--settings``
|
||||||
|
|
@ -88,7 +88,7 @@ command line option, e.g.
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
python manage.py runserver --settings=mysite.settings --configuration=Dev
|
$ python -m manage runserver --settings=mysite.settings --configuration=Dev
|
||||||
|
|
||||||
To enable Django to use your configuration you now have to modify your
|
To enable Django to use your configuration you now have to modify your
|
||||||
**manage.py**, **wsgi.py** or **asgi.py** script to use django-configurations's versions
|
**manage.py**, **wsgi.py** or **asgi.py** script to use django-configurations's versions
|
||||||
|
|
@ -137,7 +137,7 @@ Or if you are not serving your app via WSGI but ASGI instead, you need to modify
|
||||||
import os
|
import os
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
|
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
|
from configurations.asgi import get_asgi_application
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,12 +46,22 @@ class ConfigurationBase(type):
|
||||||
# suppressed, as downstream users are expected to make a decision.
|
# 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
|
# https://docs.djangoproject.com/en/3.2/releases/3.2/#customizing-type-of-auto-created-primary-keys
|
||||||
"DEFAULT_AUTO_FIELD",
|
"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_DAYS is deprecated in favor of
|
||||||
# PASSWORD_RESET_TIMEOUT in Django 3.1
|
# PASSWORD_RESET_TIMEOUT in Django 3.1
|
||||||
# https://github.com/django/django/commit/226ebb17290b604ef29e82fb5c1fbac3594ac163#diff-ec2bed07bb264cb95a80f08d71a47c06R163-R170
|
# https://github.com/django/django/commit/226ebb17290b604ef29e82fb5c1fbac3594ac163#diff-ec2bed07bb264cb95a80f08d71a47c06R163-R170
|
||||||
if "PASSWORD_RESET_TIMEOUT" in settings_vars:
|
if "PASSWORD_RESET_TIMEOUT" in settings_vars:
|
||||||
deprecated_settings.add("PASSWORD_RESET_TIMEOUT_DAYS")
|
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:
|
for deprecated_setting in deprecated_settings:
|
||||||
if deprecated_setting in settings_vars:
|
if deprecated_setting in settings_vars:
|
||||||
del settings_vars[deprecated_setting]
|
del settings_vars[deprecated_setting]
|
||||||
|
|
@ -60,7 +70,7 @@ class ConfigurationBase(type):
|
||||||
return super().__new__(cls, name, bases, attrs)
|
return super().__new__(cls, name, bases, attrs)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<Configuration '{0}.{1}'>".format(self.__module__,
|
return "<Configuration '{}.{}'>".format(self.__module__,
|
||||||
self.__name__)
|
self.__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -97,10 +107,10 @@ class Configuration(metaclass=ConfigurationBase):
|
||||||
environment variables from a .env file located in the project root
|
environment variables from a .env file located in the project root
|
||||||
or provided directory.
|
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
|
https://gist.github.com/bennylope/2999704
|
||||||
"""
|
"""
|
||||||
# check if the class has DOTENV set wether with a path or None
|
# check if the class has DOTENV set whether with a path or None
|
||||||
dotenv = getattr(cls, 'DOTENV', None)
|
dotenv = getattr(cls, 'DOTENV', None)
|
||||||
|
|
||||||
# if DOTENV is falsy we want to disable it
|
# if DOTENV is falsy we want to disable it
|
||||||
|
|
@ -109,7 +119,7 @@ class Configuration(metaclass=ConfigurationBase):
|
||||||
|
|
||||||
# now check if we can access the file since we know we really want to
|
# now check if we can access the file since we know we really want to
|
||||||
try:
|
try:
|
||||||
with open(dotenv, 'r') as f:
|
with open(dotenv) as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
raise ImproperlyConfigured("Couldn't read .env file "
|
raise ImproperlyConfigured("Couldn't read .env file "
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import imp
|
from importlib.machinery import PathFinder
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -46,12 +46,12 @@ def install(check_options=False):
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
base.BaseCommand.create_parser = create_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)
|
sys.meta_path.insert(0, importer)
|
||||||
installed = True
|
installed = True
|
||||||
|
|
||||||
|
|
||||||
class ConfigurationImporter:
|
class ConfigurationFinder(PathFinder):
|
||||||
modvar = SETTINGS_ENVIRONMENT_VARIABLE
|
modvar = SETTINGS_ENVIRONMENT_VARIABLE
|
||||||
namevar = CONFIGURATION_ENVIRONMENT_VARIABLE
|
namevar = CONFIGURATION_ENVIRONMENT_VARIABLE
|
||||||
error_msg = ("Configuration cannot be imported, "
|
error_msg = ("Configuration cannot be imported, "
|
||||||
|
|
@ -70,7 +70,7 @@ class ConfigurationImporter:
|
||||||
self.announce()
|
self.announce()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<ConfigurationImporter for '{0}.{1}'>".format(self.module,
|
return "<ConfigurationFinder for '{}.{}'>".format(self.module,
|
||||||
self.name)
|
self.name)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -121,58 +121,60 @@ class ConfigurationImporter:
|
||||||
if (self.argv[1] == 'runserver'
|
if (self.argv[1] == 'runserver'
|
||||||
and os.environ.get('RUN_MAIN') == 'true'):
|
and os.environ.get('RUN_MAIN') == 'true'):
|
||||||
|
|
||||||
message = ("django-configurations version {0}, using "
|
message = ("django-configurations version {}, using "
|
||||||
"configuration {1}".format(__version__ or "",
|
"configuration {}".format(__version__ or "",
|
||||||
self.name))
|
self.name))
|
||||||
self.logger.debug(stylize(message))
|
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:
|
if fullname is not None and fullname == self.module:
|
||||||
module = fullname.rsplit('.', 1)[-1]
|
spec = super().find_spec(fullname, path, target)
|
||||||
return ConfigurationLoader(self.name,
|
if spec is not None:
|
||||||
imp.find_module(module, path))
|
wrap_loader(spec.loader, self.name)
|
||||||
return None
|
return spec
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
else:
|
else:
|
||||||
mod = imp.load_module(fullname, *self.location)
|
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)
|
|
||||||
|
|
||||||
setattr(mod, 'CONFIGURATION', '{0}.{1}'.format(fullname,
|
def wrap_loader(loader, class_name):
|
||||||
self.name))
|
class ConfigurationLoader(loader.__class__):
|
||||||
cls.post_setup()
|
def exec_module(self, module):
|
||||||
|
super().exec_module(module)
|
||||||
|
|
||||||
except Exception as err:
|
mod = module
|
||||||
reraise(err, "Couldn't setup configuration '{0}'".format(cls_path))
|
|
||||||
|
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -29,21 +29,21 @@ def import_by_path(dotted_path, error_prefix=''):
|
||||||
try:
|
try:
|
||||||
module_path, class_name = dotted_path.rsplit('.', 1)
|
module_path, class_name = dotted_path.rsplit('.', 1)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ImproperlyConfigured("{0}{1} doesn't look like "
|
raise ImproperlyConfigured("{}{} doesn't look like "
|
||||||
"a module path".format(error_prefix,
|
"a module path".format(error_prefix,
|
||||||
dotted_path))
|
dotted_path))
|
||||||
try:
|
try:
|
||||||
module = import_module(module_path)
|
module = import_module(module_path)
|
||||||
except ImportError as err:
|
except ImportError as err:
|
||||||
msg = '{0}Error importing module {1}: "{2}"'.format(error_prefix,
|
msg = '{}Error importing module {}: "{}"'.format(error_prefix,
|
||||||
module_path,
|
module_path,
|
||||||
err)
|
err)
|
||||||
raise ImproperlyConfigured(msg).with_traceback(sys.exc_info()[2])
|
raise ImproperlyConfigured(msg).with_traceback(sys.exc_info()[2])
|
||||||
try:
|
try:
|
||||||
attr = getattr(module, class_name)
|
attr = getattr(module, class_name)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise ImproperlyConfigured('{0}Module "{1}" does not define a '
|
raise ImproperlyConfigured('{}Module "{}" does not define a '
|
||||||
'"{2}" attribute/class'.format(error_prefix,
|
'"{}" attribute/class'.format(error_prefix,
|
||||||
module_path,
|
module_path,
|
||||||
class_name))
|
class_name))
|
||||||
return attr
|
return attr
|
||||||
|
|
@ -61,7 +61,7 @@ def reraise(exc, prefix=None, suffix=None):
|
||||||
suffix = ''
|
suffix = ''
|
||||||
elif not (suffix.startswith('(') and suffix.endswith(')')):
|
elif not (suffix.startswith('(') and suffix.endswith(')')):
|
||||||
suffix = '(' + suffix + ')'
|
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
|
raise exc
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ class Value:
|
||||||
else:
|
else:
|
||||||
environ_name = name.upper()
|
environ_name = name.upper()
|
||||||
if self.environ_prefix:
|
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
|
return environ_name
|
||||||
|
|
||||||
def setup(self, name):
|
def setup(self, name):
|
||||||
|
|
@ -102,8 +102,8 @@ class Value:
|
||||||
if full_environ_name in os.environ:
|
if full_environ_name in os.environ:
|
||||||
value = self.to_python(os.environ[full_environ_name])
|
value = self.to_python(os.environ[full_environ_name])
|
||||||
elif self.environ_required:
|
elif self.environ_required:
|
||||||
raise ValueError('Value {0!r} is required to be set as the '
|
raise ValueError('Value {!r} is required to be set as the '
|
||||||
'environment variable {1!r}'
|
'environment variable {!r}'
|
||||||
.format(name, full_environ_name))
|
.format(name, full_environ_name))
|
||||||
self.value = value
|
self.value = value
|
||||||
return value
|
return value
|
||||||
|
|
@ -112,7 +112,7 @@ class Value:
|
||||||
"""
|
"""
|
||||||
Convert the given value of a environment variable into an
|
Convert the given value of a environment variable into an
|
||||||
appropriate Python representation of the value.
|
appropriate Python representation of the value.
|
||||||
This should be overriden when subclassing.
|
This should be overridden when subclassing.
|
||||||
"""
|
"""
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
@ -128,7 +128,7 @@ class BooleanValue(Value):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if self.default not in (True, False):
|
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))
|
'boolean value'.format(self.default))
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
|
|
@ -139,7 +139,7 @@ class BooleanValue(Value):
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
raise ValueError('Cannot interpret '
|
raise ValueError('Cannot interpret '
|
||||||
'boolean value {0!r}'.format(value))
|
'boolean value {!r}'.format(value))
|
||||||
|
|
||||||
|
|
||||||
class CastingMixin:
|
class CastingMixin:
|
||||||
|
|
@ -152,12 +152,12 @@ class CastingMixin:
|
||||||
try:
|
try:
|
||||||
self._caster = import_string(self.caster)
|
self._caster = import_string(self.caster)
|
||||||
except ImportError as err:
|
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
|
raise ImproperlyConfigured(msg) from err
|
||||||
elif callable(self.caster):
|
elif callable(self.caster):
|
||||||
self._caster = self.caster
|
self._caster = self.caster
|
||||||
else:
|
else:
|
||||||
error = 'Cannot use caster of {0} ({1!r})'.format(self,
|
error = 'Cannot use caster of {} ({!r})'.format(self,
|
||||||
self.caster)
|
self.caster)
|
||||||
raise ValueError(error)
|
raise ValueError(error)
|
||||||
try:
|
try:
|
||||||
|
|
@ -345,13 +345,13 @@ class ValidationMixin:
|
||||||
try:
|
try:
|
||||||
self._validator = import_string(self.validator)
|
self._validator = import_string(self.validator)
|
||||||
except ImportError as err:
|
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
|
raise ImproperlyConfigured(msg) from err
|
||||||
elif callable(self.validator):
|
elif callable(self.validator):
|
||||||
self._validator = self.validator
|
self._validator = self.validator
|
||||||
else:
|
else:
|
||||||
raise ValueError('Cannot use validator of '
|
raise ValueError('Cannot use validator of '
|
||||||
'{0} ({1!r})'.format(self, self.validator))
|
'{} ({!r})'.format(self, self.validator))
|
||||||
if self.default:
|
if self.default:
|
||||||
self.to_python(self.default)
|
self.to_python(self.default)
|
||||||
|
|
||||||
|
|
@ -397,7 +397,7 @@ class PathValue(Value):
|
||||||
value = super().setup(name)
|
value = super().setup(name)
|
||||||
value = os.path.expanduser(value)
|
value = os.path.expanduser(value)
|
||||||
if self.check_exists and not os.path.exists(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)
|
return os.path.abspath(value)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -414,7 +414,7 @@ class SecretValue(Value):
|
||||||
def setup(self, name):
|
def setup(self, name):
|
||||||
value = super().setup(name)
|
value = super().setup(name)
|
||||||
if not value:
|
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
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
try:
|
from importlib.metadata import PackageNotFoundError, version
|
||||||
from importlib.metadata import PackageNotFoundError, version
|
|
||||||
except ImportError:
|
|
||||||
from importlib_metadata import PackageNotFoundError, version
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
__version__ = version("django-configurations")
|
__version__ = version("django-configurations")
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,37 @@
|
||||||
Changelog
|
Changelog
|
||||||
---------
|
---------
|
||||||
|
|
||||||
unreleased
|
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)
|
||||||
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Use furo as documentation theme
|
||||||
|
- Add compatibility with Django 4.2 - fix "STATICFILES_STORAGE/STORAGES are mutually exclusive" error.
|
||||||
|
- Test Django 4.1.3+ on Python 3.11
|
||||||
|
|
||||||
v2.4 (2022-08-24)
|
v2.4 (2022-08-24)
|
||||||
^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import configurations
|
||||||
|
|
||||||
# -- Project information -----------------------------------------------------
|
# -- Project information -----------------------------------------------------
|
||||||
project = 'django-configurations'
|
project = 'django-configurations'
|
||||||
copyright = '2012-2022, Jannis Leidel and other contributors'
|
copyright = '2012-2023, Jannis Leidel and other contributors'
|
||||||
author = 'Jannis Leidel and other contributors'
|
author = 'Jannis Leidel and other contributors'
|
||||||
|
|
||||||
release = configurations.__version__
|
release = configurations.__version__
|
||||||
|
|
@ -28,7 +28,7 @@ intersphinx_mapping = {
|
||||||
}
|
}
|
||||||
|
|
||||||
# -- Options for HTML output -------------------------------------------------
|
# -- Options for HTML output -------------------------------------------------
|
||||||
html_theme = 'sphinx_rtd_theme'
|
html_theme = 'furo'
|
||||||
|
|
||||||
# -- Options for Epub output ---------------------------------------------------
|
# -- Options for Epub output ---------------------------------------------------
|
||||||
epub_title = project
|
epub_title = project
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ Example:
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
$ tree mysite_env/
|
$ tree --noreport mysite_env/
|
||||||
mysite_env/
|
mysite_env/
|
||||||
├── DJANGO_SETTINGS_MODULE
|
├── DJANGO_SETTINGS_MODULE
|
||||||
├── DJANGO_DEBUG
|
├── DJANGO_DEBUG
|
||||||
|
|
@ -82,10 +82,8 @@ Example:
|
||||||
├── DJANGO_CACHE_URL
|
├── DJANGO_CACHE_URL
|
||||||
└── PYTHONSTARTUP
|
└── PYTHONSTARTUP
|
||||||
|
|
||||||
0 directories, 3 files
|
|
||||||
$ cat mysite_env/DJANGO_CACHE_URL
|
$ cat mysite_env/DJANGO_CACHE_URL
|
||||||
redis://user@host:port/1
|
redis://user@host:port/1
|
||||||
$
|
|
||||||
|
|
||||||
Then, to enable the ``mysite_env`` environment variables, simply use the
|
Then, to enable the ``mysite_env`` environment variables, simply use the
|
||||||
``envdir`` command line tool as a prefix for your program, e.g.:
|
``envdir`` command line tool as a prefix for your program, e.g.:
|
||||||
|
|
@ -151,13 +149,13 @@ First install Django 1.8.x and django-configurations:
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
$ pip install -r https://raw.github.com/jazzband/django-configurations/templates/1.8.x/requirements.txt
|
$ python -m pip install -r https://raw.github.com/jazzband/django-configurations/templates/1.8.x/requirements.txt
|
||||||
|
|
||||||
Or Django 1.8:
|
Or Django 1.8:
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
$ django-admin.py startproject mysite -v2 --template https://github.com/jazzband/django-configurations/archive/templates/1.8.x.zip
|
$ python -m django startproject mysite -v2 --template https://github.com/jazzband/django-configurations/archive/templates/1.8.x.zip
|
||||||
|
|
||||||
Now you have a default Django 1.8.x project in the ``mysite``
|
Now you have a default Django 1.8.x project in the ``mysite``
|
||||||
directory that uses django-configurations.
|
directory that uses django-configurations.
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,6 @@ Bugs and feature requests
|
||||||
As always your mileage may vary, so please don't hesitate to send feature
|
As always your mileage may vary, so please don't hesitate to send feature
|
||||||
requests and bug reports:
|
requests and bug reports:
|
||||||
|
|
||||||
https://github.com/jazzband/django-configurations/issues
|
- https://github.com/jazzband/django-configurations/issues
|
||||||
|
|
||||||
Thanks!
|
Thanks!
|
||||||
|
|
@ -3,7 +3,7 @@ Usage patterns
|
||||||
|
|
||||||
There are various configuration patterns that can be implemented with
|
There are various configuration patterns that can be implemented with
|
||||||
django-configurations. The most common pattern is to have a base class
|
django-configurations. The most common pattern is to have a base class
|
||||||
and various subclasses based on the enviroment they are supposed to be
|
and various subclasses based on the environment they are supposed to be
|
||||||
used in, e.g. in production, staging and development.
|
used in, e.g. in production, staging and development.
|
||||||
|
|
||||||
Server specific settings
|
Server specific settings
|
||||||
|
|
@ -31,9 +31,9 @@ it should be ``Prod``. In Bash that would be:
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
export DJANGO_SETTINGS_MODULE=mysite.settings
|
$ export DJANGO_SETTINGS_MODULE=mysite.settings
|
||||||
export DJANGO_CONFIGURATION=Prod
|
$ export DJANGO_CONFIGURATION=Prod
|
||||||
python manage.py runserver
|
$ python -m manage runserver
|
||||||
|
|
||||||
Alternatively you can use the ``--configuration`` option when using Django
|
Alternatively you can use the ``--configuration`` option when using Django
|
||||||
management commands along the lines of Django's default ``--settings``
|
management commands along the lines of Django's default ``--settings``
|
||||||
|
|
@ -41,7 +41,7 @@ command line option, e.g.
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
python manage.py runserver --settings=mysite.settings --configuration=Prod
|
$ python -m manage runserver --settings=mysite.settings --configuration=Prod
|
||||||
|
|
||||||
Property settings
|
Property settings
|
||||||
-----------------
|
-----------------
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
Sphinx>4
|
Sphinx>4
|
||||||
sphinx-rtd-theme
|
furo
|
||||||
docutils<0.18 # https://github.com/readthedocs/readthedocs.org/issues/8616
|
docutils
|
||||||
|
|
|
||||||
|
|
@ -86,9 +86,11 @@ prefixed with ``DJANGO_``. E.g.:
|
||||||
django-configurations will try to read the ``DJANGO_ROOT_URLCONF`` environment
|
django-configurations will try to read the ``DJANGO_ROOT_URLCONF`` environment
|
||||||
variable when deciding which value the ``ROOT_URLCONF`` setting should have.
|
variable when deciding which value the ``ROOT_URLCONF`` setting should have.
|
||||||
When you run the web server simply specify that environment variable
|
When you run the web server simply specify that environment variable
|
||||||
(e.g. in your init script)::
|
(e.g. in your init script):
|
||||||
|
|
||||||
DJANGO_ROOT_URLCONF=mysite.debugging_urls gunicorn mysite.wsgi:application
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ DJANGO_ROOT_URLCONF=mysite.debugging_urls gunicorn mysite.wsgi:application
|
||||||
|
|
||||||
If the environment variable can't be found it'll use the default
|
If the environment variable can't be found it'll use the default
|
||||||
``'mysite.urls'``.
|
``'mysite.urls'``.
|
||||||
|
|
@ -125,7 +127,9 @@ Allow final value to be used outside the configuration context
|
||||||
|
|
||||||
You may use the ``environ_name`` parameter to allow a :class:`~Value` to be
|
You may use the ``environ_name`` parameter to allow a :class:`~Value` to be
|
||||||
directly converted to its final value for use outside of the configuration
|
directly converted to its final value for use outside of the configuration
|
||||||
context::
|
context:
|
||||||
|
|
||||||
|
.. code-block:: pycon
|
||||||
|
|
||||||
>>> type(values.Value([]))
|
>>> type(values.Value([]))
|
||||||
<class 'configurations.values.Value'>
|
<class 'configurations.values.Value'>
|
||||||
|
|
@ -283,17 +287,21 @@ Type values
|
||||||
MONTY_PYTHONS = ListValue(['John Cleese', 'Eric Idle'],
|
MONTY_PYTHONS = ListValue(['John Cleese', 'Eric Idle'],
|
||||||
converter=check_monty_python)
|
converter=check_monty_python)
|
||||||
|
|
||||||
You can override this list with an environment variable like this::
|
You can override this list with an environment variable like this:
|
||||||
|
|
||||||
DJANGO_MONTY_PYTHONS="Terry Jones,Graham Chapman" gunicorn mysite.wsgi:application
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ DJANGO_MONTY_PYTHONS="Terry Jones,Graham Chapman" gunicorn mysite.wsgi:application
|
||||||
|
|
||||||
Use a custom separator::
|
Use a custom separator::
|
||||||
|
|
||||||
EMERGENCY_EMAILS = ListValue(['admin@mysite.net'], separator=';')
|
EMERGENCY_EMAILS = ListValue(['admin@mysite.net'], separator=';')
|
||||||
|
|
||||||
And override it::
|
And override it:
|
||||||
|
|
||||||
DJANGO_EMERGENCY_EMAILS="admin@mysite.net;manager@mysite.org;support@mysite.com" gunicorn mysite.wsgi:application
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ DJANGO_EMERGENCY_EMAILS="admin@mysite.net;manager@mysite.org;support@mysite.com" gunicorn mysite.wsgi:application
|
||||||
|
|
||||||
.. class:: TupleValue
|
.. class:: TupleValue
|
||||||
|
|
||||||
|
|
|
||||||
12
setup.py
12
setup.py
|
|
@ -31,9 +31,8 @@ setup(
|
||||||
},
|
},
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'django>=3.2',
|
'django>=3.2',
|
||||||
'importlib-metadata;python_version<"3.8"',
|
|
||||||
],
|
],
|
||||||
python_requires='>=3.7, <4.0',
|
python_requires='>=3.9, <4.0',
|
||||||
extras_require={
|
extras_require={
|
||||||
'cache': ['django-cache-url'],
|
'cache': ['django-cache-url'],
|
||||||
'database': ['dj-database-url'],
|
'database': ['dj-database-url'],
|
||||||
|
|
@ -50,18 +49,21 @@ setup(
|
||||||
'Development Status :: 5 - Production/Stable',
|
'Development Status :: 5 - Production/Stable',
|
||||||
'Framework :: Django',
|
'Framework :: Django',
|
||||||
'Framework :: Django :: 3.2',
|
'Framework :: Django :: 3.2',
|
||||||
'Framework :: Django :: 4.0',
|
|
||||||
'Framework :: Django :: 4.1',
|
'Framework :: Django :: 4.1',
|
||||||
|
'Framework :: Django :: 4.2',
|
||||||
|
'Framework :: Django :: 5.0',
|
||||||
|
'Framework :: Django :: 5.1',
|
||||||
'Intended Audience :: Developers',
|
'Intended Audience :: Developers',
|
||||||
'License :: OSI Approved :: BSD License',
|
'License :: OSI Approved :: BSD License',
|
||||||
'Operating System :: OS Independent',
|
'Operating System :: OS Independent',
|
||||||
'Programming Language :: Python',
|
'Programming Language :: Python',
|
||||||
'Programming Language :: Python :: 3',
|
'Programming Language :: Python :: 3',
|
||||||
'Programming Language :: Python :: 3 :: Only',
|
'Programming Language :: Python :: 3 :: Only',
|
||||||
'Programming Language :: Python :: 3.7',
|
|
||||||
'Programming Language :: Python :: 3.8',
|
|
||||||
'Programming Language :: Python :: 3.9',
|
'Programming Language :: Python :: 3.9',
|
||||||
'Programming Language :: Python :: 3.10',
|
'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',
|
'Programming Language :: Python :: Implementation :: PyPy',
|
||||||
'Topic :: Utilities',
|
'Topic :: Utilities',
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,6 @@ class DotEnvConfiguration(Configuration):
|
||||||
DOTENV = 'test_project/.env'
|
DOTENV = 'test_project/.env'
|
||||||
|
|
||||||
DOTENV_VALUE = values.Value()
|
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")
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
import django
|
|
||||||
|
|
||||||
from configurations import Configuration, pristinemethod
|
from configurations import Configuration, pristinemethod
|
||||||
from configurations.values import BooleanValue
|
|
||||||
|
|
||||||
|
|
||||||
class Test(Configuration):
|
class Test(Configuration):
|
||||||
|
|
@ -11,8 +9,6 @@ class Test(Configuration):
|
||||||
os.path.join(os.path.dirname(
|
os.path.join(os.path.dirname(
|
||||||
os.path.abspath(__file__)), os.pardir))
|
os.path.abspath(__file__)), os.pardir))
|
||||||
|
|
||||||
ENV_LOADED = BooleanValue(False)
|
|
||||||
|
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
SITE_ID = 1
|
SITE_ID = 1
|
||||||
|
|
@ -36,9 +32,6 @@ class Test(Configuration):
|
||||||
|
|
||||||
ROOT_URLCONF = 'tests.urls'
|
ROOT_URLCONF = 'tests.urls'
|
||||||
|
|
||||||
if django.VERSION[:2] < (1, 6):
|
|
||||||
TEST_RUNNER = 'discover_runner.DiscoverRunner'
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ALLOWED_HOSTS(self):
|
def ALLOWED_HOSTS(self):
|
||||||
allowed_hosts = super().ALLOWED_HOSTS[:]
|
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||||
|
|
|
||||||
|
|
@ -11,4 +11,5 @@ class DotEnvLoadingTests(TestCase):
|
||||||
def test_env_loaded(self):
|
def test_env_loaded(self):
|
||||||
from tests.settings import dot_env
|
from tests.settings import dot_env
|
||||||
self.assertEqual(dot_env.DOTENV_VALUE, 'is set')
|
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)
|
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 unittest.mock import patch
|
||||||
|
|
||||||
from configurations.importer import ConfigurationImporter
|
from configurations.importer import ConfigurationFinder
|
||||||
|
|
||||||
ROOT_DIR = os.path.dirname(os.path.dirname(__file__))
|
ROOT_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||||
TEST_PROJECT_DIR = os.path.join(ROOT_DIR, 'test_project')
|
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')
|
@patch.dict(os.environ, clear=True, DJANGO_CONFIGURATION='Test')
|
||||||
def test_empty_module_var(self):
|
def test_empty_module_var(self):
|
||||||
self.assertRaises(ImproperlyConfigured, ConfigurationImporter)
|
with self.assertRaises(ImproperlyConfigured):
|
||||||
|
ConfigurationFinder()
|
||||||
|
|
||||||
@patch.dict(os.environ, clear=True,
|
@patch.dict(os.environ, clear=True,
|
||||||
DJANGO_SETTINGS_MODULE='tests.settings.main')
|
DJANGO_SETTINGS_MODULE='tests.settings.main')
|
||||||
def test_empty_class_var(self):
|
def test_empty_class_var(self):
|
||||||
self.assertRaises(ImproperlyConfigured, ConfigurationImporter)
|
with self.assertRaises(ImproperlyConfigured):
|
||||||
|
ConfigurationFinder()
|
||||||
|
|
||||||
def test_global_settings(self):
|
def test_global_settings(self):
|
||||||
from configurations.base import Configuration
|
from configurations.base import Configuration
|
||||||
|
|
@ -70,21 +72,21 @@ class MainTests(TestCase):
|
||||||
DJANGO_SETTINGS_MODULE='tests.settings.main',
|
DJANGO_SETTINGS_MODULE='tests.settings.main',
|
||||||
DJANGO_CONFIGURATION='Test')
|
DJANGO_CONFIGURATION='Test')
|
||||||
def test_initialization(self):
|
def test_initialization(self):
|
||||||
importer = ConfigurationImporter()
|
finder = ConfigurationFinder()
|
||||||
self.assertEqual(importer.module, 'tests.settings.main')
|
self.assertEqual(finder.module, 'tests.settings.main')
|
||||||
self.assertEqual(importer.name, 'Test')
|
self.assertEqual(finder.name, 'Test')
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
repr(importer),
|
repr(finder),
|
||||||
"<ConfigurationImporter for 'tests.settings.main.Test'>")
|
"<ConfigurationFinder for 'tests.settings.main.Test'>")
|
||||||
|
|
||||||
@patch.dict(os.environ, clear=True,
|
@patch.dict(os.environ, clear=True,
|
||||||
DJANGO_SETTINGS_MODULE='tests.settings.inheritance',
|
DJANGO_SETTINGS_MODULE='tests.settings.inheritance',
|
||||||
DJANGO_CONFIGURATION='Inheritance')
|
DJANGO_CONFIGURATION='Inheritance')
|
||||||
def test_initialization_inheritance(self):
|
def test_initialization_inheritance(self):
|
||||||
importer = ConfigurationImporter()
|
finder = ConfigurationFinder()
|
||||||
self.assertEqual(importer.module,
|
self.assertEqual(finder.module,
|
||||||
'tests.settings.inheritance')
|
'tests.settings.inheritance')
|
||||||
self.assertEqual(importer.name, 'Inheritance')
|
self.assertEqual(finder.name, 'Inheritance')
|
||||||
|
|
||||||
@patch.dict(os.environ, clear=True,
|
@patch.dict(os.environ, clear=True,
|
||||||
DJANGO_SETTINGS_MODULE='tests.settings.main',
|
DJANGO_SETTINGS_MODULE='tests.settings.main',
|
||||||
|
|
@ -93,12 +95,12 @@ class MainTests(TestCase):
|
||||||
'--settings=tests.settings.main',
|
'--settings=tests.settings.main',
|
||||||
'--configuration=Test'])
|
'--configuration=Test'])
|
||||||
def test_configuration_option(self):
|
def test_configuration_option(self):
|
||||||
importer = ConfigurationImporter(check_options=False)
|
finder = ConfigurationFinder(check_options=False)
|
||||||
self.assertEqual(importer.module, 'tests.settings.main')
|
self.assertEqual(finder.module, 'tests.settings.main')
|
||||||
self.assertEqual(importer.name, 'NonExisting')
|
self.assertEqual(finder.name, 'NonExisting')
|
||||||
importer = ConfigurationImporter(check_options=True)
|
finder = ConfigurationFinder(check_options=True)
|
||||||
self.assertEqual(importer.module, 'tests.settings.main')
|
self.assertEqual(finder.module, 'tests.settings.main')
|
||||||
self.assertEqual(importer.name, 'Test')
|
self.assertEqual(finder.name, 'Test')
|
||||||
|
|
||||||
def test_configuration_argument_in_cli(self):
|
def test_configuration_argument_in_cli(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ class ValueTests(TestCase):
|
||||||
|
|
||||||
def test_value_with_default(self):
|
def test_value_with_default(self):
|
||||||
value = Value('default', environ=False)
|
value = Value('default', environ=False)
|
||||||
self.assertEqual(type(value), type('default'))
|
self.assertEqual(type(value), str)
|
||||||
self.assertEqual(value, 'default')
|
self.assertEqual(value, 'default')
|
||||||
self.assertEqual(str(value), 'default')
|
self.assertEqual(str(value), 'default')
|
||||||
|
|
||||||
|
|
@ -44,17 +44,17 @@ class ValueTests(TestCase):
|
||||||
with env(DJANGO_TEST='override'):
|
with env(DJANGO_TEST='override'):
|
||||||
self.assertEqual(value.setup('TEST'), 'default')
|
self.assertEqual(value.setup('TEST'), 'default')
|
||||||
value = Value(environ_name='TEST')
|
value = Value(environ_name='TEST')
|
||||||
self.assertEqual(type(value), type('override'))
|
self.assertEqual(type(value), str)
|
||||||
self.assertEqual(value, 'override')
|
self.assertEqual(value, 'override')
|
||||||
self.assertEqual(str(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('%s' % value, 'override')
|
||||||
|
|
||||||
value = Value(environ_name='TEST', late_binding=True)
|
value = Value(environ_name='TEST', late_binding=True)
|
||||||
self.assertEqual(type(value), Value)
|
self.assertEqual(type(value), Value)
|
||||||
self.assertEqual(value.value, 'override')
|
self.assertEqual(value.value, 'override')
|
||||||
self.assertEqual(str(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('%s' % value, 'override')
|
||||||
|
|
||||||
self.assertEqual(repr(value), repr('override'))
|
self.assertEqual(repr(value), repr('override'))
|
||||||
|
|
@ -373,16 +373,23 @@ class ValueTests(TestCase):
|
||||||
value = DatabaseURLValue()
|
value = DatabaseURLValue()
|
||||||
self.assertEqual(value.default, {})
|
self.assertEqual(value.default, {})
|
||||||
with env(DATABASE_URL='sqlite://'):
|
with env(DATABASE_URL='sqlite://'):
|
||||||
self.assertEqual(value.setup('DATABASE_URL'), {
|
settings_value = value.setup('DATABASE_URL')
|
||||||
'default': {
|
# 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,
|
'CONN_MAX_AGE': 0,
|
||||||
|
'DISABLE_SERVER_SIDE_CURSORS': False,
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
'HOST': '',
|
'HOST': '',
|
||||||
'NAME': ':memory:',
|
'NAME': ':memory:',
|
||||||
'PASSWORD': '',
|
'PASSWORD': '',
|
||||||
'PORT': '',
|
'PORT': '',
|
||||||
'USER': '',
|
'USER': '',
|
||||||
}})
|
},
|
||||||
|
settings_value['default']
|
||||||
|
)
|
||||||
|
|
||||||
def test_database_url_additional_args(self):
|
def test_database_url_additional_args(self):
|
||||||
|
|
||||||
|
|
|
||||||
30
tox.ini
30
tox.ini
|
|
@ -3,20 +3,22 @@ skipsdist = true
|
||||||
usedevelop = true
|
usedevelop = true
|
||||||
minversion = 1.8
|
minversion = 1.8
|
||||||
envlist =
|
envlist =
|
||||||
py37-checkqa
|
py311-checkqa
|
||||||
docs
|
docs
|
||||||
py{37,py37}-dj{32}
|
py{39}-dj{32,41,42}
|
||||||
py{38,py38,39,py39,310}-dj{32,40,41,main}
|
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]
|
[gh-actions]
|
||||||
python =
|
python =
|
||||||
3.7: py37,flake8,readme
|
|
||||||
3.8: py38
|
|
||||||
3.9: py39
|
3.9: py39
|
||||||
3.10: py310
|
3.10: py310
|
||||||
pypy-3.7: pypy37
|
3.11: py311,flake8,readme
|
||||||
pypy-3.8: pypy38
|
3.12: py312
|
||||||
pypy-3.9: pypy39
|
3.13: py313
|
||||||
|
pypy-3.10: pypy310
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
usedevelop = true
|
usedevelop = true
|
||||||
|
|
@ -26,9 +28,15 @@ setenv =
|
||||||
COVERAGE_PROCESS_START = {toxinidir}/setup.cfg
|
COVERAGE_PROCESS_START = {toxinidir}/setup.cfg
|
||||||
deps =
|
deps =
|
||||||
dj32: django~=3.2.9
|
dj32: django~=3.2.9
|
||||||
dj40: django~=4.0.0
|
dj41: django~=4.1.3
|
||||||
dj41: django~=4.1.0
|
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
|
djmain: https://github.com/django/django/archive/main.tar.gz
|
||||||
|
py312: setuptools
|
||||||
|
py312: wheel
|
||||||
|
py313: setuptools
|
||||||
|
py313: wheel
|
||||||
coverage
|
coverage
|
||||||
coverage_enable_subprocess
|
coverage_enable_subprocess
|
||||||
extras = testing
|
extras = testing
|
||||||
|
|
@ -39,7 +47,7 @@ commands =
|
||||||
coverage report -m --skip-covered
|
coverage report -m --skip-covered
|
||||||
coverage xml
|
coverage xml
|
||||||
|
|
||||||
[testenv:py37-checkqa]
|
[testenv:py311-checkqa]
|
||||||
commands =
|
commands =
|
||||||
flake8 {toxinidir}
|
flake8 {toxinidir}
|
||||||
check-manifest -v
|
check-manifest -v
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue