mirror of
https://github.com/jazzband/django-configurations.git
synced 2026-03-16 22:20:27 +00:00
Merge remote-tracking branch 'origin/master' into vseva/fix_250
This commit is contained in:
commit
02e8f55ac8
37 changed files with 372 additions and 273 deletions
54
.github/workflows/release.yml
vendored
Normal file
54
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# something silly
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository == 'jazzband/django-configurations'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
|
||||
- name: Get pip cache dir
|
||||
id: pip-cache
|
||||
run: |
|
||||
echo "::set-output name=dir::$(pip cache dir)"
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ steps.pip-cache.outputs.dir }}
|
||||
key: release-${{ hashFiles('**/setup.py') }}
|
||||
restore-keys: |
|
||||
release-
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install -U pip
|
||||
python -m pip install -U setuptools twine wheel
|
||||
|
||||
- name: Build package
|
||||
run: |
|
||||
python setup.py --version
|
||||
python setup.py sdist --format=gztar bdist_wheel
|
||||
twine check dist/*
|
||||
|
||||
- name: Upload packages to Jazzband
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
|
||||
uses: pypa/gh-action-pypi-publish@master
|
||||
with:
|
||||
user: jazzband
|
||||
password: ${{ secrets.JAZZBAND_RELEASE_KEY }}
|
||||
repository_url: https://jazzband.co/projects/django-configurations/upload
|
||||
53
.github/workflows/test.yml
vendored
Normal file
53
.github/workflows/test.yml
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
name: Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 5
|
||||
matrix:
|
||||
python-version: ['3.6', '3.7', '3.8', '3.9', 'pypy3']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Get pip cache dir
|
||||
id: pip-cache
|
||||
run: |
|
||||
echo "::set-output name=dir::$(pip cache dir)"
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ steps.pip-cache.outputs.dir }}
|
||||
key:
|
||||
${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.py') }}
|
||||
restore-keys: |
|
||||
${{ matrix.python-version }}-v1-
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install --upgrade tox tox-gh-actions
|
||||
|
||||
- name: Tox tests
|
||||
run: |
|
||||
tox -v
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
name: Python ${{ matrix.python-version }}
|
||||
fail_ci_if_error: true
|
||||
1
.pre-commit-config.yaml
Normal file
1
.pre-commit-config.yaml
Normal file
|
|
@ -0,0 +1 @@
|
|||
repos: []
|
||||
37
.travis.yml
37
.travis.yml
|
|
@ -1,37 +0,0 @@
|
|||
language: python
|
||||
dist: xenial
|
||||
cache: pip
|
||||
python:
|
||||
- '2.7'
|
||||
- '3.5'
|
||||
- '3.6'
|
||||
- '3.7'
|
||||
- '3.8'
|
||||
- 'pypy3'
|
||||
install: travis_retry pip install tox-travis codecov
|
||||
script: tox -v
|
||||
after_success: codecov --required -X gcov fix pycov -f coverage.xml --flags ${TOXENV//-/ }
|
||||
branches:
|
||||
except: templates/1.5.x templates/1.6.x
|
||||
stages:
|
||||
- test
|
||||
- name: deploy
|
||||
if: repo = jazzband/django-configurations AND tag IS present
|
||||
jobs:
|
||||
include:
|
||||
- stage: test
|
||||
- stage: deploy
|
||||
install: skip
|
||||
script: skip
|
||||
python: 3.7
|
||||
env: skip
|
||||
deploy:
|
||||
provider: pypi
|
||||
user: jazzband
|
||||
server: https://jazzband.co/projects/django-configurations/upload
|
||||
distributions: sdist bdist_wheel
|
||||
password:
|
||||
secure: LuserSjUTGSsls9zrvck/FbfL+gFpNU/ywOQ/67ufEbbpGCeDBEgxDzgb0acfHNk8wlAkaPvaAejQBFtcUulhdNT/g0NsmaEAjd6HhCGM+FRJAnYFaj33Js6C+N2tX5wznL7uCBxqgtaaH0hf6ucqC8OXqwoCVGgdxAEnUlC/fY=
|
||||
on:
|
||||
tags: true
|
||||
repo: jazzband/django-configurations
|
||||
46
CODE_OF_CONDUCT.md
Normal file
46
CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Code of Conduct
|
||||
|
||||
As contributors and maintainers of the Jazzband projects, and in the interest of
|
||||
fostering an open and welcoming community, we pledge to respect all people who
|
||||
contribute through reporting issues, posting feature requests, updating documentation,
|
||||
submitting pull requests or patches, and other activities.
|
||||
|
||||
We are committed to making participation in the Jazzband a harassment-free experience
|
||||
for everyone, regardless of the level of experience, gender, gender identity and
|
||||
expression, sexual orientation, disability, personal appearance, body size, race,
|
||||
ethnicity, age, religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
- The use of sexualized language or imagery
|
||||
- Personal attacks
|
||||
- Trolling or insulting/derogatory comments
|
||||
- Public or private harassment
|
||||
- Publishing other's private information, such as physical or electronic addresses,
|
||||
without explicit permission
|
||||
- Other unethical or unprofessional conduct
|
||||
|
||||
The Jazzband roadies have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are not
|
||||
aligned to this Code of Conduct, or to ban temporarily or permanently any contributor
|
||||
for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
By adopting this Code of Conduct, the roadies commit themselves to fairly and
|
||||
consistently applying these principles to every aspect of managing the jazzband
|
||||
projects. Roadies who do not follow or enforce the Code of Conduct may be permanently
|
||||
removed from the Jazzband roadies.
|
||||
|
||||
This code of conduct applies both within project spaces and in public spaces when an
|
||||
individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
|
||||
contacting the roadies at `roadies@jazzband.co`. All complaints will be reviewed and
|
||||
investigated and will result in a response that is deemed necessary and appropriate to
|
||||
the circumstances. Roadies are obligated to maintain confidentiality with regard to the
|
||||
reporter of an incident.
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version
|
||||
1.3.0, available at [https://contributor-covenant.org/version/1/3/0/][version]
|
||||
|
||||
[homepage]: https://contributor-covenant.org
|
||||
[version]: https://contributor-covenant.org/version/1/3/0/
|
||||
11
MANIFEST.in
11
MANIFEST.in
|
|
@ -1,9 +1,10 @@
|
|||
include README.rst
|
||||
include CONTRIBUTING.md
|
||||
include .pre-commit-config.yaml
|
||||
include AUTHORS
|
||||
include .travis.yml
|
||||
include CODE_OF_CONDUCT.md
|
||||
include CONTRIBUTING.md
|
||||
include LICENSE
|
||||
include README.rst
|
||||
include tox.ini
|
||||
recursive-include tests *
|
||||
recursive-include docs *
|
||||
recursive-include test_project *
|
||||
include LICENSE
|
||||
recursive-include tests *
|
||||
|
|
|
|||
23
README.rst
23
README.rst
|
|
@ -13,9 +13,9 @@ Check out the `documentation`_ for more complete examples.
|
|||
.. |latest-version| image:: https://img.shields.io/pypi/v/django-configurations.svg
|
||||
:alt: Latest version on PyPI
|
||||
:target: https://pypi.python.org/pypi/django-configurations
|
||||
.. |build-status| image:: https://img.shields.io/travis/jazzband/django-configurations/master.svg
|
||||
:alt: Build status
|
||||
:target: https://travis-ci.org/jazzband/django-configurations
|
||||
.. |build-status| image:: https://github.com/jazzband/django-configurations/workflows/Test/badge.svg
|
||||
:target: https://github.com/jazzband/django-configurations/actions
|
||||
:alt: GitHub Actions
|
||||
.. |codecov| image:: https://codecov.io/github/jazzband/django-configurations/coverage.svg?branch=master
|
||||
:alt: Codecov
|
||||
:target: https://codecov.io/github/jazzband/django-configurations?branch=master
|
||||
|
|
@ -81,7 +81,7 @@ command line option, e.g.
|
|||
python manage.py runserver --settings=mysite.settings --configuration=Dev
|
||||
|
||||
To enable Django to use your configuration you now have to modify your
|
||||
**manage.py** or **wsgi.py** script to use django-configurations's versions
|
||||
**manage.py**, **wsgi.py** or **asgi.py** script to use django-configurations's versions
|
||||
of the appropriate starter functions, e.g. a typical **manage.py** using
|
||||
django-configurations would look like this:
|
||||
|
||||
|
|
@ -120,5 +120,18 @@ The same applies to your **wsgi.py** file, e.g.:
|
|||
Here we don't use the default ``django.core.wsgi.get_wsgi_application``
|
||||
function but instead ``configurations.wsgi.get_wsgi_application``.
|
||||
|
||||
Or if you are not serving your app via WSGI but ASGI instead, you need to modify your **asgi.py** file too.:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import os
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
|
||||
os.environ.setdefault('DJANGO_CONFIGURATION', 'DEV')
|
||||
|
||||
from configurations.asgi import get_asgi_application
|
||||
|
||||
application = get_asgi_application()
|
||||
|
||||
That's it! You can now use your project with ``manage.py`` and your favorite
|
||||
WSGI enabled server.
|
||||
WSGI/ASGI enabled server.
|
||||
|
|
|
|||
8
configurations/asgi.py
Normal file
8
configurations/asgi.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
from . import importer
|
||||
|
||||
importer.install()
|
||||
|
||||
from django.core.asgi import get_asgi_application # noqa: E402
|
||||
|
||||
# this is just for the crazy ones
|
||||
application = get_asgi_application()
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import os
|
||||
import re
|
||||
import six
|
||||
|
||||
from django.conf import global_settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
|
@ -33,18 +32,39 @@ class ConfigurationBase(type):
|
|||
for base in bases[::-1]:
|
||||
settings_vars.update(uppercase_attributes(base))
|
||||
attrs = dict(settings_vars, **attrs)
|
||||
# Fix ImproperlyConfigured issue introduced in Django
|
||||
|
||||
deprecated_settings = {
|
||||
# DEFAULT_HASHING_ALGORITHM is always deprecated, as it's a
|
||||
# transitional setting
|
||||
# https://docs.djangoproject.com/en/3.1/releases/3.1/#default-hashing-algorithm-settings
|
||||
"DEFAULT_HASHING_ALGORITHM",
|
||||
# DEFAULT_CONTENT_TYPE and FILE_CHARSET are deprecated in
|
||||
# Django 2.2 and are removed in Django 3.0
|
||||
"DEFAULT_CONTENT_TYPE",
|
||||
"FILE_CHARSET",
|
||||
# When DEFAULT_AUTO_FIELD is not explicitly set, Django's emits a
|
||||
# system check warning models.W042. This warning should not be
|
||||
# suppressed, as downstream users are expected to make a decision.
|
||||
# https://docs.djangoproject.com/en/3.2/releases/3.2/#customizing-type-of-auto-created-primary-keys
|
||||
"DEFAULT_AUTO_FIELD",
|
||||
}
|
||||
# PASSWORD_RESET_TIMEOUT_DAYS is deprecated in favor of
|
||||
# PASSWORD_RESET_TIMEOUT in Django 3.1
|
||||
# https://github.com/django/django/commit/226ebb17290b604ef29e82fb5c1fbac3594ac163#diff-ec2bed07bb264cb95a80f08d71a47c06R163-R170
|
||||
if "PASSWORD_RESET_TIMEOUT_DAYS" in attrs and "PASSWORD_RESET_TIMEOUT" in attrs:
|
||||
attrs.pop("PASSWORD_RESET_TIMEOUT_DAYS")
|
||||
return super(ConfigurationBase, cls).__new__(cls, name, bases, attrs)
|
||||
if "PASSWORD_RESET_TIMEOUT" in attrs:
|
||||
deprecated_settings.add("PASSWORD_RESET_TIMEOUT_DAYS")
|
||||
for deprecated_setting in deprecated_settings:
|
||||
if deprecated_setting in attrs:
|
||||
del attrs[deprecated_setting]
|
||||
|
||||
return super().__new__(cls, name, bases, attrs)
|
||||
|
||||
def __repr__(self):
|
||||
return "<Configuration '{0}.{1}'>".format(self.__module__,
|
||||
self.__name__)
|
||||
|
||||
|
||||
class Configuration(six.with_metaclass(ConfigurationBase)):
|
||||
class Configuration(metaclass=ConfigurationBase):
|
||||
"""
|
||||
The base configuration class to inherit from.
|
||||
|
||||
|
|
@ -91,10 +111,10 @@ class Configuration(six.with_metaclass(ConfigurationBase)):
|
|||
try:
|
||||
with open(dotenv, 'r') as f:
|
||||
content = f.read()
|
||||
except IOError as e:
|
||||
except OSError as e:
|
||||
raise ImproperlyConfigured("Couldn't read .env file "
|
||||
"with the path {}. Error: "
|
||||
"{}".format(dotenv, e))
|
||||
"{}".format(dotenv, e)) from e
|
||||
else:
|
||||
for line in content.splitlines():
|
||||
m1 = re.match(r'\A([A-Za-z_0-9]+)=(.*)\Z', line)
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ def install(check_options=False):
|
|||
installed = True
|
||||
|
||||
|
||||
class ConfigurationImporter(object):
|
||||
class ConfigurationImporter:
|
||||
modvar = SETTINGS_ENVIRONMENT_VARIABLE
|
||||
namevar = CONFIGURATION_ENVIRONMENT_VARIABLE
|
||||
error_msg = ("Configuration cannot be imported, "
|
||||
|
|
@ -82,16 +82,10 @@ class ConfigurationImporter(object):
|
|||
return os.environ.get(self.namevar)
|
||||
|
||||
def check_options(self):
|
||||
try:
|
||||
parser = base.CommandParser(
|
||||
usage="%(prog)s subcommand [options] [args]",
|
||||
add_help=False)
|
||||
except TypeError:
|
||||
# Django before 2.1 used a `cmd` argument.
|
||||
parser = base.CommandParser(
|
||||
None,
|
||||
usage="%(prog)s subcommand [options] [args]",
|
||||
add_help=False)
|
||||
parser = base.CommandParser(
|
||||
usage="%(prog)s subcommand [options] [args]",
|
||||
add_help=False,
|
||||
)
|
||||
parser.add_argument('--settings')
|
||||
parser.add_argument('--pythonpath')
|
||||
parser.add_argument(CONFIGURATION_ARGUMENT,
|
||||
|
|
@ -140,7 +134,7 @@ class ConfigurationImporter(object):
|
|||
return None
|
||||
|
||||
|
||||
class ConfigurationLoader(object):
|
||||
class ConfigurationLoader:
|
||||
|
||||
def __init__(self, name, location):
|
||||
self.name = name
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import inspect
|
||||
import six
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from functools import partial
|
||||
from importlib import import_module
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
|
@ -12,8 +13,7 @@ def isuppercase(name):
|
|||
|
||||
|
||||
def uppercase_attributes(obj):
|
||||
return dict((name, getattr(obj, name))
|
||||
for name in filter(isuppercase, dir(obj)))
|
||||
return {name: getattr(obj, name) for name in dir(obj) if isuppercase(name)}
|
||||
|
||||
|
||||
def import_by_path(dotted_path, error_prefix=''):
|
||||
|
|
@ -24,6 +24,8 @@ def import_by_path(dotted_path, error_prefix=''):
|
|||
|
||||
Backported from Django 1.6.
|
||||
"""
|
||||
warnings.warn("Function utils.import_by_path is deprecated in favor of "
|
||||
"django.utils.module_loading.import_string.", DeprecationWarning)
|
||||
try:
|
||||
module_path, class_name = dotted_path.rsplit('.', 1)
|
||||
except ValueError:
|
||||
|
|
@ -36,8 +38,7 @@ def import_by_path(dotted_path, error_prefix=''):
|
|||
msg = '{0}Error importing module {1}: "{2}"'.format(error_prefix,
|
||||
module_path,
|
||||
err)
|
||||
six.reraise(ImproperlyConfigured, ImproperlyConfigured(msg),
|
||||
sys.exc_info()[2])
|
||||
raise ImproperlyConfigured(msg).with_traceback(sys.exc_info()[2])
|
||||
try:
|
||||
attr = getattr(module, class_name)
|
||||
except AttributeError:
|
||||
|
|
@ -61,77 +62,40 @@ def reraise(exc, prefix=None, suffix=None):
|
|||
elif not (suffix.startswith('(') and suffix.endswith(')')):
|
||||
suffix = '(' + suffix + ')'
|
||||
exc.args = ('{0} {1} {2}'.format(prefix, args[0], suffix),) + args[1:]
|
||||
raise
|
||||
raise exc
|
||||
|
||||
|
||||
# Copied over from Sphinx
|
||||
if sys.version_info >= (3, 0):
|
||||
from functools import partial
|
||||
|
||||
def getargspec(func):
|
||||
"""Like inspect.getargspec but supports functools.partial as well."""
|
||||
if inspect.ismethod(func):
|
||||
func = func.__func__
|
||||
if type(func) is partial:
|
||||
orig_func = func.func
|
||||
argspec = getargspec(orig_func)
|
||||
args = list(argspec[0])
|
||||
defaults = list(argspec[3] or ())
|
||||
kwoargs = list(argspec[4])
|
||||
kwodefs = dict(argspec[5] or {})
|
||||
if func.args:
|
||||
args = args[len(func.args):]
|
||||
for arg in func.keywords or ():
|
||||
try:
|
||||
i = args.index(arg) - len(args)
|
||||
del args[i]
|
||||
try:
|
||||
del defaults[i]
|
||||
except IndexError:
|
||||
pass
|
||||
except ValueError: # must be a kwonly arg
|
||||
i = kwoargs.index(arg)
|
||||
del kwoargs[i]
|
||||
del kwodefs[arg]
|
||||
return inspect.FullArgSpec(args, argspec[1], argspec[2],
|
||||
tuple(defaults), kwoargs,
|
||||
kwodefs, argspec[6])
|
||||
while hasattr(func, '__wrapped__'):
|
||||
func = func.__wrapped__
|
||||
if not inspect.isfunction(func):
|
||||
raise TypeError('%r is not a Python function' % func)
|
||||
return inspect.getfullargspec(func)
|
||||
|
||||
else: # 2.6, 2.7
|
||||
from functools import partial
|
||||
|
||||
def getargspec(func):
|
||||
"""Like inspect.getargspec but supports functools.partial as well."""
|
||||
if inspect.ismethod(func):
|
||||
func = func.im_func
|
||||
parts = 0, ()
|
||||
if type(func) is partial:
|
||||
keywords = func.keywords
|
||||
if keywords is None:
|
||||
keywords = {}
|
||||
parts = len(func.args), keywords.keys()
|
||||
func = func.func
|
||||
if not inspect.isfunction(func):
|
||||
raise TypeError('%r is not a Python function' % func)
|
||||
args, varargs, varkw = inspect.getargs(func.func_code)
|
||||
func_defaults = func.func_defaults
|
||||
if func_defaults is None:
|
||||
func_defaults = []
|
||||
else:
|
||||
func_defaults = list(func_defaults)
|
||||
if parts[0]:
|
||||
args = args[parts[0]:]
|
||||
if parts[1]:
|
||||
for arg in parts[1]:
|
||||
def getargspec(func):
|
||||
"""Like inspect.getargspec but supports functools.partial as well."""
|
||||
if inspect.ismethod(func):
|
||||
func = func.__func__
|
||||
if type(func) is partial:
|
||||
orig_func = func.func
|
||||
argspec = getargspec(orig_func)
|
||||
args = list(argspec[0])
|
||||
defaults = list(argspec[3] or ())
|
||||
kwoargs = list(argspec[4])
|
||||
kwodefs = dict(argspec[5] or {})
|
||||
if func.args:
|
||||
args = args[len(func.args):]
|
||||
for arg in func.keywords or ():
|
||||
try:
|
||||
i = args.index(arg) - len(args)
|
||||
del args[i]
|
||||
try:
|
||||
del func_defaults[i]
|
||||
del defaults[i]
|
||||
except IndexError:
|
||||
pass
|
||||
return inspect.ArgSpec(args, varargs, varkw, func_defaults)
|
||||
except ValueError: # must be a kwonly arg
|
||||
i = kwoargs.index(arg)
|
||||
del kwoargs[i]
|
||||
del kwodefs[arg]
|
||||
return inspect.FullArgSpec(args, argspec[1], argspec[2],
|
||||
tuple(defaults), kwoargs,
|
||||
kwodefs, argspec[6])
|
||||
while hasattr(func, '__wrapped__'):
|
||||
func = func.__wrapped__
|
||||
if not inspect.isfunction(func):
|
||||
raise TypeError('%r is not a Python function' % func)
|
||||
return inspect.getfullargspec(func)
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ import ast
|
|||
import copy
|
||||
import decimal
|
||||
import os
|
||||
import six
|
||||
import sys
|
||||
|
||||
from django.core import validators
|
||||
from django.core.exceptions import ValidationError, ImproperlyConfigured
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
from .utils import import_by_path, getargspec
|
||||
from .utils import getargspec
|
||||
|
||||
|
||||
def setup_value(target, name, value):
|
||||
|
|
@ -20,7 +20,7 @@ def setup_value(target, name, value):
|
|||
setattr(target, multiple_name, multiple_value)
|
||||
|
||||
|
||||
class Value(object):
|
||||
class Value:
|
||||
"""
|
||||
A single settings value that is able to interpret env variables
|
||||
and implements a simple validation scheme.
|
||||
|
|
@ -117,7 +117,7 @@ class Value(object):
|
|||
return value
|
||||
|
||||
|
||||
class MultipleMixin(object):
|
||||
class MultipleMixin:
|
||||
multiple = True
|
||||
|
||||
|
||||
|
|
@ -126,7 +126,7 @@ class BooleanValue(Value):
|
|||
false_values = ('no', 'n', 'false', '0', '')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(BooleanValue, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.default not in (True, False):
|
||||
raise ValueError('Default value {0!r} is not a '
|
||||
'boolean value'.format(self.default))
|
||||
|
|
@ -142,14 +142,18 @@ class BooleanValue(Value):
|
|||
'boolean value {0!r}'.format(value))
|
||||
|
||||
|
||||
class CastingMixin(object):
|
||||
class CastingMixin:
|
||||
exception = (TypeError, ValueError)
|
||||
message = 'Cannot interpret value {0!r}'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CastingMixin, self).__init__(*args, **kwargs)
|
||||
if isinstance(self.caster, six.string_types):
|
||||
self._caster = import_by_path(self.caster)
|
||||
super().__init__(*args, **kwargs)
|
||||
if isinstance(self.caster, str):
|
||||
try:
|
||||
self._caster = import_string(self.caster)
|
||||
except ImportError as err:
|
||||
msg = "Could not import {!r}".format(self.caster)
|
||||
raise ImproperlyConfigured(msg) from err
|
||||
elif callable(self.caster):
|
||||
self._caster = self.caster
|
||||
else:
|
||||
|
|
@ -158,9 +162,7 @@ class CastingMixin(object):
|
|||
raise ValueError(error)
|
||||
try:
|
||||
arg_names = getargspec(self._caster)[0]
|
||||
self._params = dict((name, kwargs[name])
|
||||
for name in arg_names
|
||||
if name in kwargs)
|
||||
self._params = {name: kwargs[name] for name in arg_names if name in kwargs}
|
||||
except TypeError:
|
||||
self._params = {}
|
||||
|
||||
|
|
@ -181,7 +183,7 @@ class IntegerValue(CastingMixin, Value):
|
|||
class PositiveIntegerValue(IntegerValue):
|
||||
|
||||
def to_python(self, value):
|
||||
int_value = super(PositiveIntegerValue, self).to_python(value)
|
||||
int_value = super().to_python(value)
|
||||
if int_value < 0:
|
||||
raise ValueError(self.message.format(value))
|
||||
return int_value
|
||||
|
|
@ -213,7 +215,7 @@ class SequenceValue(Value):
|
|||
converter = kwargs.pop('converter', None)
|
||||
if converter is not None:
|
||||
self.converter = converter
|
||||
super(SequenceValue, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
# make sure the default is the correct sequence type
|
||||
if self.default is None:
|
||||
self.default = self.sequence_type()
|
||||
|
|
@ -257,7 +259,7 @@ class SingleNestedSequenceValue(SequenceValue):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.seq_separator = kwargs.pop('seq_separator', ';')
|
||||
super(SingleNestedSequenceValue, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def _convert(self, items):
|
||||
# This could receive either a bare or nested sequence
|
||||
|
|
@ -266,8 +268,7 @@ class SingleNestedSequenceValue(SequenceValue):
|
|||
super(SingleNestedSequenceValue, self)._convert(i) for i in items
|
||||
]
|
||||
return self.sequence_type(converted_sequences)
|
||||
return self.sequence_type(
|
||||
super(SingleNestedSequenceValue, self)._convert(items))
|
||||
return self.sequence_type(super()._convert(items))
|
||||
|
||||
def to_python(self, value):
|
||||
split_value = [
|
||||
|
|
@ -293,9 +294,9 @@ class BackendsValue(ListValue):
|
|||
|
||||
def converter(self, value):
|
||||
try:
|
||||
import_by_path(value)
|
||||
except ImproperlyConfigured as err:
|
||||
six.reraise(ValueError, ValueError(err), sys.exc_info()[2])
|
||||
import_string(value)
|
||||
except ImportError as err:
|
||||
raise ValueError(err).with_traceback(sys.exc_info()[2])
|
||||
return value
|
||||
|
||||
|
||||
|
|
@ -303,28 +304,28 @@ class SetValue(ListValue):
|
|||
message = 'Cannot interpret set item {0!r} in set {1!r}'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SetValue, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.default is None:
|
||||
self.default = set()
|
||||
else:
|
||||
self.default = set(self.default)
|
||||
|
||||
def to_python(self, value):
|
||||
return set(super(SetValue, self).to_python(value))
|
||||
return set(super().to_python(value))
|
||||
|
||||
|
||||
class DictValue(Value):
|
||||
message = 'Cannot interpret dict value {0!r}'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DictValue, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.default is None:
|
||||
self.default = {}
|
||||
else:
|
||||
self.default = dict(self.default)
|
||||
|
||||
def to_python(self, value):
|
||||
value = super(DictValue, self).to_python(value)
|
||||
value = super().to_python(value)
|
||||
if not value:
|
||||
return {}
|
||||
try:
|
||||
|
|
@ -336,12 +337,16 @@ class DictValue(Value):
|
|||
return evaled_value
|
||||
|
||||
|
||||
class ValidationMixin(object):
|
||||
class ValidationMixin:
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ValidationMixin, self).__init__(*args, **kwargs)
|
||||
if isinstance(self.validator, six.string_types):
|
||||
self._validator = import_by_path(self.validator)
|
||||
super().__init__(*args, **kwargs)
|
||||
if isinstance(self.validator, str):
|
||||
try:
|
||||
self._validator = import_string(self.validator)
|
||||
except ImportError as err:
|
||||
msg = "Could not import {!r}".format(self.validator)
|
||||
raise ImproperlyConfigured(msg) from err
|
||||
elif callable(self.validator):
|
||||
self._validator = self.validator
|
||||
else:
|
||||
|
|
@ -380,19 +385,19 @@ class RegexValue(ValidationMixin, Value):
|
|||
def __init__(self, *args, **kwargs):
|
||||
regex = kwargs.pop('regex', None)
|
||||
self.validator = validators.RegexValidator(regex=regex)
|
||||
super(RegexValue, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class PathValue(Value):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.check_exists = kwargs.pop('check_exists', True)
|
||||
super(PathValue, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def setup(self, name):
|
||||
value = super(PathValue, self).setup(name)
|
||||
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('Path {0!r} does not exist.'.format(value))
|
||||
return os.path.abspath(value)
|
||||
|
||||
|
||||
|
|
@ -401,13 +406,13 @@ class SecretValue(Value):
|
|||
def __init__(self, *args, **kwargs):
|
||||
kwargs['environ'] = True
|
||||
kwargs['environ_required'] = True
|
||||
super(SecretValue, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.default is not None:
|
||||
raise ValueError('Secret values are only allowed to '
|
||||
'be set as environment variables')
|
||||
|
||||
def setup(self, name):
|
||||
value = super(SecretValue, self).setup(name)
|
||||
value = super().setup(name)
|
||||
if not value:
|
||||
raise ValueError('Secret value {0!r} is not set'.format(name))
|
||||
return value
|
||||
|
|
@ -422,7 +427,7 @@ class EmailURLValue(CastingMixin, MultipleMixin, Value):
|
|||
kwargs.setdefault('environ', True)
|
||||
kwargs.setdefault('environ_prefix', None)
|
||||
kwargs.setdefault('environ_name', 'EMAIL_URL')
|
||||
super(EmailURLValue, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.default is None:
|
||||
self.default = {}
|
||||
else:
|
||||
|
|
@ -437,14 +442,14 @@ class DictBackendMixin(Value):
|
|||
kwargs.setdefault('environ', True)
|
||||
kwargs.setdefault('environ_prefix', None)
|
||||
kwargs.setdefault('environ_name', self.environ_name)
|
||||
super(DictBackendMixin, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.default is None:
|
||||
self.default = {}
|
||||
else:
|
||||
self.default = self.to_python(self.default)
|
||||
|
||||
def to_python(self, value):
|
||||
value = super(DictBackendMixin, self).to_python(value)
|
||||
value = super().to_python(value)
|
||||
return {self.alias: value}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from pkg_resources import get_distribution, DistributionNotFound
|
||||
|
||||
try:
|
||||
__version__ = get_distribution(__name__).version
|
||||
__version__ = get_distribution("django-configurations").version
|
||||
except DistributionNotFound:
|
||||
# package is not installed
|
||||
__version__ = None
|
||||
|
|
|
|||
|
|
@ -2,13 +2,7 @@ from . import importer
|
|||
|
||||
importer.install()
|
||||
|
||||
try:
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
except ImportError: # pragma: no cover
|
||||
from django.core.handlers.wsgi import WSGIHandler
|
||||
|
||||
def get_wsgi_application(): # noqa
|
||||
return WSGIHandler()
|
||||
from django.core.wsgi import get_wsgi_application # noqa: E402
|
||||
|
||||
# this is just for the crazy ones
|
||||
application = get_wsgi_application()
|
||||
|
|
|
|||
|
|
@ -3,6 +3,20 @@
|
|||
Changelog
|
||||
---------
|
||||
|
||||
unreleased
|
||||
^^^^^^^^^^
|
||||
|
||||
- **BACKWARD INCOMPATIBLE** Drop support for Python 2.7 and 3.5.
|
||||
|
||||
- **BACKWARD INCOMPATIBLE** Drop support for Django < 2.2.
|
||||
|
||||
- Add support for Django 3.1 and 3.2.
|
||||
|
||||
- Add suppport for Python 3.9.
|
||||
|
||||
- Deprecate ``utils.import_by_path`` in favor of
|
||||
``django.utils.module_loading.import_string``.
|
||||
|
||||
v2.2 (2019-12-03)
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
|
|
|||
28
docs/conf.py
28
docs/conf.py
|
|
@ -1,5 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# django-configurations documentation build configuration file, created by
|
||||
# sphinx-quickstart on Sat Jul 21 15:03:23 2012.
|
||||
#
|
||||
|
|
@ -43,8 +41,8 @@ source_suffix = '.rst'
|
|||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'django-configurations'
|
||||
copyright = u'2012-2014, Jannis Leidel and other contributors'
|
||||
project = 'django-configurations'
|
||||
copyright = '2012-2014, Jannis Leidel and other contributors'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
|
|
@ -186,8 +184,8 @@ latex_elements = {
|
|||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'django-configurations.tex', u'django-configurations Documentation',
|
||||
u'Jannis Leidel', 'manual'),
|
||||
('index', 'django-configurations.tex', 'django-configurations Documentation',
|
||||
'Jannis Leidel', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
|
|
@ -216,8 +214,8 @@ latex_documents = [
|
|||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'django-configurations', u'django-configurations Documentation',
|
||||
[u'Jannis Leidel'], 1)
|
||||
('index', 'django-configurations', 'django-configurations Documentation',
|
||||
['Jannis Leidel'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
|
|
@ -230,8 +228,8 @@ man_pages = [
|
|||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'django-configurations', u'django-configurations Documentation',
|
||||
u'Jannis Leidel', 'django-configurations', 'One line description of project.',
|
||||
('index', 'django-configurations', 'django-configurations Documentation',
|
||||
'Jannis Leidel', 'django-configurations', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
|
|
@ -248,10 +246,10 @@ texinfo_documents = [
|
|||
# -- Options for Epub output ---------------------------------------------------
|
||||
|
||||
# Bibliographic Dublin Core info.
|
||||
epub_title = u'django-configurations'
|
||||
epub_author = u'Jannis Leidel'
|
||||
epub_publisher = u'Jannis Leidel'
|
||||
epub_copyright = u'2012, Jannis Leidel'
|
||||
epub_title = 'django-configurations'
|
||||
epub_author = 'Jannis Leidel'
|
||||
epub_publisher = 'Jannis Leidel'
|
||||
epub_copyright = '2012, Jannis Leidel'
|
||||
|
||||
# The language of the text. It defaults to the language option
|
||||
# or en if the language is not set.
|
||||
|
|
@ -290,7 +288,7 @@ epub_copyright = u'2012, Jannis Leidel'
|
|||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {
|
||||
'python': ('http://docs.python.org/2.7', None),
|
||||
'python': ('http://docs.python.org/3', None),
|
||||
'sphinx': ('http://sphinx.pocoo.org/', None),
|
||||
'django': ('http://docs.djangoproject.com/en/dev/',
|
||||
'http://docs.djangoproject.com/en/dev/_objects/'),
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ probably just add the following to the **beginning** of your settings module:
|
|||
import configurations
|
||||
configurations.setup()
|
||||
|
||||
That has the same effect as using the ``manage.py`` or ``wsgi.py`` utilities.
|
||||
That has the same effect as using the ``manage.py``, ``wsgi.py`` or ``asgi.py`` utilities.
|
||||
This will also call ``django.setup()``.
|
||||
|
||||
>= 3.1
|
||||
|
|
@ -332,8 +332,8 @@ Channels
|
|||
--------
|
||||
|
||||
If you want to deploy a project that uses the Django channels with
|
||||
`Daphne <http://github.com/django/daphne/>` as the
|
||||
`interface server <http://channels.readthedocs.io/en/latest/deploying.html#run-interface-servers>`
|
||||
`Daphne <http://github.com/django/daphne/>`_ as the
|
||||
`interface server <http://channels.readthedocs.io/en/latest/deploying.html#run-interface-servers>`_
|
||||
you have to use a asgi.py script similar to the following:
|
||||
|
||||
.. code-block:: python
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ file:
|
|||
|
||||
class Dev(Base):
|
||||
DEBUG = True
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
|
||||
class Prod(Base):
|
||||
TIME_ZONE = 'America/New_York'
|
||||
|
|
@ -91,7 +90,7 @@ a few mixin you re-use multiple times:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
class FullPageCaching(object):
|
||||
class FullPageCaching:
|
||||
USE_ETAGS = True
|
||||
|
||||
Then import that mixin class in your site settings module and use it with
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@ value:
|
|||
|
||||
class Dev(Configuration):
|
||||
DEBUG = values.BooleanValue(True)
|
||||
TEMPLATE_DEBUG = values.BooleanValue(DEBUG)
|
||||
|
||||
See the list of :ref:`built-in value classes<built-ins>` for more information.
|
||||
|
||||
|
|
@ -162,7 +161,7 @@ the prefix.
|
|||
:param environ: toggle for environment use
|
||||
:param environ_name: name of environment variable to look for
|
||||
:param environ_prefix: prefix to use when looking for environment variable
|
||||
:param environ_required: wheter or not the value is required to be set as an environment variable
|
||||
:param environ_required: whether or not the value is required to be set as an environment variable
|
||||
:type environ: bool
|
||||
:type environ_name: capitalized string or None
|
||||
:type environ_prefix: capitalized string
|
||||
|
|
@ -353,6 +352,10 @@ Type values
|
|||
DEPARTMENTS = values.DictValue({
|
||||
'it': ['Mike', 'Joe'],
|
||||
})
|
||||
|
||||
Override using environment variables like this::
|
||||
|
||||
DJANGO_DEPARTMENTS={'it':['Mike','Joe'],'hr':['Emma','Olivia']}
|
||||
|
||||
Validator values
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
|
@ -547,7 +550,7 @@ Other values
|
|||
|
||||
::
|
||||
|
||||
MIDDLEWARE_CLASSES = values.BackendsValue([
|
||||
MIDDLEWARE = values.BackendsValue([
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
[wheel]
|
||||
universal = 1
|
||||
|
||||
[coverage:run]
|
||||
source = .
|
||||
branch = 1
|
||||
|
|
|
|||
14
setup.py
14
setup.py
|
|
@ -1,4 +1,3 @@
|
|||
from __future__ import print_function
|
||||
import os
|
||||
import codecs
|
||||
from setuptools import setup
|
||||
|
|
@ -27,7 +26,10 @@ setup(
|
|||
'django-cadmin = configurations.management:execute_from_command_line',
|
||||
],
|
||||
},
|
||||
install_requires=['six'],
|
||||
install_requires=[
|
||||
'django>=2.2',
|
||||
'setuptools',
|
||||
],
|
||||
extras_require={
|
||||
'cache': ['django-cache-url'],
|
||||
'database': ['dj-database-url'],
|
||||
|
|
@ -45,20 +47,18 @@ setup(
|
|||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Framework :: Django',
|
||||
'Framework :: Django :: 1.11',
|
||||
'Framework :: Django :: 2.0',
|
||||
'Framework :: Django :: 2.1',
|
||||
'Framework :: Django :: 2.2',
|
||||
'Framework :: Django :: 3.0',
|
||||
'Framework :: Django :: 3.1',
|
||||
'Framework :: Django :: 3.2',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: Implementation :: PyPy',
|
||||
'Topic :: Utilities',
|
||||
],
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ class Base(Configuration):
|
|||
# Django settings for test_project project.
|
||||
|
||||
DEBUG = values.BooleanValue(True, environ=True)
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
|
||||
ADMINS = (
|
||||
# ('Your Name', 'your_email@example.com'),
|
||||
|
|
@ -95,7 +94,7 @@ class Base(Configuration):
|
|||
'django.template.loaders.app_directories.Loader',
|
||||
)
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
MIDDLEWARE = (
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
from optparse import make_option
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
# Used by a specific test to see how unupgraded
|
||||
# management commands play with configurations.
|
||||
# See the test code for more details.
|
||||
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option('--arg1', action='store_true'),
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
pass
|
||||
|
|
@ -41,7 +41,7 @@ class Test(Configuration):
|
|||
|
||||
@property
|
||||
def ALLOWED_HOSTS(self):
|
||||
allowed_hosts = super(Test, self).ALLOWED_HOSTS[:]
|
||||
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||
allowed_hosts.append('base')
|
||||
return allowed_hosts
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
from configurations import Configuration
|
||||
|
||||
|
||||
class Mixin1(object):
|
||||
class Mixin1:
|
||||
@property
|
||||
def ALLOWED_HOSTS(self):
|
||||
allowed_hosts = super(Mixin1, self).ALLOWED_HOSTS[:]
|
||||
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||
allowed_hosts.append('test1')
|
||||
return allowed_hosts
|
||||
|
||||
|
||||
class Mixin2(object):
|
||||
class Mixin2:
|
||||
|
||||
@property
|
||||
def ALLOWED_HOSTS(self):
|
||||
allowed_hosts = super(Mixin2, self).ALLOWED_HOSTS[:]
|
||||
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||
allowed_hosts.append('test2')
|
||||
return allowed_hosts
|
||||
|
||||
|
|
@ -21,6 +21,6 @@ class Mixin2(object):
|
|||
class Inheritance(Mixin2, Mixin1, Configuration):
|
||||
|
||||
def ALLOWED_HOSTS(self):
|
||||
allowed_hosts = super(Inheritance, self).ALLOWED_HOSTS[:]
|
||||
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||
allowed_hosts.append('test3')
|
||||
return allowed_hosts
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@ from .single_inheritance import Inheritance as BaseInheritance
|
|||
class Inheritance(BaseInheritance):
|
||||
|
||||
def ALLOWED_HOSTS(self):
|
||||
allowed_hosts = super(Inheritance, self).ALLOWED_HOSTS[:]
|
||||
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||
allowed_hosts.append('test-test')
|
||||
return allowed_hosts
|
||||
|
|
|
|||
|
|
@ -5,6 +5,6 @@ class Inheritance(Base):
|
|||
|
||||
@property
|
||||
def ALLOWED_HOSTS(self):
|
||||
allowed_hosts = super(Inheritance, self).ALLOWED_HOSTS[:]
|
||||
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||
allowed_hosts.append('test')
|
||||
return allowed_hosts
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
"""Used by tests to ensure logging is kept when calling setup() twice."""
|
||||
|
||||
try:
|
||||
from unittest import mock
|
||||
except ImportError:
|
||||
from mock import mock
|
||||
from unittest import mock
|
||||
|
||||
import configurations
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import os
|
||||
from django.test import TestCase
|
||||
from mock import patch
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
class DotEnvLoadingTests(TestCase):
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import os
|
|||
|
||||
from django.test import TestCase
|
||||
|
||||
from mock import patch
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
class InheritanceTests(TestCase):
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import sys
|
|||
from django.test import TestCase
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
from mock import patch
|
||||
from unittest.mock import patch
|
||||
|
||||
from configurations.importer import ConfigurationImporter
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from contextlib import contextmanager
|
|||
from django.test import TestCase
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
from mock import patch
|
||||
from unittest.mock import patch
|
||||
|
||||
from configurations.values import (Value, BooleanValue, IntegerValue,
|
||||
FloatValue, DecimalValue, ListValue,
|
||||
|
|
@ -270,9 +270,9 @@ class ValueTests(TestCase):
|
|||
def test_set_values_default(self):
|
||||
value = SetValue()
|
||||
with env(DJANGO_TEST='2,2'):
|
||||
self.assertEqual(value.setup('TEST'), set(['2', '2']))
|
||||
self.assertEqual(value.setup('TEST'), {'2', '2'})
|
||||
with env(DJANGO_TEST='2, 2 ,'):
|
||||
self.assertEqual(value.setup('TEST'), set(['2', '2']))
|
||||
self.assertEqual(value.setup('TEST'), {'2', '2'})
|
||||
with env(DJANGO_TEST=''):
|
||||
self.assertEqual(value.setup('TEST'), set())
|
||||
|
||||
|
|
@ -485,12 +485,12 @@ class ValueTests(TestCase):
|
|||
self.assertEqual(value.value, set())
|
||||
|
||||
value = SetValue([1, 2])
|
||||
self.assertEqual(value.default, set([1, 2]))
|
||||
self.assertEqual(value.value, set([1, 2]))
|
||||
self.assertEqual(value.default, {1, 2})
|
||||
self.assertEqual(value.value, {1, 2})
|
||||
|
||||
def test_setup_value(self):
|
||||
|
||||
class Target(object):
|
||||
class Target:
|
||||
pass
|
||||
|
||||
value = EmailURLValue()
|
||||
|
|
|
|||
26
tox.ini
26
tox.ini
|
|
@ -4,20 +4,16 @@ usedevelop = true
|
|||
minversion = 1.8
|
||||
whitelist_externals = sphinx-build
|
||||
envlist =
|
||||
py36-checkqa,
|
||||
py{27,35,36,py}-dj111
|
||||
py{35,36,37,py3}-dj20
|
||||
py{35,36,37,py3}-dj21
|
||||
py{35,36,37,38,py3}-dj22
|
||||
py{36,37,38,py3}-dj{30,master}
|
||||
py36-checkqa
|
||||
py{36,37,38,39,py3}-dj{22,30,31,32}
|
||||
py{38,39}-djmain
|
||||
|
||||
[travis]
|
||||
[gh-actions]
|
||||
python =
|
||||
2.7: py27
|
||||
3.5: py35
|
||||
3.6: py36,flake8,readme
|
||||
3.7: py37
|
||||
3.8: py38
|
||||
3.9: py39
|
||||
pypy3: pypy3
|
||||
|
||||
[testenv]
|
||||
|
|
@ -27,13 +23,11 @@ setenv =
|
|||
DJANGO_CONFIGURATION = Test
|
||||
COVERAGE_PROCESS_START = {toxinidir}/setup.cfg
|
||||
deps =
|
||||
dj111: django>=1.11,<2.0
|
||||
dj20: django>=2.0a1,<2.1
|
||||
dj21: django>=2.1a1,<2.2
|
||||
dj22: django>=2.2a1,<3.0
|
||||
dj30: django>=3.0a1,<3.1
|
||||
djmaster: https://github.com/django/django/archive/master.tar.gz#egg=django
|
||||
py27,pypy: mock
|
||||
dj22: django~=2.2.17
|
||||
dj30: django~=3.0.11
|
||||
dj31: django~=3.1.3
|
||||
dj32: https://github.com/django/django/archive/stable/3.2.x.tar.gz
|
||||
djmain: https://github.com/django/django/archive/main.tar.gz
|
||||
coverage
|
||||
coverage_enable_subprocess
|
||||
extras = testing
|
||||
|
|
|
|||
Loading…
Reference in a new issue