Compare commits

..

506 commits
0.1 ... master

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


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

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

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

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

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

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

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

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

https://github.com/jazzband/django-configurations/issues/358
2023-09-27 14:08:16 +03:00
Michal Szczesny
dce5f37a93
Merge pull request #366 from washeck/master
Add pypy 3.10 to the testing matrix
2023-09-27 11:31:26 +01:00
Vaclav Rehak
2278975744 Add pypy 3.10 to the testing matrix 2023-09-22 17:55:59 +02:00
Paolo Melchiorre
cad6dcb7f0
Fix #355 - Update to Django 4.2 stable (#356) 2023-04-04 12:56:38 +02:00
Paolo Melchiorre
adaf92085f Fix #353 Improve code blocks in documentation 2023-02-15 07:50:29 +01:00
Paolo Melchiorre
a1f072ebf3
Fix #351 Use 'furo' as Sphinx theme (#352) 2023-02-14 16:01:22 +01:00
Ran Benita
6f47271526
Fix "STATICFILES_STORAGE/STORAGES are mutually exclusive" on Django>=4.2 (#349) 2023-02-13 09:18:38 +01:00
Camilo Nova
befe7f1e0d
Merge pull request #346 from pauloxnet/feature/actions-update
Update github actions and fix tests
2023-01-20 12:52:54 -06:00
Paolo Melchiorre
352d95b2ab
Update github actions and fix pipeline 2023-01-19 18:06:05 +01:00
Paolo Melchiorre
1f8bac5ba4 Fixed #344 - Run tests on python 3.11 2022-11-04 18:52:34 +01:00
Camilo Nova
17ca033d33
Merge pull request #342 from timgates42/bugfix_typos
docs: Fix a few typos
2022-09-22 17:04:19 -05:00
Camilo Nova
7520ae4123
Merge pull request #341 from michael-k/fix-release-workflow
Pin publish action in release workflow
2022-09-22 17:03:41 -05:00
Tim Gates
ac5408d7eb
docs: Fix a few typos
There are small typos in:
- configurations/base.py
- configurations/values.py
- docs/patterns.rst

Fixes:
- Should read `whether` rather than `wether`.
- Should read `overridden` rather than `overriden`.
- Should read `environment` rather than `enviroment`.

Signed-off-by: Tim Gates <tim.gates@iress.com>
2022-09-01 20:41:53 +10:00
Michael Käufl
e2861e6327
Pin publish action in release workflow
From https://github.com/pypa/gh-action-pypi-publish/blob/unstable/v1/README.md:

    The `master` branch version has been sunset. Please, change the
    GitHub Action version you use from `master` to `release/v1` or use
    an exact tag, or a full Git commit SHA.
2022-08-31 17:41:17 +02:00
Paolo Melchiorre
40d244d865 Update docs/changes.rst
Co-authored-by: Michael K. <michael-k@users.noreply.github.com>
2022-08-25 10:58:39 +02:00
Paolo Melchiorre
8923ef8c49 Prepare next release 2.4 2022-08-25 10:58:39 +02:00
Andrii Oriekhov
7e473d0f9b
add GitHub URL for PyPi (#331) 2022-08-05 16:43:17 +02:00
Paolo Melchiorre
794b858548
Fixed #336 -- Update Python and Django versions (#337) 2022-08-05 16:42:58 +02:00
Nicolas Delaby
141d8ef2c4
Merge pull request #328 from ticosax/prepare-new-release
Prepare next release 2.3.2
2022-01-25 10:27:57 +01:00
Nicolas Delaby
fcba8b6d92 Prepare next release 2.3.2 2022-01-25 09:26:07 +01:00
Nicolas Delaby
861935fd45
Merge pull request #327 from ticosax/fix-deprecated-settings
Remove deprecated default fields only from the global
2022-01-25 09:12:33 +01:00
Nicolas Delaby
5f438451ea add changelog entry 2022-01-24 18:57:41 +01:00
Nicolas Delaby
45a4557efe Remove deprecated default fields only from the global
And keep user defined values
2022-01-24 18:46:17 +01:00
Jannis Leidel
bb2523ddb9
Fix badge URL. 2021-12-23 20:36:48 +01:00
Paolo Melchiorre
32a41d62b9
Align README badges with other jazzband projects (#326) 2021-12-23 20:35:53 +01:00
Paolo Melchiorre
ded548b19b Update Django 4.0 stable versions 2021-12-23 17:23:58 +06:00
Jannis Leidel
c3b5e8627b
Update changelog. 2021-11-08 16:56:32 +01:00
Brian Helba
93e628e870
Refactor the documentation build process (#311)
* Refactor the documentation build process

Significant improvements:
* Allow easy isolated local builds via a dedicated Tox environment
* Configure Sphinx to use consistent packages and theme across local, CI,
  and ReadTheDocs builds; local builds now look the same as the published
  RTD pages
* Explicitly add a ReadTheDocs configuration as code, per their documented
  best practices
* Remove lots of dead code and simplify the Sphinx configuration file
* Build docs in a dedicated CI step and enable stricter warning checking

* Fix docutils related build error.

* Relax Sphinx version.

* Invalidate RTD build cache.

* No need to combine coverage reporting I think.

* Add combining again.

Co-authored-by: Jannis Leidel <jannis@leidel.info>
2021-11-08 16:49:41 +01:00
Paolo Melchiorre
7ff29852ab
Add pypy versions support (#321)
* Add pypy versions support

* Update Python classifiers
2021-11-08 15:50:21 +01:00
Jannis Leidel
2655ecdd4f
Add python version limiter. 2021-11-08 15:48:22 +01:00
Jannis Leidel
750f143724
Fix documentation build errors. 2021-11-08 15:48:07 +01:00
Paolo Melchiorre
dcce8bccfd
Add support port Django 3.2 and Python 3.10 in tox
Django 3.2.9 adds compatibility with Python 3.10.
https://docs.djangoproject.com/en/3.2/releases/3.2.9/
2021-11-01 12:20:42 +01:00
Brian Helba
1f17d675a1
Merge pull request #319 from jazzband/authors 2021-10-27 12:50:35 -04:00
Brian Helba
381809d88c Add new AUTHORS since 2.2 release
Got this from "git shortlog -s 2.2..master".
2021-10-27 12:41:15 -04:00
Brian Helba
5698fe35b2 Finalize changelog for 2.3 release 2021-10-27 22:40:16 +06:00
Paolo Melchiorre
9f38e87a58
Update Python and Django versions in tox.ini (#307)
* Update Python and Django versions in tox.ini

- remove Django 3.1
- add Django 4.0b1
- add Python 3.10

* Add python 3.10  in github action matrix

* Update tox.ini from django-debug-toolbar

* Fix whitespaces

* Add missing setup classifiers

* Fix typo in tox.ini

* Remove django 4 classifier
2021-10-27 16:16:26 +06:00
Brian Helba
5562322599 Update changelog 2021-10-27 14:24:34 +06:00
Brian Helba
cea79d1a7a Revert "Update release.yml" 2021-10-27 10:23:34 +02:00
Brian Helba
03dc29848e Fix "reference target not found" warnings in the docs
These were found by running Sphinx in nitpicky mode.
2021-10-27 10:17:56 +02:00
Brian Helba
596ab9a1bf Restore an example of a dependent setting to the docs
This was removed by #303. This now uses a real Django setting,
"DEBUG_PROPAGATE_EXCEPTIONS", instead of a deprecated one.
2021-10-27 10:16:59 +02:00
Brian Helba
80f2cee84b Update the copyright date range 2021-10-27 08:06:34 +02:00
Thomas Grainger
089a039efa Added "python -m configurations" entry point.
inspired by e0a46367df/django/__main__.py (L1-L9)

see also https://code.djangoproject.com/ticket/24857
2021-10-27 07:29:02 +06:00
Brian Helba
1dce659946
Merge pull request #305 from jazzband/importlib-version 2021-10-25 14:41:48 -04:00
Brian Helba
f5d6ef7877 Add importlib.metadata backport for older Python versions 2021-10-25 14:33:55 -04:00
Brian Helba
4bd4cf5dd4 Use importlib.metadata instead of pkg_resources to get version 2021-10-25 14:33:54 -04:00
Brian Helba
91b359f74a
Merge pull request #304 from jazzband/rm-mock 2021-10-25 13:21:58 -04:00
Brian Helba
d373c9ab75 Remove "mock" as a test requirement
This is included in the standard library of Python 3.3+.
2021-10-25 13:05:36 -04:00
Brian Helba
62d34c2a16
Merge pull request #253 from linuxmaniac/vseva/fix_250 2021-10-25 13:04:19 -04:00
Brian Helba
02e8f55ac8 Merge remote-tracking branch 'origin/master' into vseva/fix_250 2021-10-25 12:37:36 -04:00
Brian Helba
6c2ea44352
Merge pull request #303 from jazzband/template-debug 2021-10-25 12:33:25 -04:00
Brian Helba
2d9e145a0f
Merge pull request #258 from arsensokolov/master 2021-10-25 12:33:08 -04:00
Brian Helba
b75c8d7a7a Remove references to TEMPLATE_DEBUG
This setting is deprecated since Django 1.8.
2021-10-25 12:18:19 -04:00
Brian Helba
c9c44c59b7
Merge pull request #290 from brianhelba/2.2-deprecated 2021-10-25 11:36:23 -04:00
Brian Helba
38059e1417
Merge pull request #302 from jazzband/rm-command 2021-10-25 11:36:05 -04:00
Brian Helba
df967f4d76 Remove an obsolete command used for testing
The test using this was removed in a045609934.
2021-10-25 11:21:06 -04:00
Brian Helba
6da8420635 Prevent warnings for settings deprecated in Django 2.2
Fixes #233.
2021-10-25 11:15:14 -04:00
Brian Helba
a155ac54ad
Merge pull request #287 from brianhelba/auto-field 2021-10-25 11:13:46 -04:00
Brian Helba
40bdab3f4a Preserve Django warnings when DEFAULT_AUTO_FIELD is not set
Fixes #286.
2021-10-25 10:40:24 -04:00
Brian Helba
b1f62da419
Merge pull request #281 from brianhelba/warning 2021-10-25 10:38:07 -04:00
Brian Helba
f4e2241f44
Merge pull request #300 from jazzband/wsgi-import 2021-10-25 10:35:03 -04:00
Brian Helba
c9c4a02169
Merge pull request #301 from jazzband/space-typo 2021-10-25 10:33:41 -04:00
Brian Helba
ec12828877 Simplify import of django.core.wsgi.get_wsgi_application
This has been available since Django 1.4. The import guard is no longer necessary.
2021-10-25 09:52:21 -04:00
Brian Helba
91ef9fd8ad Fix a double space typo in a string 2021-10-25 09:50:51 -04:00
Brian Helba
df51535afc
Merge pull request #291 from jazzband/feature/asgi 2021-10-25 09:49:54 -04:00
Brian Helba
7c9ac5e53f
Suppress import ordering style error 2021-10-25 09:34:42 -04:00
Brian Helba
36265ab3f1
Merge pull request #288 from brianhelba/require-django 2021-10-25 09:32:59 -04:00
Brian Helba
a5542b2bfc
Merge pull request #299 from jazzband/sort-manifest 2021-10-25 09:26:10 -04:00
Brian Helba
3d1554b4f8
Merge pull request #298 from jazzband/ci-push 2021-10-25 09:21:37 -04:00
Brian Helba
3ac97e539f Require Django as an install_requires
Since "import django" occurs in the code, it is an explicit requirement.
2021-10-25 09:21:17 -04:00
Brian Helba
a56889dbc1
Merge pull request #282 from hroncok/patch-1 2021-10-25 09:17:19 -04:00
Brian Helba
09dfa471e0 Sort MANIFEST.in 2021-10-25 09:12:15 -04:00
Brian Helba
71924a195f
Merge pull request #296 from jazzband/jazzband/sync/default 2021-10-25 09:11:29 -04:00
Brian Helba
55a92d86ad
Merge pull request #252 from timgates42/bugfix_typo_whether 2021-10-25 09:08:12 -04:00
Brian Helba
fad40b8003
Merge pull request #256 from bmispelon/malformed-rst-links 2021-10-25 09:05:23 -04:00
Brian Helba
8ec465de77
Merge pull request #289 from brianhelba/middleware 2021-10-25 09:03:21 -04:00
Brian Helba
7e4b425ea3 Don't run duplicate CI tasks on every PR
The "pull_request" action already runs on pushes to any branch with an open PR,
so only run the "push" action on the "master" branch.
2021-10-25 09:02:06 -04:00
Brian Helba
12e033ed1e Add CODE_OF_CONDUCT.md to the sdist 2021-10-25 08:57:16 -04:00
Brian Helba
38534a7caf Merge remote-tracking branch 'origin/master' into jazzband/sync/default 2021-10-25 08:56:56 -04:00
Brian Helba
96ef5cc182
Merge pull request #297 from jazzband/fix-ci 2021-10-25 08:56:24 -04:00
Brian Helba
fa026af595 Add a pre-commit configuration 2021-10-25 08:38:39 -04:00
jazzband-bot
68cbb437cf Jazzband: Created local 'CODE_OF_CONDUCT.md' from remote 'CODE_OF_CONDUCT.md' 2021-10-25 11:14:26 +00:00
Finn-Thorben Sell
6a4a620891
add additional documentation references to asgi.py 2021-09-09 15:11:49 +02:00
Finn-Thorben Sell
2cae9838b5
add documentation to README about get_asgi_application 2021-09-09 15:08:42 +02:00
Finn-Thorben Sell
c1cb3874f2
remove fallback onto ASGIHandler
ASGIHandler is considered a django internal api with get_asgi_application being the public counterpart.
get_asgi_application is also present since the introduction of django's asgi support so the ImportError should never occur anyways.
2021-09-09 15:01:11 +02:00
Richard de Wit
1ada7d14f7
Add ASGI support
Then in your project's `asgi.py` file use this:

```python
from configurations.asgi import get_asgi_application

application = get_asgi_application()
```
2021-09-09 14:35:09 +02:00
Brian Helba
271f6fb5bb Replace references to MIDDLEWARE_CLASSES with MIDDLEWARE
MIDDLEWARE_CLASSES was removed in Django 2.0.
2021-08-31 22:01:34 -04:00
Brian Helba
d715a719a4 Use a more general structure for removing deprecated settings 2021-08-31 21:38:51 -04:00
Miro Hrončok
58a4f689ff
Require setuptools, configurations/version.py imports pkg_resources 2021-04-12 21:53:16 +02:00
Brian Helba
f85ec1fb89
Prevent an ImproperlyConfigured warning from DEFAULT_HASHING_ALGORITHM 2021-04-09 00:20:36 -04:00
Jannis Leidel
d89fe5a2cb
Rename Django's dev branch to main. (#279)
* Rename Django's dev branch to main.

More information: https://groups.google.com/g/django-developers/c/tctDuKUGosc/
Refs: https://github.com/django/django/pull/14048

* Fix test matrix.

* Remove CODEOWNERS again.

* Fix name of tox env.

* Fix test matrix.
2021-03-09 19:20:31 +01:00
Jannis Leidel
856b55bea8
Merge pull request #275 from jazzband/co
Codeowners update.
2021-01-19 16:41:05 +01:00
Jannis Leidel
3da6af02fb
Codeowners update. 2021-01-19 16:40:19 +01:00
Jannis Leidel
c8153bc081
Merge pull request #274 from jazzband/branch-protection-1
Update release.yml
2021-01-19 16:34:23 +01:00
Jannis Leidel
6fa45aba1b
Update release.yml 2021-01-19 14:55:46 +01:00
Jannis Leidel
58575bfb84
Minor update. 2021-01-19 14:54:47 +01:00
Jannis Leidel
184df10a66
Add code owner for workflows. 2021-01-19 14:48:20 +01:00
Michael Käufl
9b9ff4c0a2 Deprecate utils.import_by_path in favor of django.utils.module_loading.import_string 2021-01-19 13:11:16 +06:00
Michael Käufl
c3d5b4b715 Add support for Python 3.9 2021-01-19 13:11:16 +06:00
Michael Käufl
add9b3ce80 Add support for Django 3.1 and 3.2 2021-01-19 13:11:16 +06:00
Michael Käufl
6cb932e47f Run pyupgrade on the code
But don't touch string formatting.

https://pypi.org/project/pyupgrade/
2021-01-19 13:11:16 +06:00
Michael Käufl
fcdbfc0bcb Drop support for Python 3.5
It has reached its EOL in September 2020, see
https://www.python.org/dev/peps/pep-0478/#release-schedule
2021-01-19 13:11:16 +06:00
Michael Käufl
dd5d6974cb Drop support for Django < 2.2 LTS, incl. Python 2.7 2021-01-19 13:11:16 +06:00
Jannis Leidel
78f9824f0c
Merge pull request #270 from jazzband/gha
Migrate to GitHub Actions
2020-11-27 17:31:02 +01:00
Jannis Leidel
f5bebd4ecc
Add fail_ci_if_error to test workflow. 2020-11-27 09:59:12 +01:00
Jannis Leidel
5053da4357
Add release workflow. 2020-11-26 17:08:22 +01:00
Jannis Leidel
eea5b9ad34
Remove travis cruft. 2020-11-26 17:08:14 +01:00
Jannis Leidel
64eefaef1a
Fix an issue with an old version of dj-email-url. 2020-11-26 16:51:15 +01:00
Jannis Leidel
b540ceadb3
Add GitHub Actions test workflow. 2020-11-26 16:18:58 +01:00
Michael K
3305de960a
Merge pull request #261 from dat2/patch-1
fix version string at runtime
2020-10-07 15:20:56 +00:00
Nicholas Dujay
4fb5928a9c
use package name in get_distribution function call 2020-09-21 09:43:42 -04:00
Arseny Sokolov
b9648bddb3
Add DictValue example 2020-06-17 23:38:48 +08:00
Baptiste Mispelon
566af30ce6 Fixed malformed links in documentation 2020-05-29 12:15:08 +02:00
Victor Seva
ac6d31bb83 testing: remove django-discover-runner
project supports Django 1.11+

> https://pypi.org/project/django-discover-runner/

This runner has been added to Django 1.6 as the default test runner.
If you use Django 1.6 or above you don’t need this app.

fix #250
2020-03-24 10:55:04 +01:00
Tim Gates
3b3f5db60d
docs: Fix simple typo, wheter -> whether
There is a small typo in docs/values.rst.

Should read `whether` rather than `wheter`.
2020-03-12 06:40:15 +11:00
Jannis Leidel
601d52218a
Merge pull request #234 from ckrybus/update-django-trove-classifiers
Update django trove classifiers
2020-01-06 19:45:18 +01:00
Christoph Krybus
6314038a77 Update django trove classifiers 2020-01-06 17:10:39 +01:00
Jannis Leidel
8d7036d253
Merge pull request #248 from jazzband/tox-travis
Move to tox-travis. Fix #246.
2019-12-03 13:25:36 +01:00
Jannis Leidel
42641f5eb4
Switch to setuptools-scm. 2019-12-03 13:09:15 +01:00
Jannis Leidel
bd61710591
Add check-manifest. 2019-12-03 12:47:25 +01:00
Jannis Leidel
b9f6aa98ec
No Bandit. 2019-12-03 12:42:36 +01:00
Jannis Leidel
c15996a0fa
Use twine check instead of readme_renderer. 2019-12-03 12:08:49 +01:00
Jannis Leidel
8e32e5b3c7
Add COVERAGE_PROCESS_START env var again. 2019-12-03 12:05:47 +01:00
Jannis Leidel
3de33277a7
Update changelog. 2019-12-03 12:00:41 +01:00
Jannis Leidel
7f469b6d6e
Fix Python version. 2019-12-03 11:55:03 +01:00
Jannis Leidel
0f486ef6e4
Fix running on Django master.
This fixes an exception that was introduced upstream in 226ebb1729 (diff-ec2bed07bb264cb95a80f08d71a47c06R163-R170)
2019-12-03 11:52:00 +01:00
Jannis Leidel
843271a701
Move to tox-travis. Fix #246. 2019-12-03 11:51:21 +01:00
Paolo Melchiorre
12ea3c0a5b Prepare release v2.2 2019-12-02 14:26:05 +01:00
Paolo Melchiorre
51e0265276
Merge pull request #242 from johnfraney/master
Update tested Python & Django versions
2019-11-29 18:28:58 +01:00
John Franey
7e74d67308 Update changelog for v2.2 2019-11-29 12:03:02 -05:00
John Franey
9c9b07dca5 Sort .travis.yml by dj version; add more pypy3 coverage 2019-11-28 20:35:00 -05:00
John Franey
0f479a56f5 Remove xenial references for Travis
Removes Xenial references in Travis config file since it is now the
default CI build environment.

See:

https://blog.travis-ci.com/2019-04-15-xenial-default-build-environment
2019-11-28 20:22:44 -05:00
John Franey
f387e1ee63 Remove Python 3.4 and add 3.8 2019-11-28 20:12:04 -05:00
John Franey
41b905c92e Update tested Python & Django versions
Replaces `django.utils.six` with `six` to support newer Django versions.

Updates Tox config to:
- remove unsupported Django versions
- add pypy3
- add Django 2.2
- update djmaster Python versions
2019-10-06 15:43:03 -04:00
Jannis Leidel
be2a0dc18f
Merge pull request #237 from jazzband/checks
Enable fussy fox check suite
2019-04-01 21:29:01 +02:00
Rustem Saiargaliev
12036fabc8
Fix typo
Co-Authored-By: codingjoe <info@johanneshoppe.com>
2019-04-01 15:27:44 +02:00
Johannes Hoppe
bda8c22065
Rename .checks.yml to .fussyfox.yml 2019-04-01 14:44:43 +02:00
Johannes Hoppe
934ca95883 Add checkqa test environment to tox
Add support to run QA tests locally
2019-03-26 11:36:41 +01:00
Johannes Hoppe
ec8a8e7df4 Fix bandit and flake8 errors 2019-03-26 08:16:18 +01:00
Johannes Hoppe
b754256413 Enable check suite in favor of travis-ci QA runs 2019-03-26 08:15:50 +01:00
Harry Moreno
f887ad5821
Explain default value for ecretValue() 2019-01-24 15:41:33 -05:00
Harry Moreno
2db80d8c34
Flesh out usage of values.Value() in .env 2019-01-24 15:37:34 -05:00
Harry Moreno
f65000b38e
remove quote from secret key to fix syntax 2019-01-24 15:13:00 -05:00
Harry Moreno
4c64bb602f
Improve reading from .env
* fix runtime error when `values` is accessed
* show how an env var can be set and read
2019-01-19 16:23:59 -05:00
Peter Bittner
ec56685da4 Move Sentry recommendations to Cookbook section
Fix code sample bug

Use code-block markup consistently
2018-12-14 09:34:35 +01:00
Peter Bittner
690e8784f6 Link to RTD project dashboard, use shields.io badge 2018-12-10 15:42:53 +01:00
Peter Bittner
f38e07b94a
Merge pull request #223 from jazzband/docs/explain-dotenv-files
Explain how to read .env files
2018-12-10 13:17:29 +01:00
Peter Bittner
3c8590a8d4
Merge pull request #222 from jazzband/feature/remove-deprecated-import
Remove deprecated import (gone in Django 1.9)
2018-12-10 13:17:02 +01:00
Peter Bittner
e7d63e0815 Explain how to read .env files 2018-12-10 03:47:52 +01:00
Peter Bittner
4480bdb4ad
Merge pull request #221 from jazzband/feature/readme-more-badges
More badges to display project properties (README)
2018-12-10 01:48:26 +01:00
Peter Bittner
79b92372f7 Remove deprecated import (gone in Django 1.9) 2018-12-10 00:16:51 +01:00
Daniel Hahler
605e6fe296
flake8: ignore W503 (and pick W504) (#220) 2018-12-09 18:48:17 +01:00
Peter Bittner
55592874b4 More badges to display project properties (README) 2018-12-09 18:40:12 +01:00
Peter Bittner
aa7864f722
Merge pull request #213 from spookyUnknownUser/patch-1
Add sentry example to patterns
2018-12-09 17:27:02 +01:00
Peter Bittner
efea2224da
Merge pull request #219 from jazzband/feature/add-extras-require
Add extras for optional requirements
2018-12-09 16:14:45 +01:00
Peter Bittner
06f2d57386 Fix flake8 complaints 2018-12-09 15:52:53 +01:00
Peter Bittner
0e45c7b3e4 Also move testing requirements into packaging setup 2018-12-09 15:52:05 +01:00
Peter Bittner
a2dffdb8e4 Add extras for optional requirements 2018-12-09 15:48:12 +01:00
spookyUnknownUser
6bc31023d2
Drop heroku from example
It doesn't actually work the way I thought it did. Though sentry still works great!
2018-10-29 12:47:35 +02:00
spookyUnknownUser
54e4e76af9
Add sentry example to patterns 2018-10-28 23:57:36 +02:00
Daniel Hahler
cfdaf6caca
Travis: fix reporting of coverage (#211) 2018-08-30 22:35:13 +02:00
Daniel Hahler
9dcb47dc55
Fix utils.reraise for exceptions without args (#210)
This is the case for e.g. `bdb.BdbQuit`.
2018-08-30 21:15:31 +02:00
Daniel Hahler
18dcab03f9
Merge pull request #209 from blueyed/coverage_enable_subprocess
tests: use coverage_enable_subprocess instead of sitecustomize.py
2018-08-30 18:10:17 +02:00
Daniel Hahler
ea060f0985 tox/Travis: use separate coverage factor
This will only install/run coverage on demand.
2018-08-26 02:00:52 +02:00
Daniel Hahler
cc2a821579 tests: use coverage_enable_subprocess instead of sitecustomize.py 2018-08-26 01:53:11 +02:00
Daniel Hahler
5ea0db0aee
Merge pull request #191 from blueyed/setup-once
_setup: do not call django.setup() if settings are configured already
2018-08-16 18:12:52 +02:00
Daniel Hahler
a0a43494d0 Add sitecustomize.py to fix subprocess coverage reporting 2018-08-16 17:12:37 +02:00
Daniel Hahler
8f199eb40c _setup: do not call django.setup() if settings are configured already
Without this, using `configurations.setup()` after Django has been setup
already re-triggers the logging configuration, which then causes e.g.
pytest's caplog to not work anymore.
2018-08-16 17:04:50 +02:00
Drew Winstel
a045609934 Drop obsolete Django 1.8 and 1.10 from Travis configs (#207)
* Drop obsolete versions of Django from Travis tests

Django 1.8 reached EOL in April 2018.
Django 1.10 reached EOL in December 2017.

* Add support for testing Django 2.0 and 2.1 on Python 3.7

* Drop obsolete test (requires Django < 1.10)
2018-08-16 17:03:31 +02:00
Jannis Leidel
52a86327fc
Merge pull request #202 from blueyed/coverage-subprocess
coverage: track subprocesses
2018-08-16 16:02:08 +02:00
Daniel Hahler
89cdca07ae coverage: track subprocesses 2018-08-16 15:50:41 +02:00
Daniel Hahler
b2d27dc016 Travis: fix codecov integration
Closes https://github.com/jazzband/django-configurations/pull/208
2018-08-16 15:38:33 +02:00
Drew Winstel
2d885caa46 Drop Python 3.4 from Django master tests
Django master explicitly refuses to build on Python 3.4, so let's
drop testing for it.
2018-08-16 15:37:39 +02:00
Drew Winstel
c804a30aa2 Add changelog for version 2.1 2018-08-16 14:38:37 +02:00
Daniel Hahler
fbb0ff8884 Fix compatibility with Django 2.1
Ref: https://github.com/django/django/pull/9894
Closes: https://github.com/jazzband/django-configurations/pull/201
2018-08-02 20:32:40 +02:00
Daniel Hahler
b1d92cf85d doc: add version information to PositiveIntegerValue
Ref: 1c6fd0f505 (r29675182)
2018-07-11 22:05:59 +02:00
Jannis Leidel
c0ac52f948
Merge pull request #200 from jazzband/revert-197-issue/GH196
Revert "add configuration as suggested from @blueyed"
2018-07-11 20:47:00 +02:00
Jannis Leidel
80a648bb03 Revert "doc: cookbook: document workaround for celery (#197)"
This reverts commit 2b0d2cee0a.
2018-07-11 20:46:21 +02:00
Jannis Leidel
5770f02bab Revert "Production/stable, really."
This reverts commit ba346e2af4.
2018-07-11 20:46:21 +02:00
Jannis Leidel
537fde7f5b Revert "Update Travis deploy info."
This reverts commit b43bf2dd47.
2018-07-11 20:46:21 +02:00
Jannis Leidel
b43bf2dd47
Update Travis deploy info.
Refs https://github.com/jazzband/roadies/issues/102.
2018-07-11 20:25:45 +02:00
Jannis Leidel
ba346e2af4
Production/stable, really. 2018-07-11 20:25:15 +02:00
gthieleb
2b0d2cee0a doc: cookbook: document workaround for celery (#197)
See https://github.com/jazzband/django-configurations/issues/196
2018-07-11 17:52:23 +02:00
Daniel Hahler
ee43a1d872 coverage: improve config / conditionals
- move config to setup.cfg, add report-include
- Travis: codecov: skip fixes, generate and pass coverage.xml
- only do coverage for py jobs, fix -X for codecov

Closes https://github.com/jazzband/django-configurations/pull/194
2018-07-11 17:40:58 +02:00
Mike
1c6fd0f505 Add PositiveIntegerValue to only allow positive integers (#186) 2018-03-15 13:19:50 +01:00
Pavel Savchenko
3883cc4fe4 Docs: explicitly describe property in patterns.rst (#189)
Hopefully this saves time for new users of django-configuration (like myself), who "just needed" to lazily evaluate a string inside a dictionary.

This doubles as an example for `RAVEN_CONFIG` which was the whole reason I was here in the first place... The actual problem I faced was that a setting remains of type `values.Value` when nested inside a dictionary. Which results in a weird issues like this:

```
2018-02-24T20:59:26.125208+00:00 app[web.1]: Traceback (most recent call last):
2018-02-24T20:59:26.125364+00:00 app[web.1]:     self.client.http_context(self.get_http_context(environ))
2018-02-24T20:59:26.125215+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/raven/middleware.py", line 98, in __call__
2018-02-24T20:59:26.125368+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/raven/contrib/django/models.py", line 54, in <lambda>
2018-02-24T20:59:26.125482+00:00 app[web.1]:     __getattr__ = lambda x, o: getattr(get_client(), o)
2018-02-24T20:59:26.125486+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/raven/contrib/django/models.py", line 134, in get_client
2018-02-24T20:59:26.125613+00:00 app[web.1]:     instance = Client(**options)
2018-02-24T20:59:26.125618+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/raven/contrib/django/client.py", line 147, in __init__
2018-02-24T20:59:26.125769+00:00 app[web.1]:     Client.__init__(self, *args, **kwargs)
2018-02-24T20:59:26.125771+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/raven/base.py", line 171, in __init__
2018-02-24T20:59:26.125927+00:00 app[web.1]:     self.set_dsn(dsn, transport)
2018-02-24T20:59:26.125929+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/raven/base.py", line 251, in set_dsn
2018-02-24T20:59:26.126063+00:00 app[web.1]:     if dsn not in self._transport_cache:
2018-02-24T20:59:26.126076+00:00 app[web.1]: TypeError: unhashable type: 'Value'
```
2018-02-27 21:43:18 +01:00
Daniel Hahler
51e2d3e7d2 .coveragerc: use include instead of multiple sources (#183)
This will generate a better coverage.xml file, which makes it easier for
codecov hopefully.

Old:

    <sources>
            <source>…/Vcs/django-configurations/configurations</source>
            <source>…/Vcs/django-configurations/tests</source>
    </sources>
    <packages>
            <package branch-rate="0.7178" complexity="0" line-rate="0.8902" name=".">

New:
    <sources>
            <source>…/Vcs/django-configurations</source>
    </sources>
    <packages>
            <package branch-rate="0.712" complexity="0" line-rate="0.8126" name="configurations">

Fixes d364802a8a (commitcomment-24826518).
2017-10-07 18:03:03 +02:00
Daniel Hahler
e3b547f5e1 qa: run flake8 against tests (#180) 2017-10-07 12:32:20 +02:00
Daniel Hahler
d364802a8a Fix Django 2.0: use new url syntax (#182) 2017-10-05 02:42:34 +02:00
Nicolas Delaby
1060acaf78 Merge pull request #178 from ticosax/drop-unmaintained-version-django
Drop unmaintained version of  django and python
2017-10-04 19:43:52 +02:00
Nicolas Delaby
8f318991c2 Drop unmaintained version of django and python
- Drop django==1.9 as it reached end of life
- Drop python2.6, python3.3, and bring python3.6
- Bring django-2.0 in the matrix as expected failure until we add its
support
2017-09-30 14:37:38 +02:00
Daniel Hahler
deb94fd61e Merge pull request #179 from blueyed/coverage
Improvements to coverage reporting
2017-09-29 17:57:48 +02:00
Daniel Hahler
a7dfb7dfcf codecov: use flags based on TOXENV 2017-09-29 12:57:47 +02:00
Daniel Hahler
ece5a35790 Travis: report coverage, install codecov only after_success 2017-09-29 12:57:47 +02:00
Daniel Hahler
abfc8a7002 .coveragerc: include tests 2017-09-29 12:57:47 +02:00
Daniel Hahler
6360a34c55 Fix Travis for py33 and pypy
Python 3.3 reached EOL today, so remove it (works around
test_multiprocessing failing there
(https://travis-ci.org/jazzband/django-configurations/jobs/281237282)).
2017-09-29 12:49:01 +02:00
Daniel Hahler
6f163727dc tests: fix test_cache_url_value for new django-cache-url 2017-09-29 12:21:12 +02:00
e_fpischedda
6ed4559c92 doc: mention how to configure Daphne (channels interface server)
Closes https://github.com/jazzband/django-configurations/pull/164.
2017-09-24 00:33:29 +02:00
Sanny Kumar
4fea22bd16 included license (#176)
Include LICENSE file when packaging
2017-09-23 22:58:44 +02:00
Luke Murphy
645395af47 Add docs for environ_name functionality (#174)
* Add note for final value forcing

Closes https://github.com/jazzband/django-configurations/issues/173

* Remove white space.
2017-07-16 16:10:46 +02:00
José Antonio Perdiguero
a606003c77 Fix truthy and falsey of values (#162) 2016-10-13 00:00:47 +02:00
Jannis Leidel
ea8d4deb58 Merge pull request #156 from adamchainz/readthedocs.io
Convert readthedocs links for their .org -> .io migration for hosted projects
2016-07-30 02:11:24 +02:00
Jannis Leidel
9d8f12ea27 Simplified Travis config. 2016-07-30 00:11:54 +02:00
Jannis Leidel
eb0cde231b Update changelog. 2016-07-30 00:03:19 +02:00
Jannis Leidel
5dba80b313 Finally remove configurations.Settings. 2016-07-30 00:00:47 +02:00
Johannes Hoppe
7aaffd3a95 Add EMAIL_USE_SSL to tests (#157)
`dj-email-url` added support for `EMAIL_USE_SSL` in its latest
version.
2016-06-14 18:59:13 +02:00
Rustem Sayargaliev
1c5bd06c68 Merge pull request #154 from codingjoe/issues/146
Fixes #146 -- Adds multiprocessing support for sphinx
2016-06-14 13:07:10 +02:00
Johannes Hoppe
9592356572 Move sphinx callback to a separate sphinx submodule 2016-06-13 12:26:52 +02:00
Adam Chainz
4158480c91 Convert readthedocs links for their .org -> .io migration for hosted projects
As per [their blog post of the 27th April](https://blog.readthedocs.com/securing-subdomains/) ‘Securing subdomains’:

> Starting today, Read the Docs will start hosting projects from subdomains on the domain readthedocs.io, instead of on readthedocs.org. This change addresses some security concerns around site cookies while hosting user generated data on the same domain as our dashboard.

Test Plan: Manually visited all the links I’ve modified.
2016-06-11 10:56:42 +01:00
Johannes Hoppe
cd596f6788 Fixes #146 -- Adds multiprocessing support for sphinx 2016-04-05 18:14:20 +02:00
Daniel Hahler
7701e86b75 Merge pull request #153 from adamn/patch-1
Fix typo in CONTRIBUTING.md
2016-03-21 22:22:27 +01:00
Adam Nelson
e4ab567e1e Typo 2016-03-21 14:40:28 -04:00
Daniel Hahler
407af2e27d Merge pull request #150 from blaze33/master
Fix RemovedInDjango19Warning and test_database_url_value
2016-03-13 14:33:25 +01:00
Maxime Rouyrre
7b4df1f6ce Fixed test_database_url_value 2016-03-11 12:01:40 +01:00
Maxime Rouyrre
aba5100e4f Fixed RemovedInDjango19Warning
It's better to try the new import method first because in Django<1.9 the
old method is still available and will raise a RemovedInDjango19Warning
that we could have avoided.
2016-03-11 11:02:26 +01:00
Daniel Hahler
d66ecc6d6e Merge pull request #144 from ticosax/remove-old-versions
Drop support of python 2.6, 3.2 and Django < 1.8
2016-01-27 22:56:51 +01:00
Nicolas Delaby
cf3961ea1d Bump upcoming version to 2.0 as this changeset introduce breaking change 2016-01-27 21:08:54 +01:00
Nicolas Delaby
063bf61a16 Drop support of python 2.6, 3.2 and Django < 1.8 2016-01-27 21:08:54 +01:00
Daniel Hahler
abe1890c5b Merge pull request #142 from mauricioabreu/add-standalone-script-doc
Add documentation for standalone scripts
2016-01-12 15:42:10 +01:00
Mauricio de Abreu Antunes
6d52e560a3 Add documentation for standalone scripts 2016-01-12 12:22:08 -02:00
Daniel Hahler
4f91fcc6a7 Merge pull request #141 from mauricioabreu/improve-example-usage
Handle example usage for Django 1.9
2016-01-12 00:14:19 +01:00
Mauricio de Abreu Antunes
0a4cae9c2b Handle example usage for Django 1.9
Django 1.9 makes the languages setting a list of tuples.
2016-01-08 18:27:30 -02:00
Nicolas Delaby
4615e1e8fb Bump version to 1.0.1.dev
The version pattern follows the recommendation described in PEP 440.
https://www.python.org/dev/peps/pep-0440/#development-release-separators

Closes https://github.com/jazzband/django-configurations/pull/139.
2016-01-07 12:28:05 +01:00
Nicolas Delaby
320324b511 Merge pull request #138 from ticosax/stub-release-notes
Add stub release note for v1.1
2016-01-05 10:47:00 +01:00
Nicolas Delaby
8f68d93a3c Add stub release note for v1.0.1
Encourage developers to contribute to the changelog as they go.
2016-01-05 10:26:08 +01:00
Jannis Leidel
0aba2276bc Added automatic check for README rendering. 2016-01-04 22:05:05 +01:00
Jannis Leidel
2ad095ebfa Removed unsupported rST directive options to fix README rendering on PyPI. 2016-01-04 20:52:18 +01:00
Nicolas Delaby
376b5947a8 Merge pull request #136 from ticosax/changelog-release-1.0
Changelog for release 1.0
2016-01-04 19:43:22 +01:00
Nicolas Delaby
261817d965 Prepare next release 1.0 2016-01-04 14:24:50 +01:00
James Keys
4da8939df0 Update changelog. 2016-01-04 14:24:50 +01:00
Nicolas Delaby
d827664e92 Merge pull request #124 from gatherhealth/master
Fix optparse fallback logic in Django 1.8+
2016-01-04 14:21:14 +01:00
Jannis Leidel
5560a21d09 Added Python version print to make sure we're running the right version in the tox env. Fix #137. 2016-01-04 13:17:24 +01:00
Jannis Leidel
8e2eb88ef1 Made tox call verbose. 2016-01-04 13:08:54 +01:00
Nicolas Delaby
1b75984f57 Merge pull request #135 from ticosax/codecov-integration
Add codecov integration
2016-01-04 12:55:31 +01:00
Nicolas Delaby
067cfaf6e6 Add codecov integration 2016-01-04 09:56:36 +01:00
John R Dietrick
66bb6a8725 Get skipIf without installing unittest2
In Python 2.7+, unittest *is* unittest2. We prefer this one. But in
Python 2.6, we can get skipIf from django.utils.unittest, provided
Django is 1.8 or older.
2016-01-03 01:21:29 -05:00
John R Dietrick
4fe1c74b35 Add a test which demonstrates the optparse fallback issue 2016-01-03 01:20:29 -05:00
John R Dietrick
cd0f1b4d0c Fix handling of fallback to optparse.OptionParser
By the time the code here runs, it's too late to be modifying
base.BaseCommand.option_list; in fact doing so causes an OptionConflictError if
you later call another management command "recursively" while you're running
the first one.

We want to leave BaseCommand's option_list untouched (it's `()` by default in
Django 1.8+) in case the command we are wrapping has already upgraded to
argparse. BUT, if it hasn't, we'll get an OptionParser back, and can tack our
argument on at the last minute.
2015-12-19 18:25:53 +08:00
Daniel Hahler
afb057f33b Merge pull request #131 from blueyed/fix-tests-and-travis
Fix tests and travis
2015-12-18 18:21:46 +01:00
Daniel Hahler
cb3a02ee51 Travis: use master branch in build status image
The URL is the one suggested by Travis CI itself.
2015-12-17 23:31:08 +01:00
Daniel Hahler
06fa8d3eab Travis: minor: fix indent 2015-12-17 23:31:08 +01:00
Daniel Hahler
b5e1eb6ade Travis/tox: use dj19 tarball, add master; remove unsupported
- Add `allow_failures` section for Django master on Travis.
 - Django 1.9 is only supported on Python 2.7 and 3.4+.
 - Only test main Python 2 and 3 with Django master.
2015-12-17 23:31:08 +01:00
Daniel Hahler
3a1a88bd15 Travis: use Python 3.5 for Python 3 flake8 test 2015-12-17 23:07:40 +01:00
Daniel Hahler
11990d1db6 tox: do not use coverage with Python 3.2
Fixes https://github.com/jezdez/django-configurations/issues/129.
2015-12-17 13:05:03 +01:00
Daniel Hahler
09eae37b23 Fix test_cache_url_value for newer django-cache-url
This fixes the following failure:

    ERROR: test_cache_url_value (tests.test_values.ValueTests)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "…/django-configurations/configurations/values.py", line 158, in to_python
        return self._caster(value)
      File "…/django-configurations/.tox/py34-dj18/lib/python3.4/site-packages/django_cache_url.py", line 98, in parse
        config['LOCATION'] = "%s:%s:%s" % (url.hostname, url.port, db)
      File "…/pyenv/3.4.3/lib/python3.4/urllib/parse.py", line 156, in port
        port = int(port, 10)
    ValueError: invalid literal for int() with base 10: 'port'

    During handling of the above exception, another exception occurred:

    Traceback (most recent call last):
      File "…/django-configurations/tests/test_values.py", line 415, in test_cache_url_value
        value = CacheURLValue(cache_url)
      File "…/django-configurations/configurations/values.py", line 420, in __init__
        self.default = self.to_python(self.default)
      File "…/django-configurations/configurations/values.py", line 423, in to_python
        value = super(DictBackendMixin, self).to_python(value)
      File "…/django-configurations/configurations/values.py", line 160, in to_python
        raise ValueError(self.message.format(value))
    ValueError: Cannot interpret cache URL value 'redis://user@host:port/1'

    ----------------------------------------------------------------------
    Ran 63 tests in 1.132s

    FAILED (errors=1)
2015-12-17 13:05:03 +01:00
Jannis Leidel
3fe709196a Hook up PyPI via Travis. 2015-12-17 10:25:23 +01:00
Jannis Leidel
dd128b586d Moved to Jazzband. 2015-12-17 10:22:38 +01:00
Jannis Leidel
85fe4c0ae5 Merge branch 'master' of github.com:jezdez/django-configurations 2015-12-17 10:20:52 +01:00
Jannis Leidel
0ff4c7612d Added a bus factor indicator. 2015-08-10 10:34:50 +02:00
Jannis Leidel
010067b433 Introduced environ_required parameter for Value class. Refs #118. 2015-08-09 14:54:55 +02:00
Jannis Leidel
8ba1804ff2 Added full_environ_name method to Value class. 2015-08-09 14:48:16 +02:00
Jannis Leidel
5ece107044 Only run __init__ in __new__ when late binding is enabled.
Signed-off-by: Jannis Leidel <jannis@leidel.info>
2015-03-16 21:19:40 +01:00
Miguel Araujo Perez
e09e1e0f42 Added equal operator to values.Value
Allows value setting to work when checking if setting variable is within
a list, i.e: settings.database in ['default', 'other']

Signed-off-by: Jannis Leidel <jannis@leidel.info>
2015-03-16 20:54:17 +01:00
Jannis Leidel
e332d5eff4 Use tar.gz instead of zip to work around an encoding issue in pip's unpacker. 2015-03-16 19:30:57 +01:00
Jannis Leidel
d9f60cfaef Try using the pip caching in the tox config. 2015-03-16 16:59:37 +01:00
Jannis Leidel
01274d4965 Use pip caching and docker based Travis runner. 2015-03-16 15:17:55 +01:00
Jannis Leidel
6f6930495c Get rid of manage.py for running tests now that we have django-cadmin.
Closes #92.
2015-03-16 15:13:31 +01:00
Jannis Leidel
cc869bf577 Merge pull request #94 from RonnyPfannschmidt/entrypoints
add entrypoints to run in non-source-tree installs
2015-03-16 15:08:46 +01:00
Ronny Pfannschmidt
7a0b2b378a add entrypoints to run in non-source-tree installs 2015-02-19 10:51:32 +01:00
Jannis Leidel
c60b7daac4 Fixed SequenceValue to work on Python 3.x. 2015-02-13 22:29:14 +01:00
Jannis Leidel
4d35ad346c Call django.setup() for Sphinx, too.
This also adds a configurations.setup() analogue to django.setup().
2015-02-13 22:18:33 +01:00
Jannis Leidel
b91ebf083f Merge pull request #91 from benjaminabel/notebook-support-for-django17
Add `django.setup()` in `load_ipython_extension` function for django>=1....
2015-02-13 22:08:56 +01:00
Jannis Leidel
8f617ad98c Merge pull request #85 from abbottc/master
Add values.TupleOfTuplesValue (plus associated tests and docs)
2015-02-13 22:08:08 +01:00
Jannis Leidel
13980b3d80 Add Django 1.9/master testing. 2015-02-13 22:06:43 +01:00
Jannis Leidel
f7629aa84c Fix test error on Python 3.x. 2015-02-13 21:54:21 +01:00
Jannis Leidel
6ce3740365 Cover the case in which BaseCommand.create_parser retursn optparse.OptionParser. 2015-02-13 21:52:47 +01:00
Jannis Leidel
f35e7e57e0 Add global --configuration option in Django >= 1.8. 2015-02-13 21:47:08 +01:00
Jannis Leidel
9be0c4f700 Fix compatibility between Django versions with regard to the type of some iterable settings such as TEMPLATE_CONTEXT_PROCESSORS. 2015-02-13 21:46:25 +01:00
Jannis Leidel
61d162d376 Fix tox config. 2015-02-13 21:45:37 +01:00
Jannis Leidel
8be47c0813 Merge remote-tracking branch 'benjaminabel/master' 2015-02-13 18:12:56 +01:00
Jannis Leidel
ea28a6ebd6 Minor fixes for 1.8. 2015-02-13 18:12:02 +01:00
Jannis Leidel
c6b3d05f71 Revert "Use py.test."
This reverts commit 7799241900.
2015-02-13 16:43:55 +01:00
Jannis Leidel
21d1712143 Use importlib.import_module if needed. 2015-02-13 16:24:02 +01:00
Jannis Leidel
e0e12b1b9f Fixed minor flake8 error. 2015-02-13 16:21:17 +01:00
Jannis Leidel
2e57cde3ea pytest-django works with Django 1.8 now 2015-02-10 18:29:01 +01:00
Benjamin ABEL
546f488197 Add django.setup() in load_ipython_extension function for django>=1.7 compatibility 2015-02-01 16:47:37 +01:00
Benjamin ABEL
36a7061a61 Add a test for configuration argument
This test do not use mock, and only searches the `configuration` option in
the help messages.
2015-01-27 22:49:58 +01:00
Benjamin ABEL
ae767eaf2d Cleaned importer to be more DRY 2015-01-27 22:36:59 +01:00
Benjamin ABEL
d9b2815526 Use CommandParser instead of LaxOptionParser in django1.8
Added a django version check, and removed the `LaxOptionParser` import for django>=1.8 and used `CommandParser` instead as in Claude Paroz django [commit](8568638603 (diff-860fce37924469764af399caaa365e00R275))

Reference: [#19973 (Management commands migration to argparse) –
Django](https://code.djangoproject.com/ticket/19973)
2015-01-25 18:13:47 +01:00
Benjamin ABEL
a8bf15b358 Use django 1.8 branch in tox.ini 2015-01-25 18:12:41 +01:00
Jannis Leidel
f53e999041 Merge branch 'master' of github.com:jezdez/django-configurations 2015-01-15 13:06:15 +01:00
Jannis Leidel
ffbf79c95d Removed gittip. 2015-01-15 13:06:02 +01:00
Jannis Leidel
186f50c2a4 Merge pull request #88 from benjaminabel/master
Remove coverage env from `tox.ini`
2015-01-07 15:39:40 +01:00
Benjamin ABEL
e20c9b5939 Remove coverage env from tox.ini 2015-01-07 15:28:12 +01:00
Christian Abbott
8287ab6f7f Merge remote-tracking branch 'upstream/master' 2015-01-07 03:19:24 -08:00
Christian Abbott
e0a68fdbb6 Rename TupleOfTupleValue to SingleNestedTupleValue; Add SingleNestedListValue and do a DRY refactor of ListValue, TupleValue, and their SingleNested subclasses 2015-01-07 03:08:30 -08:00
Jannis Leidel
fe96f5b46a Install pytest-django master to work around issue with Django 1.8. 2015-01-07 00:11:15 +01:00
Jannis Leidel
14dd728ad4 Improved docs. 2015-01-07 00:06:38 +01:00
Jannis Leidel
8a34b53500 Fixed version in docs.
Signed-off-by: Jannis Leidel <jannis@leidel.info>
2015-01-07 00:05:47 +01:00
Jannis Leidel
f2a46fb009 Merge remote-tracking branch 'nagyv/patch-1'
Signed-off-by: Jannis Leidel <jannis@leidel.info>
2015-01-07 00:05:27 +01:00
Jannis Leidel
f1f9973547 Remove coverage env from travis config.
Signed-off-by: Jannis Leidel <jannis@leidel.info>
2015-01-06 23:28:33 +01:00
Jannis Leidel
7799241900 Use py.test.
Signed-off-by: Jannis Leidel <jannis@leidel.info>
2015-01-06 23:27:00 +01:00
Jannis Leidel
aba18a4cd8 Fix 1.8 test runner. 2015-01-06 23:15:57 +01:00
Jannis Leidel
44476bdd1d Merge remote-tracking branch 'hiisi13/master'
Signed-off-by: Jannis Leidel <jannis@leidel.info>
2015-01-06 23:15:08 +01:00
Jannis Leidel
01e3f5837f Merge remote-tracking branch 'joke2k/dot-env'
Signed-off-by: Jannis Leidel <jannis@leidel.info>

Conflicts:
	.gitignore
2015-01-06 22:34:29 +01:00
Jannis Leidel
ebc0c51dd8 Merge branch 'master' of github.com:jezdez/django-configurations 2015-01-06 21:38:20 +01:00
Jannis Leidel
98de57b27e Added configurations.management.call_command. Fix #72. 2015-01-06 21:38:08 +01:00
Jannis Leidel
cb9f7c36fb Merge pull request #83 from mauricioabreu/master
Fixed wrong behaviour when assigning BooleanValue
2015-01-06 21:23:37 +01:00
Jannis Leidel
a8643a1af5 Simplify coverage setup. 2015-01-06 21:20:52 +01:00
Jannis Leidel
9dd8ba2a6e Ignore dists. 2015-01-06 21:16:41 +01:00
Jannis Leidel
41dfcee46c Backported LaxOptionParser from Django 1.7 to make it work on 1.8. 2015-01-06 21:16:14 +01:00
Mauricio de Abreu Antunes
3f892f9814 Fixed wrong behaviour when assigning BooleanValue
When assigning False to a BooleanValue and reusing it was raising a
ValueError exception because BooleanValue was evaluating False as None.

Thanks to @abbottc for catching this error.
2014-12-27 10:34:40 -02:00
Christian Abbott
c4ba5ca559 Add TupleOfTuplesValue along with associated tests and updated documentation 2014-12-18 07:15:20 -08:00
Jannis Leidel
5d1a8d7887 Merge branch 'master' of github.com:jezdez/django-configurations 2014-12-12 17:26:43 +01:00
Jannis Leidel
45c9127d4e More fixes to the test setup. 2014-12-12 17:26:21 +01:00
Jannis Leidel
fb9275744e Merge pull request #78 from slafs/validationmixinfix
fix ValidationMixin instantiation with no default
2014-12-12 16:28:58 +01:00
Jannis Leidel
ecb3db3762 Merge pull request #76 from rassie/patch-1
Correct documentation about mixin usage
2014-12-12 16:28:15 +01:00
Jannis Leidel
08eabf2ae0 Merge remote-tracking branch 'heww/master' 2014-12-12 16:27:46 +01:00
Jannis Leidel
82f2514b81 Merge remote-tracking branch 'slafs/newtox' 2014-12-12 16:26:18 +01:00
Jannis Leidel
6ddd647361 Merge pull request #73 from jpadilla/master
Test 1.7 against stable version instead of master
2014-12-12 16:22:33 +01:00
Sławek Ehlert
8d366fca44 fix ValidationMixin instantiation with no default
with tests. fixes #69
2014-10-30 23:44:48 +01:00
Sławek Ehlert
55a2cf6a36 use tox generative envlist with factor dependencies 2014-10-30 23:18:26 +01:00
Nikolai Prokoschenko
d97afc163a Correct documentation about mixin usage
`Configuration` class needs to be last in the inheritance list, otherwise it's not possible to override Django's default settings. Tests are already correct, just the documentation is wrong.
2014-10-20 11:06:05 +02:00
He Weiwei
f8ebb564da fix bug of setup_value func for EmailURLValue 2014-10-08 12:19:35 +08:00
José Padilla
f0d956740c Test 1.7 against stable version instead of master 2014-09-09 12:06:18 -04:00
Viktor Nagy
738b79bc70 more generic path given 2014-08-12 23:10:05 +02:00
Jannis Leidel
7af6d9e7c5 Merge pull request #62 from sassman/issues/#61
solution proposal for Issue #61
2014-08-12 20:41:18 +02:00
Viktor Nagy
1998ba8d8c Added recipe to work with Sphinx 2014-05-22 09:36:11 +02:00
Sven Aßmann
95e378aec7 add python 3.4 tox environments to travis for builds 2014-05-02 23:48:35 +02:00
Sven Aßmann
4ec540c32c - cleanup of unused tox configuration 2014-05-02 22:57:20 +02:00
Sven Aßmann
467e4ebe52 - add configuration for python 3.4 with same django versions as for 3.3
- add coverage summary and html report for checking code coverage
2014-05-02 22:53:32 +02:00
Sven Aßmann
4018f7b42a - add late_binding to value with default False
- this enables a value to load from environment if the env variable name is given at construction time
- cache value of environment value now in property value
- add __new__ to allow a construction of a given type from environment directly. In this case now Value instance is constructed but an instance of the desired type that is covered by the Value implementation. For Value it is str, for DictValue it is dict etc.
- extend tests for this new behavior
- extend test coverage for some other places
2014-05-02 22:51:48 +02:00
joke2k
27eb748d68 refactoring of DotConfiguration 2014-04-17 18:07:33 +02:00
joke2k
f5c8b49842 Merge branch 'cleaning' into dot-env 2014-04-17 16:26:01 +02:00
joke2k
8919aa163e Merge branch 'master' into cleaning 2014-04-17 16:25:37 +02:00
joke2k
5279ae4ace add support to load .env file into os.environ 2014-04-17 16:23:36 +02:00
joke2k
8a8f99ab68 add *.pyc to .gitignore; remove repeated INSTALLED_APPS from test_project.settings; fix check dj-database-url returned None values to empty string 2014-04-17 16:22:31 +02:00
Jannis Leidel
280be2e3e2 Fix database URL test. 2014-04-15 15:36:37 -04:00
Jannis Leidel
e1e02c2e8b Merge pull request #58 from jpadilla/patch-1
Small typo fix on FastCGI section
2014-04-15 15:33:38 -04:00
José Padilla
1701b95fa8 Small typo fix on FastCGI section 2014-03-20 08:41:31 -04:00
Jannis Leidel
035c3301a8 Merge pull request #55 from hiisi13/test_fixes
test_cache_url_value fix
2014-03-03 12:15:11 +01:00
Dmitry Kozhedubov
3ae1e368c2 test_cache_url_value fix 2014-03-02 20:41:40 +04:00
Dmitry Kozhedubov
e33eeeb871 Ability to pass keyword args to CastingMixin casters 2014-03-02 13:31:42 +04:00
Jannis Leidel
88ae1f28cd Merge pull request #52 from luzfcb/patch-1
added link to docs on README.rst
2014-02-12 18:23:24 +01:00
Fábio C. Barrionuevo da Luz
d99758d4af added link to docs 2014-02-12 12:27:38 -02:00
Jannis Leidel
a55bca2d0c Merge pull request #50 from chrishas35/patch-1
Fixed typos in docs
2014-02-08 10:14:55 +00:00
Chris Hasenpflug
97ac529254 Fixed typos in docs 2014-02-07 23:59:24 -06:00
Jannis Leidel
9da8256062 Fixed typo in docs. 2014-01-16 19:04:59 +01:00
Jannis Leidel
713dce0048 Bump version. 2014-01-16 19:01:35 +01:00
Jannis Leidel
38aaca68c6 Ignore .tox. 2014-01-16 19:01:28 +01:00
Jannis Leidel
6278a17ae4 Happy New Year! 2014-01-16 19:00:40 +01:00
Jannis Leidel
f087cea84d Added SearchURLValue. 2014-01-16 19:00:29 +01:00
Jannis Leidel
7ddaf158b1 Added 0.3.2 release from 0.3.x. 2014-01-16 17:59:37 +01:00
Jannis Leidel
290a72993c Added another item to the changelog. 2013-11-26 16:46:40 +01:00
Jannis Leidel
11b1dc2a46 Updated version. 2013-11-26 16:29:00 +01:00
Jannis Leidel
11e2f1d349 Updated changelog. 2013-11-26 16:28:50 +01:00
Jannis Leidel
a407bebf5d Removed an unsupported test environment. 2013-11-26 15:51:05 +01:00
Jannis Leidel
5950740c5d Fixed a 1.7.x test. 2013-11-26 15:46:53 +01:00
Jannis Leidel
072f87d495 Minor cleanup. 2013-11-26 15:42:15 +01:00
Jannis Leidel
c941cdaf06 Fixed #46 — Update cookbook with an example for Celery >= 3.1. 2013-11-26 12:57:33 +01:00
Jannis Leidel
ab0c748318 Fixed #43 — Removed stdout wrapper from management command hook to not accidently raise an exception too early. Instead use a logger. 2013-11-26 12:25:50 +01:00
Jannis Leidel
7dbe8c063f Ported tests to run via tox and Travis. 2013-11-26 12:25:07 +01:00
Jannis Leidel
ed8d284d66 Merge branch 'master' of github.com:jezdez/django-configurations 2013-10-08 09:32:43 +02:00
Stefan Wehrmeyer
275a5b409a Fix check_options of ConfigurationImporter 2013-10-08 09:32:05 +02:00
Jannis Leidel
c014b06c7e Merge pull request #41 from stefanw/fix-cachevalue-default
Fix CacheURLValue default value
2013-10-08 00:17:09 -07:00
Stefan Wehrmeyer
5e8dc290df Fix CacheURLValue default value
Remove unused kwarg 'name' that shadows kwarg
'default'.
2013-10-06 16:33:24 +02:00
Jannis Leidel
52c679e676 Added 0.3.1 hotfix release for issue #37. 2013-09-20 19:16:21 +02:00
Jannis Leidel
df71a193a3 Bumped version up a notch. 2013-09-19 18:56:23 +02:00
Jannis Leidel
139ad79c92 Moved the changelog into the docs. 2013-09-19 18:56:07 +02:00
Jannis Leidel
52cc7ee4bd Bumped version up to 0.6a1. 2013-09-18 23:25:19 +02:00
Jannis Leidel
50a6a6ad0b Added iPython extension to make sure the import hook is loaded correctly.
Fixes #22.
2013-09-18 23:24:51 +02:00
Jannis Leidel
924daa385d Minor fix in cookbook sentence. 2013-09-18 23:23:31 +02:00
Jannis Leidel
9a132e7b67 Move envdir paragraph to the top of the cookbook. 2013-09-18 23:23:18 +02:00
Jannis Leidel
ff2b636081 Updated changelog and bumped version up a notch. 2013-09-12 19:22:25 +02:00
Jannis Leidel
76d68c6e15 Fix showing version number. 2013-09-12 19:22:08 +02:00
Jannis Leidel
c54d5c0b43 Merge branch 'master' of github.com:jezdez/django-configurations 2013-09-12 18:52:21 +02:00
Jannis Leidel
2fd4966549 Add check_options option to the install method of the ConfigurationImporter to prevent unwanted command line option checks outside the management commands. 2013-09-12 18:51:59 +02:00
Jannis Leidel
f90b69d54a Merge pull request #34 from paltman/master
Minor grammar fix
2013-09-11 23:47:38 -07:00
Patrick Altman
7412fc2824 Minor grammar fix 2013-09-11 23:05:22 -05:00
Jannis Leidel
cd97138a8b Merge pull request #33 from paltman/master
Fix typo in changelog
2013-09-10 02:44:06 -07:00
Patrick Altman
7c9ca47d53 Fix typo in changelog 2013-09-10 01:27:34 -05:00
Jannis Leidel
0c4bfebeff Updated changelog. 2013-09-09 11:03:18 +02:00
Jannis Leidel
7770a69ced Drop d2to1. 2013-09-09 11:03:00 +02:00
Jannis Leidel
5898acb594 Moved tests out of the configurations packages. 2013-09-09 11:02:43 +02:00
Jannis Leidel
46809d02b2 Worked around an issue with Django 1.6's six. 2013-09-06 23:39:53 +02:00
Jannis Leidel
e453d2355d Fix a Python2ism 2013-09-06 23:28:50 +02:00
Jannis Leidel
515eb603c7 Slight revamp of the values docs. 2013-09-06 23:24:34 +02:00
Jannis Leidel
3848e6b818 Drastically simplified exception messages in the importer.
Less eyebleed® included!
2013-09-06 22:41:14 +02:00
Jannis Leidel
dacca7e073 Use ValueError instead of Django's ImproperlyConfigured.
This is to prevent hiding the real reason of a configuration failure behind one of Django's import time exceptions.
2013-09-06 22:40:15 +02:00
Jannis Leidel
27d77f7e29 Update changelog date. 2013-09-03 13:18:22 +02:00
Jannis Leidel
141e137d9f Minor style change for docs. 2013-09-03 13:13:20 +02:00
Jannis Leidel
1da2414ee1 Minor style changes and bumping version. 2013-09-03 13:10:25 +02:00
Jannis Leidel
cd2144492c Minor edits to the docs. 2013-09-03 13:03:59 +02:00
Jannis Leidel
a4e1dfff87 Added 1.6.x template branch to ignore list. 2013-09-03 12:40:43 +02:00
Jannis Leidel
556365bc8f Minor edits to the values docs. 2013-09-03 12:30:39 +02:00
Jannis Leidel
79e093fc58 Use newer Django versions. 2013-09-03 12:04:50 +02:00
Jannis Leidel
927c052be5 Changed the default of the Value's environ option to True. 2013-09-03 12:03:34 +02:00
Jannis Leidel
5f4229057d Use new release of django-cache-url. 2013-07-27 17:07:43 +02:00
Jannis Leidel
f4cea1e97b Moved project templates into cookbooks. 2013-07-27 16:49:50 +02:00
Jannis Leidel
61bb7b0e44 Fixed string formatting. Part 2. 2013-07-27 16:07:43 +02:00
Jannis Leidel
90292e3cec Fixed string formatting. 2013-07-27 16:05:23 +02:00
Jannis Leidel
b8222d2271 Raise an ImproperlyConfigured exception if value handling doesn't work, thanks @pydanny for the suggestion. 2013-07-27 15:38:30 +02:00
Jannis Leidel
19c7765d04 Bumped version up a notch. 2013-07-27 14:57:30 +02:00
Jannis Leidel
c6fbc08bda Updated changelog. 2013-07-27 14:56:45 +02:00
Jannis Leidel
2cc38d9ff7 Don't use django-discover-runner on 1.6 and up. 2013-07-27 13:46:18 +02:00
Jannis Leidel
d9931fbf72 Fixed 1.6.x requirement. 2013-07-27 13:42:21 +02:00
Jannis Leidel
5f97cc334e use python3 compat version of django-cache-url 2013-07-27 13:35:19 +02:00
Jannis Leidel
ef26377f13 Force list return value for filtered result. 2013-07-27 13:20:42 +02:00
Jannis Leidel
0882b97972 Fixed test method names. 2013-07-27 13:18:20 +02:00
Jannis Leidel
a954783851 Added six to test requirements since invoke needs it 2013-07-27 13:14:27 +02:00
Jannis Leidel
2c39f5f29b Also don't raise an exception during import for the Settings class since that's now deprecated. 2013-07-27 13:05:11 +02:00
Jannis Leidel
3a2fde649f Fixed broken package files. 2013-07-27 12:53:08 +02:00
Jannis Leidel
6695699f1d Switch to using invoke instead of Make. 2013-07-27 12:37:44 +02:00
Jannis Leidel
1a54847375 Added a configuration values system.
This also adds some advanced features like a setup classmethod to the Configuration class.
Reorganized and extended the documentation.
2013-07-27 12:37:28 +02:00
Jannis Leidel
6db9fdbf30 Stop using crate and PyPI mirrors. 2013-07-27 12:07:58 +02:00
Jannis Leidel
e31adbaeed Renamed Settings class to Configuration to better match what it means. Settings is still importable and is marked to be deprecated in 1.0. 2013-07-27 12:05:39 +02:00
Jannis Leidel
aac7af881c Add note about project template. 2013-06-26 12:11:15 +02:00
Jannis Leidel
dedd5682aa Don't build the templates branch. 2013-06-26 11:03:08 +02:00
Jannis Leidel
2fde445890 Fixed typo. 2013-05-15 16:34:06 +02:00
Jannis Leidel
822397799b Fix the test suite for the post_load method. 2013-05-15 13:15:47 +02:00
Jannis Leidel
0b9e5ed1cf Bumped version up a notch. 2013-05-15 12:50:49 +02:00
Jannis Leidel
4b08084787 Updated changelog. 2013-05-15 12:30:35 +02:00
Jannis Leidel
d98ce1aa81 Fixed README example to not confuse beginners. Thanks, @peterbe. 2013-05-15 12:29:25 +02:00
Jannis Leidel
bebe4f254b Extended the setup methods in pre and post phase. Fixes #27. 2013-05-15 11:32:57 +02:00
Jannis Leidel
d4da16273a Merge branch 'master' of github.com:jezdez/django-configurations
Conflicts:
	configurations/decorators.py
2013-05-15 11:06:51 +02:00
Jannis Leidel
708fb9ac50 Renamed the pristine decorator to pristinemethod for consistency. 2013-05-15 10:59:02 +02:00
Jannis Leidel
df865840bc Added a setup method hook for easier startup time code. 2013-05-15 10:57:30 +02:00
Jannis Leidel
efd0206da1 Fixed a typo. Thanks @peritus. 2013-05-13 10:18:38 +02:00
Jannis Leidel
6596e191f0 Added pristine decorator to handle callable settings. Fixes #28. 2013-04-30 17:11:29 +02:00
Jannis Leidel
e15070288c Renamed assertEquals to assertEqual. 2013-04-30 17:09:01 +02:00
Jannis Leidel
163aed8520 Normalized name of example settings class to Prod. 2013-04-30 17:07:11 +02:00
Jannis Leidel
c91b0d5a8d Bumped up the Django requirement a notch. 2013-04-26 09:03:47 +02:00
Jannis Leidel
35271f7200 Added a lambda setting, just to be sure. Refs #28. 2013-04-26 09:02:26 +02:00
Jannis Leidel
f51049dc00 Renamed a few test settings to better describe what they are. 2013-04-26 09:01:59 +02:00
Jannis Leidel
5700d96d14 Updated changes and bumped version up a notch. 2013-04-11 17:14:58 +02:00
Jannis Leidel
ad83d79397 Updated authors and license. 2013-04-11 17:14:40 +02:00
Jannis Leidel
bbd5f2dce3 Use Django's lax option parser when looking for the configuration option to prevent overlapping with Django's own option parsing.
Fixes #21, #22, #24. Thanks to @rolo for helping our in #24.
2013-04-11 16:08:47 +02:00
Jannis Leidel
40a6f0dc71 Added build dir to ignore list. 2013-04-11 16:04:12 +02:00
Jannis Leidel
0b5dada4e0 Made testproject manage.py executable to save my sanity. 2013-04-11 16:04:03 +02:00
Jannis Leidel
4c13891d4b Build universal wheels. Thanks for the hint, Daniel Holt. 2013-03-28 14:33:09 +01:00
Jannis Leidel
f08d743818 Create wheel by default. 2013-03-28 10:59:12 +01:00
Jannis Leidel
16728e8784 Fixed URL to travis. 2013-03-27 18:23:55 +01:00
Jannis Leidel
f089b1d936 Version fix. 2013-03-27 18:16:05 +01:00
Jannis Leidel
f7c1206a1e D'oh, there never was a 0.2 on PyPI. Bumped version down again. 2013-03-27 16:59:50 +01:00
Jannis Leidel
87f2b4723d Add a fastcgi helper. Fixes #19. 2013-03-27 16:57:05 +01:00
Jannis Leidel
caffc43b37 Better variant of the test project settings. 2013-03-27 16:56:28 +01:00
Jannis Leidel
ea4ba09af2 Mention a work around for Celery. Fixes #11. 2013-03-27 15:34:33 +01:00
Jannis Leidel
90beffa13b Bumped version up a notch. 2013-03-27 15:30:32 +01:00
Jannis Leidel
3dece53cda Add Makefile to sdist. 2013-03-27 15:30:14 +01:00
Jannis Leidel
5f69231de1 Use d2to1. 2013-03-27 15:30:04 +01:00
Jannis Leidel
669b578000 Updated changelog. 2013-03-27 15:29:54 +01:00
Jannis Leidel
16ae7f62b5 Use a matrix to prevent Python 3 and Django < 1.5 tests. 2013-03-27 12:19:11 +01:00
Jannis Leidel
b7d6faa46d Fixed exception syntax of a parallel branch. 2013-03-27 12:04:34 +01:00
Jannis Leidel
43e6a1a0ef Added six to requirements. 2013-03-27 12:00:21 +01:00
Jannis Leidel
aae33f759c Use newer versions of Django in Travis. 2013-03-27 12:00:13 +01:00
Jannis Leidel
a4741f93c7 Added the simple test project. 2013-03-27 11:59:59 +01:00
Jannis Leidel
f7696e5810 Merge pull request #18 from mvantellingen/python3
Python3
2013-03-27 03:58:03 -07:00
Jannis Leidel
560a8b2b84 Merge pull request #16 from bclermont/fix-import
catch error raised inside settings code
2013-03-27 03:57:05 -07:00
Jannis Leidel
5b90971f6d Merge pull request #17 from mfogel/explicit-mixin-test
Test using mixins with settings
2013-03-27 03:56:38 -07:00
Jannis Leidel
7db5db8bff Handle the --configuration option for management commands during installation of the import hook. 2013-03-27 11:55:43 +01:00
Michael van Tellingen
d654e56040 Set SECRET_KEY in settings.main 2013-01-30 21:48:15 +01:00
Michael van Tellingen
2877b66982 Initial fixes for python 3 support 2013-01-30 21:45:36 +01:00
Mike Fogel
0f83894494 Test using mixins with settings 2012-11-26 13:39:57 -08:00
Bruno Clermont
41b8f3655c fix indentation 2012-11-26 23:11:26 +08:00
Bruno Clermont
53bf1b5548 oups bad copy-paste 2012-11-26 16:29:59 +08:00
Bruno Clermont
1b052a3a0e catch error raised inside the settings code 2012-11-26 16:25:00 +08:00
Jannis Leidel
c5c0feff9f Bumped version. 2012-09-21 20:58:03 +02:00
Jannis Leidel
c616caf6d6 Added a changelog and include it both from the docs and from the setup.py. 2012-09-21 20:55:26 +02:00
Jannis Leidel
2e2d77f74f Added a note about the new command line option to the readme. 2012-09-21 20:54:58 +02:00
Jannis Leidel
876000c6bf Updated author list. 2012-09-21 20:54:35 +02:00
Jannis Leidel
08d36754f6 Merge branch 'master' of github.com:jezdez/django-configurations 2012-09-21 16:34:35 +02:00
Jannis Leidel
26bc0d692f Added a global --configuration option to management commands and show configuration class path when using runserver. Fixes #9. 2012-09-21 16:34:16 +02:00
Jannis Leidel
2d62d1ff84 phony make targets 2012-09-21 15:49:08 +02:00
Jannis Leidel
b314879e01 Use newer django requirements. 2012-09-21 15:44:52 +02:00
Jannis Leidel
eb3d8f2d82 Merge pull request #8 from bclermont/master
Don't catch AttributeError during cls init
2012-09-05 10:41:19 -07:00
Bruno Clermont
fb90a168aa use absolute import
fix test
2012-09-05 15:52:49 +02:00
Bruno Clermont
8b78278921 Don't catch AttributeError during cls init
if obj = cls() do have an attribute error inside it's initialization, it will appears as if the module could not be find.

which is hard to troubleshoot, when the module is really there :)
2012-09-05 14:29:05 +03:00
Jannis Leidel
a8917a7a87 Use 1.3.1 instead of plain 1.3. 2012-07-28 00:07:54 +02:00
Jannis Leidel
543151d511 Re-added 1.3 tests. Refs #4. 2012-07-28 00:05:49 +02:00
Jannis Leidel
5e9b066d21 Also use syntax highlighting for the shell examples. 2012-07-25 18:33:29 +02:00
Jannis Leidel
5581c472fc Merge pull request #5 from msabramo/patch-1
Minor tweaks to README.rst
2012-07-25 09:31:40 -07:00
Marc Abramowitz
41bf072eae Minor tweaks to README.rst
MySettings => MySiteSettings and added code blocks to take advantage of GitHub's new syntax highlighting of code blocks for RST files.
2012-07-25 09:25:26 -07:00
Jannis Leidel
40bb935484 Merge branch 'master' of github.com:jezdez/django-configurations 2012-07-21 21:43:50 +02:00
Jannis Leidel
df6c2edf59 Minor cosmetic doc changes. 2012-07-21 21:43:18 +02:00
Jannis Leidel
284e295022 Merge pull request #1 from gillesfabio/master
README 'DJANGO_CONFIGURATION'
2012-07-21 12:24:55 -07:00
Gilles Fabio
bb2c4de3bd Fixed README 'DJANGO_CONFIGURATION'. 2012-07-21 18:35:58 +02:00
Jannis Leidel
b9b40b32aa Added forgotten files (authors and license). 2012-07-21 17:52:05 +02:00
Jannis Leidel
e5995a0495 Fixed tests by removing the 1.3.X test target. Who uses that anyway? 2012-07-21 16:45:24 +02:00
Jannis Leidel
5c201fff1b Minor edit to trigger Travis. 2012-07-21 16:34:04 +02:00
Jannis Leidel
f03aa1012d Removed branch limitation. 2012-07-21 16:31:29 +02:00
69 changed files with 4124 additions and 1054 deletions

13
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,13 @@
# Keep GitHub Actions up to date with GitHub's Dependabot...
# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem
version: 2
updates:
- package-ecosystem: github-actions
directory: /
groups:
github-actions:
patterns:
- "*" # Group all Actions updates into a single larger pull request
schedule:
interval: weekly

53
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,53 @@
name: Release
on:
push:
tags:
- '*'
jobs:
build:
if: github.repository == 'jazzband/django-configurations'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.x
- name: Get pip cache dir
id: pip-cache
run: |
echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
- name: Cache
uses: actions/cache@v4
with:
path: ${{ steps.pip-cache.outputs.dir }}
key: release-${{ hashFiles('**/setup.py') }}
restore-keys: |
release-
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install --upgrade 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@release/v1
with:
user: jazzband
password: ${{ secrets.JAZZBAND_RELEASE_KEY }}
repository-url: https://jazzband.co/projects/django-configurations/upload

57
.github/workflows/test.yml vendored Normal file
View file

@ -0,0 +1,57 @@
name: Test
on:
pull_request:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
max-parallel: 6
matrix:
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', 'pypy-3.10']
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Get pip cache dir
id: pip-cache
run: |
echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
- name: Cache
uses: actions/cache@v4
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<4" "tox-gh-actions<3"
- name: Tox tests
run: |
tox --verbose
- name: Upload coverage
uses: codecov/codecov-action@v5
with:
name: coverage-data-${{ matrix.python-version }}
path: ".coverage.*"
include-hidden-files: true
merge-multiple: true
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}

8
.gitignore vendored
View file

@ -1,4 +1,12 @@
.coverage
coverage.xml
docs/_build
*.egg-info
*.egg
test.db
build/
.tox/
htmlcov/
*.pyc
dist/
.eggs/

1
.pre-commit-config.yaml Normal file
View file

@ -0,0 +1 @@
repos: []

16
.readthedocs.yaml Normal file
View file

@ -0,0 +1,16 @@
---
version: 2
build:
os: ubuntu-22.04
tools:
python: "3.10"
python:
install:
- requirements: docs/requirements.txt
- method: pip
path: .
sphinx:
configuration: docs/conf.py
formats:
- epub
- pdf

View file

@ -1,19 +0,0 @@
language: python
python:
- "2.5"
- "2.6"
- "2.7"
before_install:
- export PIP_USE_MIRRORS=true
- export PIP_INDEX_URL=https://simple.crate.io/
install:
- pip install -e .
- pip install -r requirements/tests.txt Django==$DJANGO
script:
- make test
env:
- DJANGO=1.3.1
- DJANGO=1.4
branches:
only:
- develop

22
AUTHORS Normal file
View file

@ -0,0 +1,22 @@
Arseny Sokolov
Asif Saif Uddin
Baptiste Mispelon
Brian Helba
Bruno Clermont
Christoph Krybus
Finn-Thorben Sell
Gilles Fabio
Jannis Leidel
John Franey
Marc Abramowitz
Michael Käufl
Michael van Tellingen
Mike Fogel
Miro Hrončok
Nicholas Dujay
Paolo Melchiorre
Peter Bittner
Richard de Wit
Thomas Grainger
Tim Gates
Victor Seva

46
CODE_OF_CONDUCT.md Normal file
View 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/

3
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,3 @@
[![Jazzband](https://jazzband.co/static/img/jazzband.svg)](https://jazzband.co/)
This is a [Jazzband](https://jazzband.co/) project. By contributing you agree to abide by the [Contributor Code of Conduct](https://jazzband.co/docs/conduct) and follow the [guidelines](https://jazzband.co/docs/guidelines).

27
LICENSE Normal file
View file

@ -0,0 +1,27 @@
Copyright (c) 2012-2023, Jannis Leidel and other contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of django-configurations nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -1,5 +1,11 @@
include .pre-commit-config.yaml
include .readthedocs.yaml
include AUTHORS
include CODE_OF_CONDUCT.md
include CONTRIBUTING.md
include LICENSE
include README.rst
include .travis.yml
include manage.py
include requirements/tests.txt
recursive-include docs *
include tox.ini
recursive-include docs *
recursive-include test_project *
recursive-include tests *

View file

@ -1,10 +0,0 @@
test:
flake8 configurations --ignore=E501,E127,E128,E124
coverage run --branch --source=configurations manage.py test configurations
coverage report --omit=configurations/test*
release:
python setup.py sdist register upload -s
doc:
cd docs; make html; cd ..

View file

@ -1,69 +1,128 @@
django-configurations
=====================
django-configurations |latest-version|
======================================
.. image:: https://secure.travis-ci.org/jezdez/django-configurations.png
:alt: Build Status
:target: https://secure.travis-ci.org/jezdez/django-configurations
|jazzband| |build-status| |codecov| |docs| |python-support| |django-support|
django-configurations eases Django project configuration by relying
on the composability of Python classes. It extends the notion of
Django's module based settings loading with well established
object oriented programming patterns.
Check out the `documentation`_ for more complete examples.
.. |latest-version| image:: https://img.shields.io/pypi/v/django-configurations.svg
:target: https://pypi.python.org/pypi/django-configurations
:alt: Latest version on PyPI
.. |jazzband| image:: https://jazzband.co/static/img/badge.svg
:target: https://jazzband.co/
:alt: Jazzband
.. |build-status| image:: https://github.com/jazzband/django-configurations/workflows/Test/badge.svg
:target: https://github.com/jazzband/django-configurations/actions
:alt: Build Status
.. |codecov| image:: https://codecov.io/github/jazzband/django-configurations/coverage.svg?branch=master
:target: https://codecov.io/github/jazzband/django-configurations?branch=master
:alt: Test coverage status
.. |docs| image:: https://img.shields.io/readthedocs/django-configurations/latest.svg
:target: https://readthedocs.org/projects/django-configurations/
:alt: Documentation status
.. |python-support| image:: https://img.shields.io/pypi/pyversions/django-configurations.svg
:target: https://pypi.python.org/pypi/django-configurations
:alt: Supported Python versions
.. |django-support| image:: https://img.shields.io/pypi/djversions/django-configurations
:target: https://pypi.org/project/django-configurations
:alt: Supported Django versions
.. _documentation: https://django-configurations.readthedocs.io/en/latest/
Quickstart
----------
Install django-configurations::
Install django-configurations:
pip install django-configurations
.. code-block:: console
Then subclass the included ``configurations.Settings`` class in your
project's ``settings.py`` or any other module you're using to store the
settings constants, e.g.::
$ python -m pip install django-configurations
from configurations import Settings
or, alternatively, if you want to use URL-based values:
class MySiteSettings(Settings):
.. code-block:: console
$ python -m pip install django-configurations[cache,database,email,search]
Then subclass the included ``configurations.Configuration`` class in your
project's **settings.py** or any other module you're using to store the
settings constants, e.g.:
.. code-block:: python
# mysite/settings.py
from configurations import Configuration
class Dev(Configuration):
DEBUG = True
Set the ``DJANGO_CONFIGURATION`` environment variable to the name of the class
you just created, e.g. in bash::
you just created, e.g. in bash:
export DJANGO_CONFIGURATION=MySettings
.. code-block:: console
$ export DJANGO_CONFIGURATION=Dev
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:
export DJANGO_SETTINGS_MODULE=mysite.settings
.. code-block:: console
$ export DJANGO_SETTINGS_MODULE=mysite.settings
*Alternatively* supply the ``--configuration`` option when using Django
management commands along the lines of Django's default ``--settings``
command line option, e.g.
.. code-block:: console
$ python -m manage 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
of the appropriate starter functions, e.g. a typical ``manage.py`` using
django-configurations would look like this::
**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:
.. code-block:: python
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
os.environ.setdefault('DJANGO_', 'MySettings')
os.environ.setdefault('DJANGO_CONFIGURATION', 'Dev')
from configurations.management import execute_from_command_line
execute_from_command_line(sys.argv)
Notice in line 9 we don't use the common tool
Notice in line 10 we don't use the common tool
``django.core.management.execute_from_command_line`` but instead
``configurations.management.execute_from_command_line``.
The same applies to your ``wsgi.py`` file, e.g.::
The same applies to your **wsgi.py** file, e.g.:
.. code-block:: python
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
os.environ.setdefault('DJANGO_CONFIGURATION', 'MySettings')
os.environ.setdefault('DJANGO_CONFIGURATION', 'Dev')
from configurations.wsgi import get_wsgi_application
application = get_wsgi_application()
@ -71,5 +130,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.

View file

@ -1,5 +1,31 @@
# flake8: noqa
from .base import Settings
from .base import Configuration # noqa
from .decorators import pristinemethod # noqa
from .version import __version__ # noqa
__version__ = '0.1'
__all__ = ['Settings']
__all__ = ['Configuration', 'pristinemethod']
def _setup():
from . import importer
importer.install()
from django.apps import apps
if not apps.ready:
import django
django.setup()
def load_ipython_extension(ipython):
"""
The `ipython` argument is the currently active `InteractiveShell`
instance, which can be used in any way. This allows you to register
new magics or aliases, for example.
"""
_setup()
def setup(app=None):
"""Function used to initialize configurations similar to :func:`.django.setup`."""
_setup()

View file

@ -0,0 +1,10 @@
"""
invokes django-cadmin when the configurations module is run as a script.
Example: python -m configurations check
"""
from .management import execute_from_command_line
if __name__ == "__main__":
execute_from_command_line()

8
configurations/asgi.py Normal file
View 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()

View file

@ -1,45 +1,86 @@
import os
import re
from django.conf import global_settings
from django.core.exceptions import ImproperlyConfigured
from .utils import uppercase_attributes
from .values import Value, setup_value
__all__ = ['Settings']
__all__ = ['Configuration']
install_failure = ("django-configurations settings importer wasn't "
"correctly installed. Please use one of the starter "
"functions to install it as mentioned in the docs: "
"http://django-configurations.readthedocs.org/")
"https://django-configurations.readthedocs.io/")
class SettingsBase(type):
class ConfigurationBase(type):
def __new__(cls, name, bases, attrs):
if bases != (object,):
if bases not in ((object,), ()) and bases[0].__name__ != 'NewBase':
# if this is actually a subclass in a settings module
# we better check if the importer was correctly installed
from . import importer
if not importer.installed:
raise ImproperlyConfigured(install_failure)
settings_vars = uppercase_attributes(global_settings)
parents = [base for base in bases if isinstance(base, SettingsBase)]
parents = [base for base in bases if isinstance(base,
ConfigurationBase)]
if parents:
for base in bases[::-1]:
settings_vars.update(uppercase_attributes(base))
attrs = dict(settings_vars, **attrs)
return super(SettingsBase, cls).__new__(cls, name, bases, attrs)
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",
# FORMS_URLFIELD_ASSUME_HTTPS is a transitional setting introduced
# in Django 5.0.
# https://docs.djangoproject.com/en/5.0/releases/5.0/#id2
"FORMS_URLFIELD_ASSUME_HTTPS"
}
# PASSWORD_RESET_TIMEOUT_DAYS is deprecated in favor of
# PASSWORD_RESET_TIMEOUT in Django 3.1
# https://github.com/django/django/commit/226ebb17290b604ef29e82fb5c1fbac3594ac163#diff-ec2bed07bb264cb95a80f08d71a47c06R163-R170
if "PASSWORD_RESET_TIMEOUT" in settings_vars:
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:
if deprecated_setting in settings_vars:
del settings_vars[deprecated_setting]
attrs = {**settings_vars, **attrs}
return super().__new__(cls, name, bases, attrs)
def __repr__(self):
return "<Settings '%s.%s'>" % (self.__module__, self.__name__)
return "<Configuration '{}.{}'>".format(self.__module__,
self.__name__)
class Settings(object):
class Configuration(metaclass=ConfigurationBase):
"""
The base configuration class to inherit from.
::
class Develop(Settings):
class Develop(Configuration):
EXTRA_AWESOME = True
@property
@ -57,4 +98,60 @@ class Settings(object):
to the name of the class.
"""
__metaclass__ = SettingsBase
DOTENV_LOADED = None
@classmethod
def load_dotenv(cls):
"""
Pulled from Honcho code with minor updates, reads local default
environment variables from a .env file located in the project root
or provided directory.
https://wellfire.co/learn/easier-12-factor-django/
https://gist.github.com/bennylope/2999704
"""
# check if the class has DOTENV set whether with a path or None
dotenv = getattr(cls, 'DOTENV', None)
# if DOTENV is falsy we want to disable it
if not dotenv:
return
# now check if we can access the file since we know we really want to
try:
with open(dotenv) as f:
content = f.read()
except OSError as e:
raise ImproperlyConfigured("Couldn't read .env file "
"with the path {}. Error: "
"{}".format(dotenv, e)) from e
else:
for line in content.splitlines():
m1 = re.match(r'\A([A-Za-z_0-9]+)=(.*)\Z', line)
if not m1:
continue
key, val = m1.group(1), m1.group(2)
m2 = re.match(r"\A'(.*)'\Z", val)
if m2:
val = m2.group(1)
m3 = re.match(r'\A"(.*)"\Z', val)
if m3:
val = re.sub(r'\\(.)', r'\1', m3.group(1))
os.environ.setdefault(key, val)
cls.DOTENV_LOADED = dotenv
@classmethod
def pre_setup(cls):
if cls.DOTENV_LOADED is None:
cls.load_dotenv()
@classmethod
def post_setup(cls):
pass
@classmethod
def setup(cls):
for name, value in uppercase_attributes(cls).items():
if isinstance(value, Value):
setup_value(cls, name, value)

View file

@ -0,0 +1,19 @@
def pristinemethod(func):
"""
A decorator for handling pristine settings like callables.
Use it like this::
from configurations import Configuration, pristinemethod
class Develop(Configuration):
@pristinemethod
def USER_CHECK(user):
return user.check_perms()
GROUP_CHECK = pristinemethod(lambda user: user.has_group_access())
"""
func.pristine = True
return staticmethod(func)

View file

@ -0,0 +1,5 @@
from . import importer
importer.install()
from django.core.servers.fastcgi import runfastcgi # noqa

View file

@ -1,75 +1,180 @@
import imp
from importlib.machinery import PathFinder
import logging
import os
import sys
from optparse import OptionParser, make_option
from django.conf import ENVIRONMENT_VARIABLE as SETTINGS_ENVIRONMENT_VARIABLE
from django.core.exceptions import ImproperlyConfigured
from django.conf import ENVIRONMENT_VARIABLE
from .utils import uppercase_attributes
from django.core.management import base
from .utils import uppercase_attributes, reraise
from .values import Value, setup_value
installed = False
CONFIGURATION_ENVIRONMENT_VARIABLE = 'DJANGO_CONFIGURATION'
CONFIGURATION_ARGUMENT = '--configuration'
CONFIGURATION_ARGUMENT_HELP = ('The name of the configuration class to load, '
'e.g. "Development". If this isn\'t provided, '
'the DJANGO_CONFIGURATION environment '
'variable will be used.')
def install():
configuration_options = (make_option(CONFIGURATION_ARGUMENT,
help=CONFIGURATION_ARGUMENT_HELP),)
def install(check_options=False):
global installed
if not installed:
sys.meta_path.append(SettingsImporter())
orig_create_parser = base.BaseCommand.create_parser
def create_parser(self, prog_name, subcommand):
parser = orig_create_parser(self, prog_name, subcommand)
if isinstance(parser, OptionParser):
# in case the option_list is set the create_parser
# will actually return a OptionParser for backward
# compatibility. In that case we should tack our
# options on to the end of the parser on the way out.
for option in configuration_options:
parser.add_option(option)
else:
# probably argparse, let's not import argparse though
parser.add_argument(CONFIGURATION_ARGUMENT,
help=CONFIGURATION_ARGUMENT_HELP)
return parser
base.BaseCommand.create_parser = create_parser
importer = ConfigurationFinder(check_options=check_options)
sys.meta_path.insert(0, importer)
installed = True
class SettingsImporter(object):
class_varname = 'DJANGO_CONFIGURATION'
error_msg = "Settings cannot be imported, environment variable %s is undefined."
class ConfigurationFinder(PathFinder):
modvar = SETTINGS_ENVIRONMENT_VARIABLE
namevar = CONFIGURATION_ENVIRONMENT_VARIABLE
error_msg = ("Configuration cannot be imported, "
"environment variable {0} is undefined.")
def __init__(self):
def __init__(self, check_options=False):
self.argv = sys.argv[:]
self.logger = logging.getLogger(__name__)
self.logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
self.logger.addHandler(handler)
if check_options:
self.check_options()
self.validate()
if check_options:
self.announce()
def __repr__(self):
return "<SettingsImporter for '%s.%s'>" % (self.module, self.name)
return "<ConfigurationFinder for '{}.{}'>".format(self.module,
self.name)
@property
def module(self):
return os.environ.get(ENVIRONMENT_VARIABLE)
return os.environ.get(self.modvar)
@property
def name(self):
return os.environ.get(self.class_varname)
return os.environ.get(self.namevar)
def check_options(self):
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,
help=CONFIGURATION_ARGUMENT_HELP)
parser.add_argument('args', nargs='*') # catch-all
try:
options, args = parser.parse_known_args(self.argv[2:])
if options.configuration:
os.environ[self.namevar] = options.configuration
base.handle_default_options(options)
except base.CommandError:
pass # Ignore any option errors at this point.
def validate(self):
if self.name is None:
raise ImproperlyConfigured(self.error_msg % self.class_varname)
raise ImproperlyConfigured(self.error_msg.format(self.namevar))
if self.module is None:
raise ImproperlyConfigured(self.error_msg % ENVIRONMENT_VARIABLE)
raise ImproperlyConfigured(self.error_msg.format(self.modvar))
def find_module(self, fullname, path=None):
def announce(self):
if len(self.argv) > 1:
from . import __version__
from django.utils.termcolors import colorize
from django.core.management.color import no_style
if '--no-color' in self.argv:
stylize = no_style()
else:
def stylize(text):
return colorize(text, fg='green')
if (self.argv[1] == 'runserver'
and os.environ.get('RUN_MAIN') == 'true'):
message = ("django-configurations version {}, using "
"configuration {}".format(__version__ or "",
self.name))
self.logger.debug(stylize(message))
def find_spec(self, fullname, path=None, target=None):
if fullname is not None and fullname == self.module:
module = fullname.rsplit('.', 1)[-1]
return SettingsLoader(self.name, imp.find_module(module, path))
return None
class SettingsLoader(object):
def __init__(self, name, location):
self.name = name
self.location = location
def load_module(self, fullname):
if fullname in sys.modules:
mod = sys.modules[fullname] # pragma: no cover
spec = super().find_spec(fullname, path, target)
if spec is not None:
wrap_loader(spec.loader, self.name)
return spec
else:
mod = imp.load_module(fullname, *self.location)
try:
cls = getattr(mod, self.name)
obj = cls()
except AttributeError: # pragma: no cover
raise ImproperlyConfigured("Couldn't find settings '%s' in "
"module '%s'" %
(self.name, mod.__package__))
for name, value in uppercase_attributes(obj).items():
if callable(value):
value = value()
setattr(mod, name, value)
setattr(mod, 'CONFIGURATION', '%s.%s' % (fullname, self.name))
return mod
return None
def wrap_loader(loader, class_name):
class ConfigurationLoader(loader.__class__):
def exec_module(self, module):
super().exec_module(module)
mod = module
cls_path = f'{mod.__name__}.{class_name}'
try:
cls = getattr(mod, class_name)
except AttributeError as err: # pragma: no cover
reraise(
err,
(
f"Couldn't find configuration '{class_name}' in "
f"module '{mod.__package__}'"
),
)
try:
cls.pre_setup()
cls.setup()
obj = cls()
attributes = uppercase_attributes(obj).items()
for name, value in attributes:
if callable(value) and not getattr(value, 'pristine', False):
value = value()
# in case a method returns a Value instance we have
# to do the same as the Configuration.setup method
if isinstance(value, Value):
setup_value(mod, name, value)
continue
setattr(mod, name, value)
setattr(mod, 'CONFIGURATION', '{0}.{1}'.format(module.__name__,
class_name))
cls.post_setup()
except Exception as err:
reraise(err, f"Couldn't setup configuration '{cls_path}'")
loader.__class__ = ConfigurationLoader

View file

@ -1,5 +1,6 @@
from . import importer
importer.install()
importer.install(check_options=True)
from django.core.management import execute_from_command_line # noqa
from django.core.management import (execute_from_command_line, # noqa
call_command)

12
configurations/sphinx.py Normal file
View file

@ -0,0 +1,12 @@
from . import _setup, __version__
def setup(app=None):
"""
The callback for Sphinx that acts as a Sphinx extension.
Add ``'configurations'`` to the ``extensions`` config variable
in your docs' ``conf.py``.
"""
_setup()
return {'version': __version__, 'parallel_read_safe': True}

View file

@ -1,9 +0,0 @@
from configurations import Settings
def test_callback(request):
return {}
class Base(Settings):
pass

View file

@ -1,43 +0,0 @@
import os
from configurations import Settings
class Test(Settings):
SITE_ID = 1
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(os.path.dirname(__file__), 'test.db'),
}
}
INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.contenttypes',
'django.contrib.sites',
'django.contrib.auth',
'django.contrib.admin',
'configurations.tests',
]
ROOT_URLCONF = 'configurations.tests.urls'
TEST_RUNNER = 'discover_runner.DiscoverRunner'
TEST_SETTING = True
_SOMETHING = 'YEAH'
DEBUG = True
@property
def LALA(self):
return 1
def LALA2(self):
return 1
def TEMPLATE_CONTEXT_PROCESSORS(self):
return Settings.TEMPLATE_CONTEXT_PROCESSORS + (
'configurations.tests.settings.base.test_callback',)

View file

@ -1,8 +0,0 @@
from .main import Test
class Inheritance(Test):
def TEMPLATE_CONTEXT_PROCESSORS(self):
return super(Inheritance, self).TEMPLATE_CONTEXT_PROCESSORS() + (
'configurations.tests.settings.base.test_callback',)

View file

@ -1,8 +0,0 @@
from .base import Base
class Inheritance(Base):
def TEMPLATE_CONTEXT_PROCESSORS(self):
return super(Inheritance, self).TEMPLATE_CONTEXT_PROCESSORS + (
'configurations.tests.settings.base.test_callback',)

View file

@ -1,41 +0,0 @@
import os
from django.test import TestCase
from mock import patch
class InheritanceTests(TestCase):
@patch.dict(os.environ, clear=True,
DJANGO_CONFIGURATION='Inheritance',
DJANGO_SETTINGS_MODULE='configurations.tests.settings.single_inheritance')
def test_inherited(self):
from configurations.tests.settings import single_inheritance
self.assertEquals(single_inheritance.TEMPLATE_CONTEXT_PROCESSORS, (
'django.contrib.auth.context_processors.auth',
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'django.core.context_processors.media',
'django.core.context_processors.static',
'django.core.context_processors.tz',
'django.contrib.messages.context_processors.messages',
'configurations.tests.settings.base.test_callback',
))
@patch.dict(os.environ, clear=True,
DJANGO_CONFIGURATION='Inheritance',
DJANGO_SETTINGS_MODULE='configurations.tests.settings.multiple_inheritance')
def test_inherited2(self):
from configurations.tests.settings import multiple_inheritance
self.assertEquals(multiple_inheritance.TEMPLATE_CONTEXT_PROCESSORS, (
'django.contrib.auth.context_processors.auth',
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'django.core.context_processors.media',
'django.core.context_processors.static',
'django.core.context_processors.tz',
'django.contrib.messages.context_processors.messages',
'configurations.tests.settings.base.test_callback',
'configurations.tests.settings.base.test_callback',
))

View file

@ -1,70 +0,0 @@
import os
from django.test import TestCase
from django.core.exceptions import ImproperlyConfigured
from mock import patch
from ..importer import SettingsImporter
class MainTests(TestCase):
def test_simple(self):
from configurations.tests.settings import main
self.assertEquals(main.LALA, 1)
self.assertEquals(main.TEMPLATE_CONTEXT_PROCESSORS, (
'django.contrib.auth.context_processors.auth',
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'django.core.context_processors.media',
'django.core.context_processors.static',
'django.core.context_processors.tz',
'django.contrib.messages.context_processors.messages',
'configurations.tests.settings.base.test_callback',
))
self.assertEquals(main.TEST_SETTING, True)
def test_global_arrival(self):
from django.conf import settings
self.assertEquals(settings.LALA, 1)
self.assertRaises(AttributeError, lambda: settings._SOMETHING)
@patch.dict(os.environ, clear=True, DJANGO_CONFIGURATION='Test')
def test_empty_module_var(self):
self.assertRaises(ImproperlyConfigured, SettingsImporter)
@patch.dict(os.environ, clear=True,
DJANGO_SETTINGS_MODULE='configurations.tests.settings.main')
def test_empty_class_var(self):
self.assertRaises(ImproperlyConfigured, SettingsImporter)
def test_global_settings(self):
from configurations.base import Settings
self.assertEquals(Settings.LOGGING_CONFIG, 'django.utils.log.dictConfig')
self.assertEquals(repr(Settings),
"<Settings 'configurations.base.Settings'>")
def test_repr(self):
from configurations.tests.settings.main import Test
self.assertEquals(repr(Test),
"<Settings 'configurations.tests.settings.main.Test'>")
@patch.dict(os.environ, clear=True,
DJANGO_SETTINGS_MODULE='configurations.tests.settings.main',
DJANGO_CONFIGURATION='Test')
def test_initialization(self):
importer = SettingsImporter()
self.assertEquals(importer.module, 'configurations.tests.settings.main')
self.assertEquals(importer.name, 'Test')
self.assertEquals(repr(importer),
"<SettingsImporter for 'configurations.tests.settings.main.Test'>")
@patch.dict(os.environ, clear=True,
DJANGO_SETTINGS_MODULE='configurations.tests.settings.inheritance',
DJANGO_CONFIGURATION='Inheritance')
def test_initialization_inheritance(self):
importer = SettingsImporter()
self.assertEquals(importer.module,
'configurations.tests.settings.inheritance')
self.assertEquals(importer.name, 'Inheritance')

View file

@ -1,9 +0,0 @@
from django.conf.urls.defaults import include, patterns
# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
(r'^admin/', include(admin.site.urls)),
)

View file

@ -1,7 +1,101 @@
import inspect
import sys
import warnings
from functools import partial
from importlib import import_module
from django.core.exceptions import ImproperlyConfigured
def isuppercase(name):
return name == name.upper() and not name.startswith('_')
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=''):
"""
Import a dotted module path and return the attribute/class designated by
the last name in the path. Raise ImproperlyConfigured if something goes
wrong.
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:
raise ImproperlyConfigured("{}{} doesn't look like "
"a module path".format(error_prefix,
dotted_path))
try:
module = import_module(module_path)
except ImportError as err:
msg = '{}Error importing module {}: "{}"'.format(error_prefix,
module_path,
err)
raise ImproperlyConfigured(msg).with_traceback(sys.exc_info()[2])
try:
attr = getattr(module, class_name)
except AttributeError:
raise ImproperlyConfigured('{}Module "{}" does not define a '
'"{}" attribute/class'.format(error_prefix,
module_path,
class_name))
return attr
def reraise(exc, prefix=None, suffix=None):
args = exc.args
if not args:
args = ('',)
if prefix is None:
prefix = ''
elif not prefix.endswith((':', ': ')):
prefix = prefix + ': '
if suffix is None:
suffix = ''
elif not (suffix.startswith('(') and suffix.endswith(')')):
suffix = '(' + suffix + ')'
exc.args = (f'{prefix} {args[0]} {suffix}',) + args[1:]
raise exc
# Copied over from Sphinx
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)

474
configurations/values.py Normal file
View file

@ -0,0 +1,474 @@
import ast
import copy
import decimal
import os
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 getargspec
def setup_value(target, name, value):
actual_value = value.setup(name)
# overwriting the original Value class with the result
setattr(target, name, value.value)
if value.multiple:
for multiple_name, multiple_value in actual_value.items():
setattr(target, multiple_name, multiple_value)
class Value:
"""
A single settings value that is able to interpret env variables
and implements a simple validation scheme.
"""
multiple = False
late_binding = False
environ_required = False
@property
def value(self):
value = self.default
if not hasattr(self, '_value') and self.environ_name:
self.setup(self.environ_name)
if hasattr(self, '_value'):
value = self._value
return value
@value.setter
def value(self, value):
self._value = value
def __new__(cls, *args, **kwargs):
"""
checks if the creation can end up directly in the final value.
That is the case whenever environ = False or environ_name is given.
"""
instance = object.__new__(cls)
if 'late_binding' in kwargs:
instance.late_binding = kwargs.get('late_binding')
if not instance.late_binding:
instance.__init__(*args, **kwargs)
if ((instance.environ and instance.environ_name)
or (not instance.environ and instance.default)):
instance = instance.setup(instance.environ_name)
return instance
def __init__(self, default=None, environ=True, environ_name=None,
environ_prefix='DJANGO', environ_required=False,
*args, **kwargs):
if isinstance(default, Value) and default.default is not None:
self.default = copy.copy(default.default)
else:
self.default = default
self.environ = environ
if environ_prefix and environ_prefix.endswith('_'):
environ_prefix = environ_prefix[:-1]
self.environ_prefix = environ_prefix
self.environ_name = environ_name
self.environ_required = environ_required
def __str__(self):
return str(self.value)
def __repr__(self):
return repr(self.value)
def __eq__(self, other):
return self.value == other
def __bool__(self):
return bool(self.value)
# Compatibility with python 2
__nonzero__ = __bool__
def full_environ_name(self, name):
if self.environ_name:
environ_name = self.environ_name
else:
environ_name = name.upper()
if self.environ_prefix:
environ_name = f'{self.environ_prefix}_{environ_name}'
return environ_name
def setup(self, name):
value = self.default
if self.environ:
full_environ_name = self.full_environ_name(name)
if full_environ_name in os.environ:
value = self.to_python(os.environ[full_environ_name])
elif self.environ_required:
raise ValueError('Value {!r} is required to be set as the '
'environment variable {!r}'
.format(name, full_environ_name))
self.value = value
return value
def to_python(self, value):
"""
Convert the given value of a environment variable into an
appropriate Python representation of the value.
This should be overridden when subclassing.
"""
return value
class MultipleMixin:
multiple = True
class BooleanValue(Value):
true_values = ('yes', 'y', 'true', '1')
false_values = ('no', 'n', 'false', '0', '')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.default not in (True, False):
raise ValueError('Default value {!r} is not a '
'boolean value'.format(self.default))
def to_python(self, value):
normalized_value = value.strip().lower()
if normalized_value in self.true_values:
return True
elif normalized_value in self.false_values:
return False
else:
raise ValueError('Cannot interpret '
'boolean value {!r}'.format(value))
class CastingMixin:
exception = (TypeError, ValueError)
message = 'Cannot interpret value {0!r}'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if isinstance(self.caster, str):
try:
self._caster = import_string(self.caster)
except ImportError as err:
msg = f"Could not import {self.caster!r}"
raise ImproperlyConfigured(msg) from err
elif callable(self.caster):
self._caster = self.caster
else:
error = 'Cannot use caster of {} ({!r})'.format(self,
self.caster)
raise ValueError(error)
try:
arg_names = getargspec(self._caster)[0]
self._params = {name: kwargs[name] for name in arg_names if name in kwargs}
except TypeError:
self._params = {}
def to_python(self, value):
try:
if self._params:
return self._caster(value, **self._params)
else:
return self._caster(value)
except self.exception:
raise ValueError(self.message.format(value))
class IntegerValue(CastingMixin, Value):
caster = int
class PositiveIntegerValue(IntegerValue):
def to_python(self, value):
int_value = super().to_python(value)
if int_value < 0:
raise ValueError(self.message.format(value))
return int_value
class FloatValue(CastingMixin, Value):
caster = float
class DecimalValue(CastingMixin, Value):
caster = decimal.Decimal
exception = decimal.InvalidOperation
class SequenceValue(Value):
"""
Common code for sequence-type values (lists and tuples).
Do not use this class directly. Instead use a subclass.
"""
# Specify this value in subclasses, e.g. with 'list' or 'tuple'
sequence_type = None
converter = None
def __init__(self, *args, **kwargs):
msg = 'Cannot interpret {0} item {{0!r}} in {0} {{1!r}}'
self.message = msg.format(self.sequence_type.__name__)
self.separator = kwargs.pop('separator', ',')
converter = kwargs.pop('converter', None)
if converter is not None:
self.converter = converter
super().__init__(*args, **kwargs)
# make sure the default is the correct sequence type
if self.default is None:
self.default = self.sequence_type()
else:
self.default = self.sequence_type(self.default)
# initial conversion
if self.converter is not None:
self.default = self._convert(self.default)
def _convert(self, sequence):
converted_values = []
for value in sequence:
try:
converted_values.append(self.converter(value))
except (TypeError, ValueError):
raise ValueError(self.message.format(value, value))
return self.sequence_type(converted_values)
def to_python(self, value):
split_value = [v.strip() for v in value.strip().split(self.separator)]
# removing empty items
value_list = self.sequence_type(filter(None, split_value))
if self.converter is not None:
value_list = self._convert(value_list)
return self.sequence_type(value_list)
class ListValue(SequenceValue):
sequence_type = list
class TupleValue(SequenceValue):
sequence_type = tuple
class SingleNestedSequenceValue(SequenceValue):
"""
Common code for nested sequences (list of lists, or tuple of tuples).
Do not use this class directly. Instead use a subclass.
"""
def __init__(self, *args, **kwargs):
self.seq_separator = kwargs.pop('seq_separator', ';')
super().__init__(*args, **kwargs)
def _convert(self, items):
# This could receive either a bare or nested sequence
if items and isinstance(items[0], self.sequence_type):
converted_sequences = [
super(SingleNestedSequenceValue, self)._convert(i) for i in items
]
return self.sequence_type(converted_sequences)
return self.sequence_type(super()._convert(items))
def to_python(self, value):
split_value = [
v.strip() for v in value.strip().split(self.seq_separator)
]
# Remove empty items
filtered = self.sequence_type(filter(None, split_value))
sequence = [
super(SingleNestedSequenceValue, self).to_python(f) for f in filtered
]
return self.sequence_type(sequence)
class SingleNestedListValue(SingleNestedSequenceValue):
sequence_type = list
class SingleNestedTupleValue(SingleNestedSequenceValue):
sequence_type = tuple
class BackendsValue(ListValue):
def converter(self, value):
try:
import_string(value)
except ImportError as err:
raise ValueError(err).with_traceback(sys.exc_info()[2])
return value
class SetValue(ListValue):
message = 'Cannot interpret set item {0!r} in set {1!r}'
def __init__(self, *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().to_python(value))
class DictValue(Value):
message = 'Cannot interpret dict value {0!r}'
def __init__(self, *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().to_python(value)
if not value:
return {}
try:
evaled_value = ast.literal_eval(value)
except ValueError:
raise ValueError(self.message.format(value))
if not isinstance(evaled_value, dict):
raise ValueError(self.message.format(value))
return evaled_value
class ValidationMixin:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if isinstance(self.validator, str):
try:
self._validator = import_string(self.validator)
except ImportError as err:
msg = f"Could not import {self.validator!r}"
raise ImproperlyConfigured(msg) from err
elif callable(self.validator):
self._validator = self.validator
else:
raise ValueError('Cannot use validator of '
'{} ({!r})'.format(self, self.validator))
if self.default:
self.to_python(self.default)
def to_python(self, value):
try:
self._validator(value)
except ValidationError:
raise ValueError(self.message.format(value))
else:
return value
class EmailValue(ValidationMixin, Value):
message = 'Cannot interpret email value {0!r}'
validator = 'django.core.validators.validate_email'
class URLValue(ValidationMixin, Value):
message = 'Cannot interpret URL value {0!r}'
validator = validators.URLValidator()
class IPValue(ValidationMixin, Value):
message = 'Cannot interpret IP value {0!r}'
validator = 'django.core.validators.validate_ipv46_address'
class RegexValue(ValidationMixin, Value):
message = "Regex doesn't match value {0!r}"
def __init__(self, *args, **kwargs):
regex = kwargs.pop('regex', None)
self.validator = validators.RegexValidator(regex=regex)
super().__init__(*args, **kwargs)
class PathValue(Value):
def __init__(self, *args, **kwargs):
self.check_exists = kwargs.pop('check_exists', True)
super().__init__(*args, **kwargs)
def setup(self, name):
value = super().setup(name)
value = os.path.expanduser(value)
if self.check_exists and not os.path.exists(value):
raise ValueError(f'Path {value!r} does not exist.')
return os.path.abspath(value)
class SecretValue(Value):
def __init__(self, *args, **kwargs):
kwargs['environ'] = True
kwargs['environ_required'] = True
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().setup(name)
if not value:
raise ValueError(f'Secret value {name!r} is not set')
return value
class EmailURLValue(CastingMixin, MultipleMixin, Value):
caster = 'dj_email_url.parse'
message = 'Cannot interpret email URL value {0!r}'
late_binding = True
def __init__(self, *args, **kwargs):
kwargs.setdefault('environ', True)
kwargs.setdefault('environ_prefix', None)
kwargs.setdefault('environ_name', 'EMAIL_URL')
super().__init__(*args, **kwargs)
if self.default is None:
self.default = {}
else:
self.default = self.to_python(self.default)
class DictBackendMixin(Value):
default_alias = 'default'
def __init__(self, *args, **kwargs):
self.alias = kwargs.pop('alias', self.default_alias)
kwargs.setdefault('environ', True)
kwargs.setdefault('environ_prefix', None)
kwargs.setdefault('environ_name', self.environ_name)
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().to_python(value)
return {self.alias: value}
class DatabaseURLValue(DictBackendMixin, CastingMixin, Value):
caster = 'dj_database_url.parse'
message = 'Cannot interpret database URL value {0!r}'
environ_name = 'DATABASE_URL'
late_binding = True
class CacheURLValue(DictBackendMixin, CastingMixin, Value):
caster = 'django_cache_url.parse'
message = 'Cannot interpret cache URL value {0!r}'
environ_name = 'CACHE_URL'
late_binding = True
class SearchURLValue(DictBackendMixin, CastingMixin, Value):
caster = 'dj_search_url.parse'
message = 'Cannot interpret Search URL value {0!r}'
environ_name = 'SEARCH_URL'
late_binding = True

View file

@ -0,0 +1,7 @@
from importlib.metadata import PackageNotFoundError, version
try:
__version__ = version("django-configurations")
except PackageNotFoundError:
# package is not installed
__version__ = None

View file

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

View file

@ -1,153 +0,0 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-configurations.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-configurations.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/django-configurations"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-configurations"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."

292
docs/changes.rst Normal file
View file

@ -0,0 +1,292 @@
.. :changelog:
Changelog
---------
Unreleased
^^^^^^^^^^
- Prevent warning about ``FORMS_URLFIELD_ASSUME_HTTPS`` on Django 5.0.
v2.5.1 (2023-11-30)
^^^^^^^^^^^^^^^^^^^
- Add compatibility with Python 3.12
v2.5 (2023-10-20)
^^^^^^^^^^^^^^^^^
- Update Github actions and fix pipeline warnings
- Add compatibility with Django 5.0
- **BACKWARD INCOMPATIBLE** Drop compatibility for Django 4.0
- **BACKWARD INCOMPATIBLE** Drop compatibility for Python 3.7 and PyPy < 3.10
v2.4.2 (2023-09-27)
^^^^^^^^^^^^^^^^^^^
- Replace imp (due for removal in Python 3.12) with importlib
- Test on PyPy 3.10.
v2.4.1 (2023-04-04)
^^^^^^^^^^^^^^^^^^^
- 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)
^^^^^^^^^^^^^^^^^
- Add compatibility with Django 4.1
- **BACKWARD INCOMPATIBLE** Drop compatibility for Django < 3.2
- **BACKWARD INCOMPATIBLE** Drop compatibility for Python 3.6
v2.3.2 (2022-01-25)
^^^^^^^^^^^^^^^^^^^
- Add compatibility with Django 4.0
- Fix regression where settings receiving a default were ignored. #323 #327
v2.3.1 (2021-11-08)
^^^^^^^^^^^^^^^^^^^
- Test Django 3.2 on Python 3.10 as well.
- Test on PyPy 3.6, 3.7 and 3.8.
- Enforce Python version requirement during installation (>=3.6).
- Fix and refactor the documentation build process.
v2.3 (2021-10-27)
^^^^^^^^^^^^^^^^^
- **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 and 3.10.
- Deprecate ``utils.import_by_path`` in favor of
``django.utils.module_loading.import_string``.
- Add ASGI support.
- Added "python -m configurations" entry point.
- Make package ``install_requires`` include ``django>=2.2``.
- Prevent an ImproperlyConfigured warning from ``DEFAULT_HASHING_ALGORITHM``.
- Prevent warnings for settings deprecated in Django 2.2
(``DEFAULT_CONTENT_TYPE`` and ``FILE_CHARSET``).
- Preserve Django warnings when ``DEFAULT_AUTO_FIELD`` is not set.
- Miscellaneous documentation fixes.
- Miscellaneous internal improvements.
v2.2 (2019-12-03)
^^^^^^^^^^^^^^^^^
- **BACKWARD INCOMPATIBLE** Drop support for Python 3.4.
- **BACKWARD INCOMPATIBLE** Drop support for Django < 1.11.
- Add support for Django 3.0.
- Add support for Python 3.8.
- Add support for PyPy 3.
- Replace ``django.utils.six`` with ``six`` to support Django >= 3.
- Start using tox-travis and setuptools-scm for simplified test harness
and release management.
v2.1 (2018-08-16)
^^^^^^^^^^^^^^^^^
- **BACKWARD INCOMPATIBLE** Drop support of Python 3.3.
- **BACKWARD INCOMPATIBLE** Drop support of Django 1.9.
- Add support for Django 2.1.
- Add ``PositiveIntegerValue`` configuration value.
- Fix ``bool(BooleanValue)`` to behave as one would expect (e.g.
``bool(BooleanValue(False))`` returns ``False``).
- Miscellaneous documentation improvements and bug fixes.
v2.0 (2016-07-29)
^^^^^^^^^^^^^^^^^
- **BACKWARD INCOMPATIBLE** Drop support of Python 2.6 and 3.2
- **BACKWARD INCOMPATIBLE** Drop support of Django < 1.8
- **BACKWARD INCOMPATIBLE** Moved sphinx callable has been moved from
``configurations`` to ``configurations.sphinx``.
- **BACKWARD INCOMPATIBLE** Removed the previously deprecated
``configurations.Settings`` class in favor of the
``configurations.Configuration`` added in 0.4. This removal was planned for
the 1.0 release and is now finally enacted.
- Add multiprocessing support for sphinx integration
- Fix a RemovedInDjango19Warning warning
v1.0 (2016-01-04)
^^^^^^^^^^^^^^^^^
- Project has moved to `Jazzband <https://jazzband.co/>`_. See guidelines for
contributing.
- Support for Django 1.8 and above.
- Allow ``Value`` classes to be used outside of ``Configuration`` classes. (#62)
- Fixed "Value with ValidationMixin will raise ValueError if no default assigned". (#69)
- Fixed wrong behaviour when assigning BooleanValue. (#83)
- Add ability to programmatically call Django commands from configurations using
``call_command``.
- Added SingleNestedTupleValue and SingleNestedListValue classes. (#85)
- Several other miscellaneous bugfixes.
v0.8 (2014-01-16)
^^^^^^^^^^^^^^^^^
- Added ``SearchURLValue`` to configure Haystack ``HAYSTACK_CONNECTIONS``
settings.
v0.7 (2013-11-26)
^^^^^^^^^^^^^^^^^
- Removed the broken stdout wrapper that displayed the currently enabled
configuration when using the runserver management command. Added a logging
based solution instead.
- Fixed default value of ``CacheURLValue`` class that was shadowed by an
unneeded name parameter. Thanks to Stefan Wehrmeyer.
- Fixed command line options checking in the importer to happen before the
validation. Thanks to Stefan Wehrmeyer.
- Added Tox test configuration.
- Fixed an erroneous use of ``PathValue`` in the 1.6.x project template.
v0.6 (2013-09-19)
^^^^^^^^^^^^^^^^^
- Added a IPython extension to support IPython notebooks correctly. See
the :doc:`cookbook` for more information.
v0.5.1 (2013-09-12)
^^^^^^^^^^^^^^^^^^^
- Prevented accidentally parsing the command line options to look for the
``--configuration`` option outside of Django's management commands.
This should fix a problem with gunicorn's own ``--config`` option.
Thanks to Brian Rosner for the report.
v0.5 (2013-09-09)
^^^^^^^^^^^^^^^^^
- Switched from raising Django's ``ImproperlyConfigured`` exception on errors
to standard ``ValueError`` to prevent hiding those errors when Django
specially handles the first.
- Switched away from d2to1 as a way to define package metadata since distutils2
is dead.
- Extended ``Value`` class documentation and fixed other issues.
- Moved tests out of the ``configurations`` package for easier maintenance.
v0.4 (2013-09-03)
^^^^^^^^^^^^^^^^^
- Added ``Value`` classes and subclasses for easier handling of settings values,
including populating them from environment variables.
- Renamed ``configurations.Settings`` class to ``configurations.Configuration``
to better describe what the class is all about. The old class still exists
and is marked as pending deprecation. It'll be removed in version 1.0.
- Added a ``setup`` method to handle the new ``Value`` classes and allow an
in-between modification of the configuration values.
- Added Django project templates for 1.5.x and 1.6.x.
- Reorganized and extended documentation.
v0.3.2 (2014-01-16)
^^^^^^^^^^^^^^^^^^^
- Fixed an installation issue.
v0.3.1 (2013-09-20)
^^^^^^^^^^^^^^^^^^^
- Backported a fix from master that makes 0.3.x compatible with newer
versions of six.
v0.3 (2013-05-15)
^^^^^^^^^^^^^^^^^
- Added ``pristinemethod`` decorator to be able to have callables as settings.
- Added ``pre_setup`` and ``post_setup`` method hooks to be able to run code
before or after the settings loading is finished.
- Minor docs and tests cleanup.
v0.2.1 (2013-04-11)
^^^^^^^^^^^^^^^^^^^
- Fixed a regression in parsing the new ``-C``/``--configuration`` management
command option.
- Minor fix in showing the configuration in the ``runserver`` management
command output.
v0.2 (2013-03-27)
^^^^^^^^^^^^^^^^^
- **backward incompatible change** Dropped support for Python 2.5! Please use
the 0.1 version if you really want.
- Added Python>3.2 and Django 1.5 support!
- Catch error when getting or evaluating callable setting class attributes.
- Simplified and extended tests.
- Added optional ``-C``/``--configuration`` management command option similar
to Django's ``--settings`` option
- Fixed the runserver message about which setting is used to
show the correct class.
- Stopped hiding AttributeErrors happening during initialization
of settings classes.
- Added FastCGI helper.
- Minor documentation fixes
v0.1 (2012-07-21)
^^^^^^^^^^^^^^^^^
- Initial public release

View file

@ -1,294 +1,44 @@
# -*- coding: utf-8 -*-
#
# django-configurations documentation build configuration file, created by
# sphinx-quickstart on Sat Jul 21 15:03:23 2012.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import configurations
import sys
import os
# -- Project information -----------------------------------------------------
project = 'django-configurations'
copyright = '2012-2023, Jannis Leidel and other contributors'
author = 'Jannis Leidel and other contributors'
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('..'))
release = configurations.__version__
version = ".".join(release.split(".")[:2])
# -- General configuration -----------------------------------------------------
# -- General configuration ---------------------------------------------------
add_function_parentheses = False
add_module_names = False
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
'sphinx.ext.viewcode',
]
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode']
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'django-configurations'
copyright = u'2012, Jannis Leidel'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
try:
from configurations import __version__
# The short X.Y version.
version = '.'.join(__version__.split('.')[:2])
# The full version, including alpha/beta/rc tags.
release = __version__
except ImportError:
version = release = 'dev'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
# html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'django-configurationsdoc'
# -- Options for LaTeX output --------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
intersphinx_mapping = {
'python': ('https://docs.python.org/3', None),
'sphinx': ('https://www.sphinx-doc.org/en/master', None),
'django': ('https://docs.djangoproject.com/en/dev',
'https://docs.djangoproject.com/en/dev/_objects/'),
}
# 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'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# 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)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (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.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# -- Options for HTML output -------------------------------------------------
html_theme = 'furo'
# -- Options for Epub output ---------------------------------------------------
epub_title = project
epub_author = author
epub_publisher = author
epub_copyright = copyright
# 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'
# The language of the text. It defaults to the language option
# or en if the language is not set.
#epub_language = ''
# The scheme of the identifier. Typical schemes are ISBN or URL.
#epub_scheme = ''
# The unique identifier of the text. This can be a ISBN number
# or the project homepage.
#epub_identifier = ''
# A unique identification for the text.
#epub_uid = ''
# A tuple containing the cover image and cover page html template filenames.
#epub_cover = ()
# HTML files that should be inserted before the pages created by sphinx.
# The format is a list of tuples containing the path and title.
#epub_pre_files = []
# HTML files shat should be inserted after the pages created by sphinx.
# The format is a list of tuples containing the path and title.
#epub_post_files = []
# A list of files that should not be packed into the epub file.
#epub_exclude_files = []
# The depth of the table of contents in toc.ncx.
#epub_tocdepth = 3
# Allow duplicate toc entries.
#epub_tocdup = True
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'http://docs.python.org/': None}
# -- Options for LaTeX output --------------------------------------------------
latex_documents = [
# (source start file, target name, title, author, documentclass)
('index', 'django-configurations.tex',
'django-configurations Documentation', author, 'manual'),
]

350
docs/cookbook.rst Normal file
View file

@ -0,0 +1,350 @@
Cookbook
========
Calling a Django management command
-----------------------------------
.. versionadded:: 0.9
If you want to call a Django management command programmatically, say
from a script outside of your usual Django code, you can use the
equivalent of Django's :func:`~django.core.management.call_command`
function with django-configurations, too.
Simply import it from ``configurations.management`` instead:
.. code-block:: python
:emphasize-lines: 1
from configurations.management import call_command
call_command('dumpdata', exclude=['contenttypes', 'auth'])
Read .env file
--------------
Configurations can read values for environment variables out of an ``.env``
file, and push them into the application's process environment. Simply set
the ``DOTENV`` setting to the appropriate file name:
.. code-block:: python
# mysite/settings.py
import os.path
from configurations import Configuration, values
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
class Dev(Configuration):
DOTENV = os.path.join(BASE_DIR, '.env')
SECRET_KEY = values.SecretValue()
API_KEY1 = values.Value()
API_KEY2 = values.Value()
API_KEY3 = values.Value('91011')
A ``.env`` file is a ``.ini``-style file. It must contain a list of
``KEY=value`` pairs, just like Shell environment variables:
.. code-block:: ini
# .env
DJANGO_DEBUG=False
DJANGO_SECRET_KEY=1q2w3e4r5t6z7u8i9o0(%&)$§!pqaycz
API_KEY1=1234
API_KEY2=5678
Envdir
------
envdir_ is an effective way to set a large number of environment variables
at once during startup of a command. This is great in combination with
django-configuration's :class:`~configurations.values.Value` subclasses
when enabling their ability to check environment variables for override
values.
Imagine for example you want to set a few environment variables, all you
have to do is to create a directory with files that have capitalized names
and contain the values you want to set.
Example:
.. code-block:: console
$ tree --noreport mysite_env/
mysite_env/
├── DJANGO_SETTINGS_MODULE
├── DJANGO_DEBUG
├── DJANGO_DATABASE_URL
├── DJANGO_CACHE_URL
└── PYTHONSTARTUP
$ cat mysite_env/DJANGO_CACHE_URL
redis://user@host:port/1
Then, to enable the ``mysite_env`` environment variables, simply use the
``envdir`` command line tool as a prefix for your program, e.g.:
.. code-block:: console
$ envdir mysite_env python manage.py runserver
See envdir_ documentation for more information, e.g. using envdir_ from
Python instead of from the command line.
.. _envdir: https://pypi.python.org/pypi/envdir
Sentry (dynamic setup calls)
----------------------------
For all tools that require an initialization call you should use
:ref:`Setup methods<setup-methods>` (unless you want them activated
for all environments).
Intuitively you might want to add the required setup call like any
other setting:
.. code-block:: python
class Prod(Base):
# ...
sentry_sdk.init("your dsn", integrations=[DjangoIntegration()])
But this will activate, in this case, Sentry even when you're running a
Dev configuration. What you should do instead, is put that code in the
``post_setup`` function. That way Sentry will only ever run when Prod
is the selected configuration:
.. code-block:: python
class Prod(Base):
# ...
@classmethod
def post_setup(cls):
"""Sentry initialization"""
super(Prod, cls).post_setup()
sentry_sdk.init(
dsn=os.environ.get("your dsn"), integrations=[DjangoIntegration()]
)
.. _project-templates:
Project templates
-----------------
You can use a special Django project template that is a copy of the one
included in Django 1.5.x and 1.6.x. The following examples assumes you're
using pip_ to install packages.
Django 1.8.x
^^^^^^^^^^^^
First install Django 1.8.x and django-configurations:
.. code-block:: console
$ python -m pip install -r https://raw.github.com/jazzband/django-configurations/templates/1.8.x/requirements.txt
Or Django 1.8:
.. code-block:: console
$ 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``
directory that uses django-configurations.
See the repository of the template for more information:
https://github.com/jazzband/django-configurations/tree/templates/1.8.x
.. _pip: http://pip-installer.org/
Celery
------
< 3.1
^^^^^
Given Celery's way to load Django settings in worker processes you should
probably just add the following to the **beginning** of your settings module:
.. code-block:: python
import configurations
configurations.setup()
That has the same effect as using the ``manage.py``, ``wsgi.py`` or ``asgi.py`` utilities.
This will also call ``django.setup()``.
>= 3.1
^^^^^^
In Celery 3.1 and later the integration between Django and Celery has been
simplified to use the standard Celery Python API. Django projects using Celery
are now advised to add a ``celery.py`` file that instantiates an explicit
``Celery`` client app.
Here's how to integrate django-configurations following the `example from
Celery's documentation`_:
.. code-block:: python
:emphasize-lines: 9, 11-12
from __future__ import absolute_import
import os
from celery import Celery
from django.conf import settings
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
os.environ.setdefault('DJANGO_CONFIGURATION', 'MySiteConfiguration')
import configurations
configurations.setup()
app = Celery('mysite')
app.config_from_object('django.conf:settings')
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
@app.task(bind=True)
def debug_task(self):
print('Request: {0!r}'.format(self.request))
.. _`example from Celery's documentation`: http://docs.celeryproject.org/en/latest/django/first-steps-with-django.html
iPython notebooks
-----------------
.. versionadded:: 0.6
To use django-configurations with IPython_'s great notebooks, you have to
enable an extension in your IPython configuration. See the IPython
documentation for how to create and `manage your IPython profile`_ correctly.
Here's a quick how-to in case you don't have a profile yet. Type in your
command line shell:
.. code-block:: console
$ ipython profile create
Then let IPython show you where the configuration file ``ipython_config.py``
was created:
.. code-block:: console
$ ipython locate profile
That should print a directory path where you can find the
``ipython_config.py`` configuration file. Now open that file and extend the
``c.InteractiveShellApp.extensions`` configuration value. It may be commented
out from when IPython created the file or it may not exist in the file at all.
In either case make sure it's not a Python comment anymore and reads like this:
.. code-block:: python
# A list of dotted module names of IPython extensions to load.
c.InteractiveShellApp.extensions = [
# .. your other extensions if available
'configurations',
]
That will tell IPython to load django-configurations correctly on startup.
It also works with django-extensions's shell_plus_ management command.
.. _IPython: http://ipython.org/
.. _`manage your IPython profile`: http://ipython.org/ipython-doc/dev/config/overview.html#configuration-file-location
.. _shell_plus: https://django-extensions.readthedocs.io/en/latest/shell_plus.html
FastCGI
-------
In case you use FastCGI for deploying Django (you really shouldn't) and aren't
allowed to use Django's runfcgi_ management command (that would automatically
handle the setup for your if you've followed the quickstart guide above), make
sure to use something like the following script:
.. code-block:: python
#!/usr/bin/env python
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
os.environ.setdefault('DJANGO_CONFIGURATION', 'MySiteConfiguration')
from configurations.fastcgi import runfastcgi
runfastcgi(method='threaded', daemonize='true')
As you can see django-configurations provides a helper module
``configurations.fastcgi`` that handles the setup of your configurations.
.. _runfcgi: https://docs.djangoproject.com/en/1.5/howto/deployment/fastcgi/
Sphinx
------
In case you would like to user the amazing `autodoc` feature of the
documentation tool `Sphinx <http://sphinx-doc.org/>`_, you need add
django-configurations to your ``extensions`` config variable and set
the environment variable accordingly:
.. code-block:: python
:emphasize-lines: 2-3, 12
# My custom Django environment variables
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
os.environ.setdefault('DJANGO_CONFIGURATION', 'Dev')
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
'sphinx.ext.viewcode',
# ...
'configurations.sphinx',
]
# ...
.. versionchanged:: 2.0
Please note that the sphinx callable has been moved from ``configurations`` to
``configurations.sphinx``.
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>`_
you have to use a asgi.py script similar to the following:
.. code-block:: python
import os
from configurations import importer
from channels.asgi import get_channel_layer
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "your_project.settings")
os.environ.setdefault('DJANGO_CONFIGURATION', 'Dev')
importer.install()
channel_layer = get_channel_layer()
That will properly load your django-configurations powered settings.

View file

@ -1,5 +1,12 @@
.. include:: ../README.rst
Project templates
^^^^^^^^^^^^^^^^^
Don't miss the Django :ref:`project templates pre-configured with
django-configurations<project-templates>` to simplify getting started
with new Django projects.
Wait, what?
-----------
@ -10,7 +17,7 @@ and adapters_ that are useful for non-trivial configuration scenarios.
It allows you to use the native abilities of Python inheritance without the
side effects of module level namespaces that often lead to the unfortunate
use of the ``import *`` anti-pattern.
use of the ``from foo import *`` anti-pattern.
.. _mixins: http://en.wikipedia.org/wiki/Mixin
.. _facades: http://en.wikipedia.org/wiki/Facade_pattern
@ -20,7 +27,7 @@ use of the ``import *`` anti-pattern.
Okay, how does it work?
-----------------------
Any subclass of the ``configurations.Settings`` class will automatically
Any subclass of the ``configurations.Configuration`` class will automatically
use the values of its class and instance attributes (including properties
and methods) to set module level variables of the same module -- that's
how Django will interface to the django-configurations based settings during
@ -34,6 +41,15 @@ environment variable) should be used for the process. It then instantiates
the class defined with ``DJANGO_CONFIGURATION`` and copies the uppercase
attributes to the module level variables.
.. versionadded:: 0.2
Alternatively you can use the ``--configuration`` command line option that
django-configurations adds to all Django management commands. Behind the
scenes it will simply set the ``DJANGO_CONFIGURATION`` environement variable
so this is purely optional and just there to compliment the default
``--settings`` option that Django adds if you prefer that instead of setting
environment variables.
But isn't that magic?
---------------------
@ -44,79 +60,21 @@ behind the scenes.
.. _`PEP 302`: http://www.python.org/dev/peps/pep-0302/
Usage patterns
--------------
Further documentation
---------------------
There are various configuration patterns that can be implemented with
django-configurations. The most common pattern is to have a base class
and various subclasses based on the enviroment they are supposed to be
used in, e.g. in production, staging and development.
.. toctree::
:maxdepth: 3
Server specific settings
^^^^^^^^^^^^^^^^^^^^^^^^
patterns
values
cookbook
changes
For example, imagine you have a base setting class in your ``settings.py``
file::
Alternatives
------------
from configurations import Settings
class Base(Settings):
TIME_ZONE = 'Europe/Berlin'
class Dev(Base):
DEBUG = True
TEMPLATE_DEBUG = DEBUG
class Prod(Base):
TIME_ZONE = 'America/New_York'
You can now set the ``DJANGO_CONFIGURATION`` environment variable to one
of the class names you've defined, e.g. on your production server it
should be ``Prod``. In bash that would be::
export DJANGO_SETTINGS_MODULE=mysite.settings
export DJANGO_CONFIGURATION=Prod
Global settings defaults
^^^^^^^^^^^^^^^^^^^^^^^^
Every ``configurations.Settings`` subclass will automatically contain
Django's global settings as class attributes, so you can refer to them when
setting other values, e.g.::
from configurations import Settings
class Base(Settings):
TEMPLATE_CONTEXT_PROCESSORS = \
Settings.TEMPLATE_CONTEXT_PROCESSORS + (
'django.core.context_processors.request',
)
@property
def LANGUAGES(self):
return Settings.LANGUAGES + (('tlh', 'Klingon'),)
Mixins
^^^^^^
You might want to apply some configuration values for each and every
project you're working on without having to repeat yourself. Just define
a few mixin you re-use multiple times::
class FullPageCaching(object):
USE_ETAGS = True
Then import that mixin class in your site settings module and use it with
a Settings class::
from configurations import Settings
class AcmeProd(Settings, FullPageCaching):
DEBUG = False
# ...
Thanks
------
Many thanks to those project that have previously solved these problems:
- The Pinax_ project for spearheading the efforts to extend the Django
project metaphor with reusable project templates and a flexible
@ -128,12 +86,13 @@ Thanks
.. _Pinax: http://pinaxproject.com
.. _`django-classbasedsettings`: https://github.com/matthewwithanm/django-classbasedsettings
Bugs and feature requests
-------------------------
As always you mileage may vary, so please don't hesitate to send in feature
requests and bug reports at the usual place:
As always your mileage may vary, so please don't hesitate to send feature
requests and bug reports:
https://github.com/jezdez/django-configurations/issues
- https://github.com/jazzband/django-configurations/issues
Thanks!

View file

@ -1,190 +0,0 @@
@ECHO OFF
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
set I18NSPHINXOPTS=%SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^<target^>` where ^<target^> is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. singlehtml to make a single large HTML file
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. devhelp to make HTML files and a Devhelp project
echo. epub to make an epub
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. text to make text files
echo. man to make manual pages
echo. texinfo to make Texinfo files
echo. gettext to make PO message catalogs
echo. changes to make an overview over all changed/added/deprecated items
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "singlehtml" (
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-configurations.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-configurations.ghc
goto end
)
if "%1" == "devhelp" (
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished.
goto end
)
if "%1" == "epub" (
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The epub file is in %BUILDDIR%/epub.
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
if errorlevel 1 exit /b 1
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "text" (
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The text files are in %BUILDDIR%/text.
goto end
)
if "%1" == "man" (
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The manual pages are in %BUILDDIR%/man.
goto end
)
if "%1" == "texinfo" (
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
goto end
)
if "%1" == "gettext" (
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
if errorlevel 1 exit /b 1
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
if errorlevel 1 exit /b 1
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
if errorlevel 1 exit /b 1
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
:end

219
docs/patterns.rst Normal file
View file

@ -0,0 +1,219 @@
Usage patterns
==============
There are various configuration patterns that can be implemented with
django-configurations. The most common pattern is to have a base class
and various subclasses based on the environment they are supposed to be
used in, e.g. in production, staging and development.
Server specific settings
------------------------
For example, imagine you have a base setting class in your **settings.py**
file:
.. code-block:: python
from configurations import Configuration
class Base(Configuration):
TIME_ZONE = 'Europe/Berlin'
class Dev(Base):
DEBUG = True
class Prod(Base):
TIME_ZONE = 'America/New_York'
You can now set the ``DJANGO_CONFIGURATION`` environment variable to
one of the class names you've defined, e.g. on your production server
it should be ``Prod``. In Bash that would be:
.. code-block:: console
$ export DJANGO_SETTINGS_MODULE=mysite.settings
$ export DJANGO_CONFIGURATION=Prod
$ python -m manage runserver
Alternatively you can use the ``--configuration`` option when using Django
management commands along the lines of Django's default ``--settings``
command line option, e.g.
.. code-block:: console
$ python -m manage runserver --settings=mysite.settings --configuration=Prod
Property settings
-----------------
Use a ``property`` to allow for computed settings. This pattern can
also be used to postpone / lazy evaluate a value. E.g., useful when
nesting a Value in a dictionary and a string is required:
.. code-block:: python
class Prod(Configuration):
SOME_VALUE = values.Value(None, environ_prefix=None)
@property
def SOME_CONFIG(self):
return {
'some_key': self.SOME_VALUE,
}
Global settings defaults
------------------------
Every ``configurations.Configuration`` subclass will automatically
contain Django's global settings as class attributes, so you can refer
to them when setting other values, e.g.
.. code-block:: python
from configurations import Configuration
class Prod(Configuration):
TEMPLATE_CONTEXT_PROCESSORS = Configuration.TEMPLATE_CONTEXT_PROCESSORS + (
'django.core.context_processors.request',
)
@property
def LANGUAGES(self):
return list(Configuration.LANGUAGES) + [('tlh', 'Klingon')]
Configuration mixins
--------------------
You might want to apply some configuration values for each and every
project you're working on without having to repeat yourself. Just define
a few mixin you re-use multiple times:
.. code-block:: python
class FullPageCaching:
USE_ETAGS = True
Then import that mixin class in your site settings module and use it with
a ``Configuration`` class:
.. code-block:: python
from configurations import Configuration
class Prod(FullPageCaching, Configuration):
DEBUG = False
# ...
Pristine methods
----------------
.. versionadded:: 0.3
In case one of your settings itself need to be a callable, you need to
tell that django-configurations by using the ``pristinemethod``
decorator, e.g.
.. code-block:: python
from configurations import Configuration, pristinemethod
class Prod(Configuration):
@pristinemethod
def ACCESS_FUNCTION(user):
return user.is_staff
Lambdas work, too:
.. code-block:: python
from configurations import Configuration, pristinemethod
class Prod(Configuration):
ACCESS_FUNCTION = pristinemethod(lambda user: user.is_staff)
.. _setup-methods:
Setup methods
-------------
.. versionadded:: 0.3
If there is something required to be set up before, during or after the
settings loading happens, please override the ``pre_setup``, ``setup`` or
``post_setup`` class methods like so (don't forget to apply the Python
``@classmethod`` decorator):
.. code-block:: python
import logging
from configurations import Configuration
class Prod(Configuration):
# ...
@classmethod
def pre_setup(cls):
super(Prod, cls).pre_setup()
if something.completely.different():
cls.DEBUG = True
@classmethod
def setup(cls):
super(Prod, cls).setup()
logging.info('production settings loaded: %s', cls)
@classmethod
def post_setup(cls):
super(Prod, cls).post_setup()
logging.debug("done setting up! \o/")
As you can see above the ``pre_setup`` method can also be used to
programmatically change a class attribute of the settings class and it
will be taken into account when doing the rest of the settings setup.
Of course that won't work for ``post_setup`` since that's when the
settings setup is already done.
In fact you can easily do something unrelated to settings, like
connecting to a database:
.. code-block:: python
from configurations import Configuration
class Prod(Configuration):
# ...
@classmethod
def post_setup(cls):
import mango
mango.connect('enterprise')
.. warning::
You could do the same by overriding the ``__init__`` method of your
settings class but this may cause hard to debug errors because
at the time the ``__init__`` method is called (during Django
startup) the Django setting system isn't fully loaded yet.
So anything you do in ``__init__`` that may require
``django.conf.settings`` or Django models there is a good chance it
won't work. Use the ``post_setup`` method for that instead.
.. versionchanged:: 0.4
A new ``setup`` method was added to be able to handle the new
:class:`~configurations.values.Value` classes and allow an
in-between modification of the configuration values.
Standalone scripts
------------------
If you want to run scripts outside of your project you need to add
these lines on top of your file:
.. code-block:: python
import configurations
configurations.setup()

3
docs/requirements.txt Normal file
View file

@ -0,0 +1,3 @@
Sphinx>4
furo
docutils

648
docs/values.rst Normal file
View file

@ -0,0 +1,648 @@
Values
======
.. module:: configurations.values
:synopsis: Optional value classes for high-level validation and behavior.
.. versionadded:: 0.4
django-configurations allows you to optionally reduce the amount of validation
and setup code in your **settings.py** by using ``Value`` classes. They have
the ability to handle values from the process environment of your software
(:data:`os.environ`) and work well in projects that follow the
`Twelve-Factor methodology`_.
.. note::
These classes are required to be used as attributes of ``Configuration``
classes. See the :doc:`main documentation<index>` for more information.
Overview
--------
Here is an example (from a **settings.py** file with a ``Configuration``
subclass):
.. code-block:: python
:emphasize-lines: 4
from configurations import Configuration, values
class Dev(Configuration):
DEBUG = values.BooleanValue(True)
As you can see all you have to do is to wrap your settings value in a call
to one of the included values classes. When Django's process starts up
it will automatically make sure the passed-in value validates correctly --
in the above case checks if the value is really a boolean.
You can safely use other :class:`~Value` instances as the default setting
value:
.. code-block:: python
:emphasize-lines: 5
from configurations import Configuration, values
class Dev(Configuration):
DEBUG = values.BooleanValue(True)
DEBUG_PROPAGATE_EXCEPTIONS = values.BooleanValue(DEBUG)
See the list of :ref:`built-in value classes<built-ins>` for more information.
Environment variables
---------------------
To separate the site configuration from your application code you should use
environment variables for configuration. Unfortunately environment variables
are string based so they are not easily mapped to the Python based settings
system Django uses.
Luckily django-configurations' :class:`~Value` subclasses have the ability
to handle environment variables for the common use cases.
Default behavior
^^^^^^^^^^^^^^^^
For example, imagine you want to override the ``ROOT_URLCONF`` setting on your
staging server to be able to debug a problem with your in-development code.
You're using a web server that passes the environment variables from
the shell it was started from into your Django WSGI process.
Use the boolean ``environ`` option of the :class:`~Value` class (``True`` by
default) to tell django-configurations to look for an environment variable with
the same name as the specific :class:`~Value` variable, only uppercased and
prefixed with ``DJANGO_``. E.g.:
.. code-block:: python
:emphasize-lines: 5
from configurations import Configuration, values
class Stage(Configuration):
# ..
ROOT_URLCONF = values.Value('mysite.urls')
django-configurations will try to read the ``DJANGO_ROOT_URLCONF`` environment
variable when deciding which value the ``ROOT_URLCONF`` setting should have.
When you run the web server simply specify that environment variable
(e.g. in your init script):
.. 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
``'mysite.urls'``.
Disabling environment variables
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To disable environment variables, specify the ``environ`` parameter of the
:class:`~Value` class. For example this would disable it for the ``TIME_ZONE``
setting value::
from configurations import Configuration, values
class Dev(Configuration):
TIME_ZONE = values.Value('UTC', environ=False)
Custom environment variable names
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To support legacy systems, integrate with other parts of your software stack or
simply better match your taste in naming public configuration variables,
django-configurations allows you to use the ``environ_name`` parameter of the
:class:`~Value` class to change the base name of the environment variable it
looks for. For example this would enforce the name ``DJANGO_MYSITE_TZ``
instead of using the name of the :class:`~Value` instance.::
from configurations import Configuration, values
class Dev(Configuration):
TIME_ZONE = values.Value('UTC', environ_name='MYSITE_TZ')
Allow final value to be used outside the configuration context
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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
context:
.. code-block:: pycon
>>> type(values.Value([]))
<class 'configurations.values.Value'>
>>> type(values.Value([], environ_name="FOOBAR"))
<class 'list'>
This can also be achieved when using ``environ=False`` and providing a
default value.
Custom environment variable prefixes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In case you want to change the default environment variable name prefix
of ``DJANGO`` to something to your likening, use the ``environ_prefix``
parameter of the :class:`~Value` instance. Here it'll look for the
``MYSITE_TIME_ZONE`` environment variable (instead of ``DJANGO_TIME_ZONE``)::
from configurations import Configuration, values
class Dev(Configuration):
TIME_ZONE = values.Value('UTC', environ_prefix='MYSITE')
The ``environ_prefix`` parameter can also be ``None`` to completely disable
the prefix.
``Value`` class
---------------
.. class:: Value(default, [environ=True, environ_name=None, environ_prefix='DJANGO', environ_required=False])
The ``Value`` class takes one required and several optional parameters.
:param default: the default value of the setting
:param environ: toggle for environment use
:param environ_name: capitalized name of environment variable to look for
:param environ_prefix: capitalized prefix to use when looking for 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: str or None
:type environ_prefix: str
:type environ_required: bool
The ``default`` parameter is effectively the value the setting has
right now in your ``settings.py``.
.. method:: setup(name)
:param name: the name of the setting
:return: setting value
The ``setup`` method is called during startup of the Django process and
implements the ability to check the environment variable. Its purpose is
to return a value django-configurations is supposed to use when loading
the settings. It'll be passed one parameter, the name of the
:class:`~Value` instance as defined in the ``settings.py``. This is used
for building the name of the environment variable.
.. method:: to_python(value)
:param value: the value of the setting as found in the process
environment (:data:`os.environ`)
:return: validated and "ready" setting value if found in process
environment
The ``to_python`` method is used when the ``environ`` parameter of the
:class:`~Value` class is set to ``True`` (the default) and an
environment variable with the appropriate name was found.
It will be used to handle the string based environment variables and
returns the "ready" value of the setting.
Some :class:`~Value` subclasses also use it during initialization when the
default value has a string-like format like an environment variable which
needs to be converted into a Python data type.
.. _built-ins:
Built-ins
---------
Type values
^^^^^^^^^^^
.. class:: BooleanValue
A :class:`~Value` subclass that checks and returns boolean values. Possible
values for environment variables are:
- ``True`` values: ``'yes'``, ``'y'``, ``'true'``, ``'1'``
- ``False`` values: ``'no'``, ``'n'``, ``'false'``, ``'0'``,
``''`` (empty string)
::
DEBUG = values.BooleanValue(True)
.. class:: IntegerValue
A :class:`~Value` subclass that handles integer values.
::
MYSITE_CACHE_TIMEOUT = values.IntegerValue(3600)
.. class:: PositiveIntegerValue
A :class:`~Value` subclass that handles positive integer values.
.. versionadded:: 2.1
::
MYSITE_WORKER_POOL = values.PositiveIntegerValue(8)
.. class:: FloatValue
A :class:`~Value` subclass that handles float values.
::
MYSITE_TAX_RATE = values.FloatValue(11.9)
.. class:: DecimalValue
A :class:`~Value` subclass that handles Decimal values.
::
MYSITE_CONVERSION_RATE = values.DecimalValue(decimal.Decimal('4.56214'))
.. class:: SequenceValue
Common base class for sequence values.
.. class:: ListValue(default, [separator=',', converter=None])
A :class:`~SequenceValue` subclass that handles list values.
:param separator: the separator to split environment variables with
:param converter: the optional converter callable to apply for each list
item
Simple example::
ALLOWED_HOSTS = ListValue(['mysite.com', 'mysite.biz'])
Use a custom converter to check for the given variables::
def check_monty_python(person):
if not is_completely_different(person):
error = '{0} is not a Monty Python member'.format(person)
raise ValueError(error)
return person
MONTY_PYTHONS = ListValue(['John Cleese', 'Eric Idle'],
converter=check_monty_python)
You can override this list with an environment variable like this:
.. code-block:: console
$ DJANGO_MONTY_PYTHONS="Terry Jones,Graham Chapman" gunicorn mysite.wsgi:application
Use a custom separator::
EMERGENCY_EMAILS = ListValue(['admin@mysite.net'], separator=';')
And override it:
.. code-block:: console
$ DJANGO_EMERGENCY_EMAILS="admin@mysite.net;manager@mysite.org;support@mysite.com" gunicorn mysite.wsgi:application
.. class:: TupleValue
A :class:`~SequenceValue` subclass that handles tuple values.
:param separator: the separator to split environment variables with
:param converter: the optional converter callable to apply for each tuple
item
See the :class:`~ListValue` examples above.
.. class:: SingleNestedSequenceValue
Common base class for nested sequence values.
.. class:: SingleNestedTupleValue(default, [seq_separator=';', separator=',', converter=None])
A :class:`~SingleNestedSequenceValue` subclass that handles single nested tuple values,
e.g. ``((a, b), (c, d))``.
:param seq_separator: the separator to split each tuple with
:param separator: the separator to split the inner tuple contents with
:param converter: the optional converter callable to apply for each inner
tuple item
Useful for ADMINS, MANAGERS, and the like. For example::
ADMINS = SingleNestedTupleValue((
('John', 'jcleese@site.com'),
('Eric', 'eidle@site.com'),
))
Override using environment variables like this::
DJANGO_ADMINS=Terry,tjones@site.com;Graham,gchapman@site.com
.. class:: SingleNestedListValue(default, [seq_separator=';', separator=',', converter=None])
A :class:`~SingleNestedSequenceValue` subclass that handles single nested list values,
e.g. ``[[a, b], [c, d]]``.
:param seq_separator: the separator to split each list with
:param separator: the separator to split the inner list contents with
:param converter: the optional converter callable to apply for each inner
list item
See the :class:`~SingleNestedTupleValue` examples above.
.. class:: SetValue
A :class:`~Value` subclass that handles set values.
:param separator: the separator to split environment variables with
:param converter: the optional converter callable to apply for each set
item
See the :class:`~ListValue` examples above.
.. class:: DictValue
A :class:`~Value` subclass that handles dicts.
::
DEPARTMENTS = values.DictValue({
'it': ['Mike', 'Joe'],
})
Override using environment variables like this::
DJANGO_DEPARTMENTS={'it':['Mike','Joe'],'hr':['Emma','Olivia']}
Validator values
^^^^^^^^^^^^^^^^
.. class:: EmailValue
A :class:`~Value` subclass that validates the value using the
:data:`django:django.core.validators.validate_email` validator.
::
SUPPORT_EMAIL = values.EmailValue('support@mysite.com')
.. class:: URLValue
A :class:`~Value` subclass that validates the value using the
:class:`django:django.core.validators.URLValidator` validator.
::
SUPPORT_URL = values.URLValue('https://support.mysite.com/')
.. class:: IPValue
A :class:`~Value` subclass that validates the value using the
:data:`django:django.core.validators.validate_ipv46_address` validator.
::
LOADBALANCER_IP = values.IPValue('127.0.0.1')
.. class:: RegexValue(default, regex, [environ=True, environ_name=None, environ_prefix='DJANGO'])
A :class:`~Value` subclass that validates according a regular expression
and uses the :class:`django:django.core.validators.RegexValidator`.
:param regex: the regular expression
::
DEFAULT_SKU = values.RegexValue('000-000-00', regex=r'\d{3}-\d{3}-\d{2}')
.. class:: PathValue(default, [check_exists=True, environ=True, environ_name=None, environ_prefix='DJANGO'])
A :class:`~Value` subclass that normalizes the given path using
:func:`os.path.expanduser` and checks if it exists on the file system.
Takes an optional ``check_exists`` parameter to disable the check with
:func:`os.path.exists`.
:param check_exists: toggle the file system check
::
BASE_DIR = values.PathValue('/opt/mysite/')
STATIC_ROOT = values.PathValue('/var/www/static', checks_exists=False)
URL-based values
^^^^^^^^^^^^^^^^
.. note::
The following URL-based :class:`~Value` subclasses are inspired by the
`Twelve-Factor methodology`_ and use environment variable names that are
already established by that methodology, e.g. ``'DATABASE_URL'``.
Each of these classes require external libraries to be installed, e.g. the
:class:`~DatabaseURLValue` class depends on the package ``dj-database-url``.
See the specific class documentation below for which package is needed.
.. class:: DatabaseURLValue(default, [alias='default', environ=True, environ_name='DATABASE_URL', environ_prefix=None])
A :class:`~Value` subclass that uses the `dj-database-url`_ app to
convert a database configuration value stored in the ``DATABASE_URL``
environment variable into an appropriate setting value. It's inspired by
the `Twelve-Factor methodology`_.
By default this :class:`~Value` subclass looks for the ``DATABASE_URL``
environment variable.
Takes an optional ``alias`` parameter to define which database alias to
use for the ``DATABASES`` setting.
:param alias: which database alias to use
The other parameters have the following default values:
:param environ: ``True``
:param environ_name: ``DATABASE_URL``
:param environ_prefix: ``None``
::
DATABASES = values.DatabaseURLValue('postgres://myuser@localhost/mydb')
.. _`dj-database-url`: https://pypi.python.org/pypi/dj-database-url/
.. class:: CacheURLValue(default, [alias='default', environ=True, environ_name='CACHE_URL', environ_prefix=None])
A :class:`~Value` subclass that uses the `django-cache-url`_ app to
convert a cache configuration value stored in the ``CACHE_URL``
environment variable into an appropriate setting value. It's inspired by
the `Twelve-Factor methodology`_.
By default this :class:`~Value` subclass looks for the ``CACHE_URL``
environment variable.
Takes an optional ``alias`` parameter to define which database alias to
use for the ``CACHES`` setting.
:param alias: which cache alias to use
The other parameters have the following default values:
:param environ: ``True``
:param environ_name: ``CACHE_URL``
:param environ_prefix: ``None``
::
CACHES = values.CacheURLValue('memcached://127.0.0.1:11211/')
.. _`django-cache-url`: https://pypi.python.org/pypi/django-cache-url/
.. class:: EmailURLValue(default, [environ=True, environ_name='EMAIL_URL', environ_prefix=None])
A :class:`~Value` subclass that uses the `dj-email-url`_ app to
convert an email configuration value stored in the ``EMAIL_URL``
environment variable into the appropriate settings. It's inspired by
the `Twelve-Factor methodology`_.
By default this :class:`~Value` subclass looks for the ``EMAIL_URL``
environment variable.
.. note::
This is a special value since email settings are divided into many
different settings variables. `dj-email-url`_ supports all options
though and simply returns a nested dictionary of settings instead of
just one setting.
The parameters have the following default values:
:param environ: ``True``
:param environ_name: ``EMAIL_URL``
:param environ_prefix: ``None``
::
EMAIL = values.EmailURLValue('console://')
.. _`dj-email-url`: https://pypi.python.org/pypi/dj-email-url/
.. class:: SearchURLValue(default, [environ=True, environ_name='SEARCH_URL', environ_prefix=None])
.. versionadded:: 0.8
A :class:`~Value` subclass that uses the `dj-search-url`_ app to
convert a search configuration value stored in the ``SEARCH_URL``
environment variable into the appropriate settings for use with Haystack_.
It's inspired by the `Twelve-Factor methodology`_.
By default this :class:`~Value` subclass looks for the ``SEARCH_URL``
environment variable.
Takes an optional ``alias`` parameter to define which search backend alias
to use for the ``HAYSTACK_CONNECTIONS`` setting.
:param alias: which cache alias to use
The other parameters have the following default values:
:param environ: ``True``
:param environ_name: ``SEARCH_URL``
:param environ_prefix: ``None``
::
HAYSTACK_CONNECTIONS = values.SearchURLValue('elasticsearch://127.0.0.1:9200/my-index')
.. _`dj-search-url`: https://pypi.python.org/pypi/dj-search-url/
.. _Haystack: http://haystacksearch.org/
Other values
^^^^^^^^^^^^
.. class:: BackendsValue
A :class:`~ListValue` subclass that validates the given list of dotted
import paths by trying to import them. In other words, this checks if
the backends exist.
::
MIDDLEWARE = values.BackendsValue([
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
])
.. class:: SecretValue
A :class:`~Value` subclass that doesn't allow setting a default value
during instantiation and force-enables the use of an environment variable
to reduce the risk of accidentally storing secret values in the settings
file. This usually resolves to ``DJANGO_SECRET_KEY`` unless you have
customized the environment variable names.
:raises: ``ValueError`` when given a default value
.. versionchanged:: 1.0
This value class has the ``environ_required`` parameter turned to
``True``.
::
SECRET_KEY = values.SecretValue()
Value mixins
^^^^^^^^^^^^
.. class:: CastingMixin
A mixin to be used with one of the :class:`~Value` subclasses that
requires a ``caster`` class attribute of one of the following types:
- dotted import path, e.g. ``'mysite.utils.custom_caster'``
- a callable, e.g. :class:`int`
Example::
class TemparatureValue(CastingMixin, Value):
caster = 'mysite.temperature.fahrenheit_to_celcius'
Optionally it can take a ``message`` class attribute as the error
message to be shown if the casting fails. Additionally an ``exception``
parameter can be set to a single or a tuple of exception classes that
are required to be handled during the casting.
.. class:: ValidationMixin
A mixin to be used with one of the :class:`~Value` subclasses that
requires a ``validator`` class attribute of one of the following types:
The validator should raise Django's
:exc:`~django.core.exceptions.ValidationError` to indicate a failed
validation attempt.
- dotted import path, e.g. ``'mysite.validators.custom_validator'``
- a callable, e.g. :class:`bool`
Example::
class TemparatureValue(ValidationMixin, Value):
validator = 'mysite.temperature.is_valid_temparature'
Optionally it can take a ``message`` class attribute as the error
message to be shown if the validation fails.
.. class:: MultipleMixin
A mixin to be used with one of the :class:`~Value` subclasses that
enables the return value of the :func:`~Value.to_python` to be
interpreted as a dictionary of settings values to be set at once,
instead of using the return value to just set one setting.
A good example for this mixin is the :class:`~EmailURLValue` value
which requires setting many ``EMAIL_*`` settings.
.. _`Twelve-Factor methodology`: http://www.12factor.net/

View file

@ -1,4 +0,0 @@
flake8
coverage
django-discover-runner
mock

10
setup.cfg Normal file
View file

@ -0,0 +1,10 @@
[coverage:run]
source = .
branch = 1
parallel = 1
[coverage:report]
include = configurations/*,tests/*
[flake8]
exclude = .tox,docs/*,.eggs
ignore = E501,E127,E128,E124,W503

View file

@ -1,49 +1,71 @@
import os
import codecs
import re
from os import path
from setuptools import setup
def read(*parts):
file_path = path.join(path.dirname(__file__), *parts)
return codecs.open(file_path).read()
def find_version(*parts):
version_file = read(*parts)
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
version_file, re.M)
if version_match:
return version_match.group(1)
raise RuntimeError("Unable to find version string.")
filename = os.path.join(os.path.dirname(__file__), *parts)
with codecs.open(filename, encoding='utf-8') as fp:
return fp.read()
setup(
name='django-configurations',
version=find_version('configurations', '__init__.py'),
description='A helper for organizing Django project settings by relying '
'on well established programming patterns.',
name="django-configurations",
use_scm_version={"version_scheme": "post-release", "local_scheme": "dirty-tag"},
setup_requires=["setuptools_scm"],
url='https://django-configurations.readthedocs.io/',
project_urls={
'Source': 'https://github.com/jazzband/django-configurations',
},
license='BSD',
description="A helper for organizing Django settings.",
long_description=read('README.rst'),
long_description_content_type='text/x-rst',
author='Jannis Leidel',
author_email='jannis@leidel.info',
license='BSD',
url='http://django-configurations.readthedocs.org/',
packages=[
'configurations',
'configurations.tests',
'configurations.tests.settings',
packages=['configurations'],
entry_points={
'console_scripts': [
'django-cadmin = configurations.management:execute_from_command_line',
],
},
install_requires=[
'django>=3.2',
],
python_requires='>=3.9, <4.0',
extras_require={
'cache': ['django-cache-url'],
'database': ['dj-database-url'],
'email': ['dj-email-url'],
'search': ['dj-search-url'],
'testing': [
'django-cache-url>=1.0.0',
'dj-database-url',
'dj-email-url',
'dj-search-url',
],
},
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Web Environment',
'Development Status :: 5 - Production/Stable',
'Framework :: Django',
'Framework :: Django :: 3.2',
'Framework :: Django :: 4.1',
'Framework :: Django :: 4.2',
'Framework :: Django :: 5.0',
'Framework :: Django :: 5.1',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2.5',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3 :: Only',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
'Programming Language :: Python :: Implementation :: PyPy',
'Topic :: Utilities',
],
zip_safe=False,
)

1
test_project/.env Normal file
View file

@ -0,0 +1 @@
DJANGO_DOTENV_VALUE='is set'

View file

@ -3,8 +3,8 @@ import os
import sys
if __name__ == "__main__":
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'configurations.tests.settings.main')
os.environ.setdefault('DJANGO_CONFIGURATION', 'Test')
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'test_project.settings')
os.environ.setdefault('DJANGO_CONFIGURATION', 'Debug')
from configurations.management import execute_from_command_line

View file

@ -0,0 +1,167 @@
from configurations import Configuration, values
class Base(Configuration):
# Django settings for test_project project.
DEBUG = values.BooleanValue(True, environ=True)
ADMINS = (
# ('Your Name', 'your_email@example.com'),
)
EMAIL_URL = values.EmailURLValue('console://', environ=True)
MANAGERS = ADMINS
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
'NAME': '', # Or path to database file if using sqlite3.
'USER': '', # Not used with sqlite3.
'PASSWORD': '', # Not used with sqlite3.
'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
'PORT': '', # Set to empty string for default. Not used with sqlite3.
}
}
# Hosts/domain names that are valid for this site; required if DEBUG is False
# See https://docs.djangoproject.com/en/1.4/ref/settings/#allowed-hosts
ALLOWED_HOSTS = []
# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# In a Windows environment this must be set to your system time zone.
TIME_ZONE = 'America/Chicago'
# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-us'
SITE_ID = 1
# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = True
# If you set this to False, Django will not format dates, numbers and
# calendars according to the current locale.
USE_L10N = True
# If you set this to False, Django will not use timezone-aware datetimes.
USE_TZ = True
# Absolute filesystem path to the directory that will hold user-uploaded files.
# Example: "/home/media/media.lawrence.com/media/"
MEDIA_ROOT = ''
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash.
# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
MEDIA_URL = ''
# Absolute path to the directory static files should be collected to.
# Don't put anything in this directory yourself; store your static files
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
# Example: "/home/media/media.lawrence.com/static/"
STATIC_ROOT = ''
# URL prefix for static files.
# Example: "http://media.lawrence.com/static/"
STATIC_URL = '/static/'
# Additional locations of static files
STATICFILES_DIRS = (
# Put strings here, like "/home/html/static" or "C:/www/django/static".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
)
# List of finder classes that know how to find static files in
# various locations.
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
)
# Make this unique, and don't share it with anybody.
SECRET_KEY = '-9i$j8kcp48(y-v0hiwgycp5jb*_)sy4(swd@#m(j1m*4vfn4w'
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
)
MIDDLEWARE = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
# Uncomment the next line for simple clickjacking protection:
# 'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
ROOT_URLCONF = 'test_project.urls'
# Python dotted path to the WSGI application used by Django's runserver.
WSGI_APPLICATION = 'test_project.wsgi.application'
TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
)
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
# Uncomment the next line to enable the admin:
# 'django.contrib.admin',
# Uncomment the next line to enable admin documentation:
# 'django.contrib.admindocs',
'configurations',
)
# A sample logging configuration. The only tangible logging
# performed by this configuration is to send an email to
# the site admins on every HTTP 500 error when DEBUG=False.
# See http://docs.djangoproject.com/en/dev/topics/logging for
# more details on how to customize your logging configuration.
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse'
}
},
'handlers': {
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler'
}
},
'loggers': {
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
},
}
}
class Debug(Base):
YEAH = True
class Other(Base):
YEAH = False

View file

@ -0,0 +1,17 @@
from django.conf.urls import patterns
# Uncomment the next two lines to enable the admin:
# from django.contrib import admin
# admin.autodiscover()
urlpatterns = patterns('',
# Examples:
# url(r'^$', 'test_project.views.home', name='home'),
# url(r'^test_project/', include('test_project.foo.urls')),
# Uncomment the admin/doc line below to enable admin documentation:
# url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable the admin:
# url(r'^admin/', include(admin.site.urls)),
)

View file

@ -0,0 +1,28 @@
"""
WSGI config for test_project project.
This module contains the WSGI application used by Django's development server
and any production WSGI deployments. It should expose a module-level variable
named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
this application via the ``WSGI_APPLICATION`` setting.
Usually you will have the standard Django WSGI application here, but it also
might make sense to replace the whole Django WSGI application with a custom one
that later delegates to the Django one. For example, you could introduce WSGI
middleware here, or combine a Django application with an application of another
framework.
"""
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_project.settings")
# This application object is used by any WSGI server configured to use this
# file. This includes Django's development server, if the WSGI_APPLICATION
# setting points here.
from django.core.wsgi import get_wsgi_application # noqa
application = get_wsgi_application()
# Apply WSGI middleware here.
# from helloworld.wsgi import HelloWorldApplication
# application = HelloWorldApplication(application)

View file

9
tests/settings/base.py Normal file
View file

@ -0,0 +1,9 @@
from configurations import Configuration
def test_callback(request):
return {}
class Base(Configuration):
pass

11
tests/settings/dot_env.py Normal file
View file

@ -0,0 +1,11 @@
from configurations import Configuration, values
class DotEnvConfiguration(Configuration):
DOTENV = 'test_project/.env'
DOTENV_VALUE = values.Value()
def DOTENV_VALUE_METHOD(self):
return values.Value(environ_name="DOTENV_VALUE")

8
tests/settings/error.py Normal file
View file

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

70
tests/settings/main.py Normal file
View file

@ -0,0 +1,70 @@
import os
import uuid
from configurations import Configuration, pristinemethod
class Test(Configuration):
BASE_DIR = os.path.abspath(
os.path.join(os.path.dirname(
os.path.abspath(__file__)), os.pardir))
DEBUG = True
SITE_ID = 1
SECRET_KEY = str(uuid.uuid4())
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(os.path.dirname(__file__), 'test.db'),
}
}
INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.contenttypes',
'django.contrib.sites',
'django.contrib.auth',
'tests',
]
ROOT_URLCONF = 'tests.urls'
@property
def ALLOWED_HOSTS(self):
allowed_hosts = super().ALLOWED_HOSTS[:]
allowed_hosts.append('base')
return allowed_hosts
ATTRIBUTE_SETTING = True
_PRIVATE_SETTING = 'ryan'
@property
def PROPERTY_SETTING(self):
return 1
def METHOD_SETTING(self):
return 2
LAMBDA_SETTING = lambda self: 3 # noqa: E731
PRISTINE_LAMBDA_SETTING = pristinemethod(lambda: 4)
@pristinemethod
def PRISTINE_FUNCTION_SETTING():
return 5
@classmethod
def pre_setup(cls):
cls.PRE_SETUP_TEST_SETTING = 6
@classmethod
def post_setup(cls):
cls.POST_SETUP_TEST_SETTING = 7
class TestWithDefaultSetExplicitely(Test):
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

View file

@ -0,0 +1,26 @@
from configurations import Configuration
class Mixin1:
@property
def ALLOWED_HOSTS(self):
allowed_hosts = super().ALLOWED_HOSTS[:]
allowed_hosts.append('test1')
return allowed_hosts
class Mixin2:
@property
def ALLOWED_HOSTS(self):
allowed_hosts = super().ALLOWED_HOSTS[:]
allowed_hosts.append('test2')
return allowed_hosts
class Inheritance(Mixin2, Mixin1, Configuration):
def ALLOWED_HOSTS(self):
allowed_hosts = super().ALLOWED_HOSTS[:]
allowed_hosts.append('test3')
return allowed_hosts

View file

@ -0,0 +1,9 @@
from .single_inheritance import Inheritance as BaseInheritance
class Inheritance(BaseInheritance):
def ALLOWED_HOSTS(self):
allowed_hosts = super().ALLOWED_HOSTS[:]
allowed_hosts.append('test-test')
return allowed_hosts

View file

@ -0,0 +1,10 @@
from .base import Base
class Inheritance(Base):
@property
def ALLOWED_HOSTS(self):
allowed_hosts = super().ALLOWED_HOSTS[:]
allowed_hosts.append('test')
return allowed_hosts

14
tests/setup_test.py Normal file
View file

@ -0,0 +1,14 @@
"""Used by tests to ensure logging is kept when calling setup() twice."""
from unittest import mock
import configurations
print('setup_1')
configurations.setup()
with mock.patch('django.setup', side_effect=Exception('setup called twice')):
print('setup_2')
configurations.setup()
print('setup_done')

15
tests/test_env.py Normal file
View file

@ -0,0 +1,15 @@
import os
from django.test import TestCase
from unittest.mock import patch
class DotEnvLoadingTests(TestCase):
@patch.dict(os.environ, clear=True,
DJANGO_CONFIGURATION='DotEnvConfiguration',
DJANGO_SETTINGS_MODULE='tests.settings.dot_env')
def test_env_loaded(self):
from tests.settings import dot_env
self.assertEqual(dot_env.DOTENV_VALUE, 'is set')
self.assertEqual(dot_env.DOTENV_VALUE_METHOD, 'is set')
self.assertEqual(dot_env.DOTENV_LOADED, dot_env.DOTENV)

22
tests/test_error.py Normal file
View file

@ -0,0 +1,22 @@
import os
from django.test import TestCase
from unittest.mock import patch
class ErrorTests(TestCase):
@patch.dict(os.environ, clear=True,
DJANGO_CONFIGURATION='ErrorConfiguration',
DJANGO_SETTINGS_MODULE='tests.settings.error')
def test_env_loaded(self):
with self.assertRaises(ValueError) as cm:
from tests.settings import error # noqa: F401
self.assertIsInstance(cm.exception, ValueError)
self.assertEqual(
cm.exception.args,
(
"Couldn't setup configuration "
"'tests.settings.error.ErrorConfiguration': Error in pre_setup ",
)
)

38
tests/test_inheritance.py Normal file
View file

@ -0,0 +1,38 @@
import os
from django.test import TestCase
from unittest.mock import patch
class InheritanceTests(TestCase):
@patch.dict(os.environ, clear=True,
DJANGO_CONFIGURATION='Inheritance',
DJANGO_SETTINGS_MODULE='tests.settings.single_inheritance')
def test_inherited(self):
from tests.settings import single_inheritance
self.assertEqual(
single_inheritance.ALLOWED_HOSTS,
['test']
)
@patch.dict(os.environ, clear=True,
DJANGO_CONFIGURATION='Inheritance',
DJANGO_SETTINGS_MODULE='tests.settings.multiple_inheritance')
def test_inherited2(self):
from tests.settings import multiple_inheritance
self.assertEqual(
multiple_inheritance.ALLOWED_HOSTS,
['test', 'test-test']
)
@patch.dict(os.environ, clear=True,
DJANGO_CONFIGURATION='Inheritance',
DJANGO_SETTINGS_MODULE='tests.settings.mixin_inheritance')
def test_inherited3(self):
from tests.settings import mixin_inheritance
self.assertEqual(
mixin_inheritance.ALLOWED_HOSTS,
['test1', 'test2', 'test3']
)

158
tests/test_main.py Normal file
View file

@ -0,0 +1,158 @@
import os
import subprocess
import sys
from django.test import TestCase
from django.core.exceptions import ImproperlyConfigured
from unittest.mock import patch
from configurations.importer import ConfigurationFinder
ROOT_DIR = os.path.dirname(os.path.dirname(__file__))
TEST_PROJECT_DIR = os.path.join(ROOT_DIR, 'test_project')
class MainTests(TestCase):
def test_simple(self):
from tests.settings import main
self.assertEqual(main.ATTRIBUTE_SETTING, True)
self.assertEqual(main.PROPERTY_SETTING, 1)
self.assertEqual(main.METHOD_SETTING, 2)
self.assertEqual(main.LAMBDA_SETTING, 3)
self.assertNotEqual(main.PRISTINE_LAMBDA_SETTING, 4)
self.assertTrue(lambda: callable(main.PRISTINE_LAMBDA_SETTING))
self.assertNotEqual(main.PRISTINE_FUNCTION_SETTING, 5)
self.assertTrue(lambda: callable(main.PRISTINE_FUNCTION_SETTING))
self.assertEqual(main.ALLOWED_HOSTS, ['base'])
self.assertEqual(main.PRE_SETUP_TEST_SETTING, 6)
self.assertRaises(AttributeError, lambda: main.POST_SETUP_TEST_SETTING)
self.assertEqual(main.Test.POST_SETUP_TEST_SETTING, 7)
def test_global_arrival(self):
from django.conf import settings
self.assertEqual(settings.PROPERTY_SETTING, 1)
self.assertRaises(AttributeError, lambda: settings._PRIVATE_SETTING)
self.assertNotEqual(settings.PRISTINE_LAMBDA_SETTING, 4)
self.assertTrue(lambda: callable(settings.PRISTINE_LAMBDA_SETTING))
self.assertNotEqual(settings.PRISTINE_FUNCTION_SETTING, 5)
self.assertTrue(lambda: callable(settings.PRISTINE_FUNCTION_SETTING))
self.assertEqual(settings.PRE_SETUP_TEST_SETTING, 6)
@patch.dict(os.environ, clear=True, DJANGO_CONFIGURATION='Test')
def test_empty_module_var(self):
with self.assertRaises(ImproperlyConfigured):
ConfigurationFinder()
@patch.dict(os.environ, clear=True,
DJANGO_SETTINGS_MODULE='tests.settings.main')
def test_empty_class_var(self):
with self.assertRaises(ImproperlyConfigured):
ConfigurationFinder()
def test_global_settings(self):
from configurations.base import Configuration
self.assertIn('dictConfig', Configuration.LOGGING_CONFIG)
self.assertEqual(repr(Configuration),
"<Configuration 'configurations.base.Configuration'>")
def test_deprecated_settings_but_set_by_user(self):
from tests.settings.main import TestWithDefaultSetExplicitely
TestWithDefaultSetExplicitely.setup()
self.assertEqual(TestWithDefaultSetExplicitely.DEFAULT_AUTO_FIELD,
"django.db.models.BigAutoField")
def test_repr(self):
from tests.settings.main import Test
self.assertEqual(repr(Test),
"<Configuration 'tests.settings.main.Test'>")
@patch.dict(os.environ, clear=True,
DJANGO_SETTINGS_MODULE='tests.settings.main',
DJANGO_CONFIGURATION='Test')
def test_initialization(self):
finder = ConfigurationFinder()
self.assertEqual(finder.module, 'tests.settings.main')
self.assertEqual(finder.name, 'Test')
self.assertEqual(
repr(finder),
"<ConfigurationFinder for 'tests.settings.main.Test'>")
@patch.dict(os.environ, clear=True,
DJANGO_SETTINGS_MODULE='tests.settings.inheritance',
DJANGO_CONFIGURATION='Inheritance')
def test_initialization_inheritance(self):
finder = ConfigurationFinder()
self.assertEqual(finder.module,
'tests.settings.inheritance')
self.assertEqual(finder.name, 'Inheritance')
@patch.dict(os.environ, clear=True,
DJANGO_SETTINGS_MODULE='tests.settings.main',
DJANGO_CONFIGURATION='NonExisting')
@patch.object(sys, 'argv', ['python', 'manage.py', 'test',
'--settings=tests.settings.main',
'--configuration=Test'])
def test_configuration_option(self):
finder = ConfigurationFinder(check_options=False)
self.assertEqual(finder.module, 'tests.settings.main')
self.assertEqual(finder.name, 'NonExisting')
finder = ConfigurationFinder(check_options=True)
self.assertEqual(finder.module, 'tests.settings.main')
self.assertEqual(finder.name, 'Test')
def test_configuration_argument_in_cli(self):
"""
Verify that's configuration option has been added to managements
commands
"""
proc = subprocess.Popen(['django-cadmin', 'test', '--help'],
stdout=subprocess.PIPE)
self.assertIn('--configuration', proc.communicate()[0].decode('utf-8'))
proc = subprocess.Popen(['django-cadmin', 'runserver', '--help'],
stdout=subprocess.PIPE)
self.assertIn('--configuration', proc.communicate()[0].decode('utf-8'))
def test_configuration_argument_in_runypy_cli(self):
"""
Verify that's configuration option has been added to managements
commands when using the -m entry point
"""
proc = subprocess.Popen(
[sys.executable, '-m', 'configurations', 'test', '--help'],
stdout=subprocess.PIPE,
)
self.assertIn('--configuration', proc.communicate()[0].decode('utf-8'))
proc = subprocess.Popen(
[sys.executable, '-m', 'configurations', 'runserver', '--help'],
stdout=subprocess.PIPE,
)
self.assertIn('--configuration', proc.communicate()[0].decode('utf-8'))
def test_django_setup_only_called_once(self):
proc = subprocess.Popen(
[sys.executable, os.path.join(os.path.dirname(__file__),
'setup_test.py')],
stdout=subprocess.PIPE)
res = proc.communicate()
stdout = res[0].decode('utf-8')
self.assertIn('setup_1', stdout)
self.assertIn('setup_2', stdout)
self.assertIn('setup_done', stdout)
self.assertEqual(proc.returncode, 0)
def test_utils_reraise(self):
from configurations.utils import reraise
class CustomException(Exception):
pass
with self.assertRaises(CustomException) as cm:
try:
raise CustomException
except Exception as exc:
reraise(exc, "Couldn't setup configuration", None)
self.assertEqual(cm.exception.args, ("Couldn't setup configuration: ",))

528
tests/test_values.py Normal file
View file

@ -0,0 +1,528 @@
import decimal
import os
from contextlib import contextmanager
from django import VERSION as DJANGO_VERSION
from django.test import TestCase
from django.core.exceptions import ImproperlyConfigured
from unittest.mock import patch
from configurations.values import (Value, BooleanValue, IntegerValue,
FloatValue, DecimalValue, ListValue,
TupleValue, SingleNestedTupleValue,
SingleNestedListValue, SetValue,
DictValue, URLValue, EmailValue, IPValue,
RegexValue, PathValue, SecretValue,
DatabaseURLValue, EmailURLValue,
CacheURLValue, BackendsValue,
CastingMixin, SearchURLValue,
setup_value, PositiveIntegerValue)
@contextmanager
def env(**kwargs):
with patch.dict(os.environ, clear=True, **kwargs):
yield
class FailingCasterValue(CastingMixin, Value):
caster = 'non.existing.caster'
class ValueTests(TestCase):
def test_value_with_default(self):
value = Value('default', environ=False)
self.assertEqual(type(value), str)
self.assertEqual(value, 'default')
self.assertEqual(str(value), 'default')
def test_value_with_default_and_late_binding(self):
value = Value('default', environ=False, late_binding=True)
self.assertEqual(type(value), Value)
with env(DJANGO_TEST='override'):
self.assertEqual(value.setup('TEST'), 'default')
value = Value(environ_name='TEST')
self.assertEqual(type(value), str)
self.assertEqual(value, 'override')
self.assertEqual(str(value), 'override')
self.assertEqual(f'{value}', 'override')
self.assertEqual('%s' % value, 'override')
value = Value(environ_name='TEST', late_binding=True)
self.assertEqual(type(value), Value)
self.assertEqual(value.value, 'override')
self.assertEqual(str(value), 'override')
self.assertEqual(f'{value}', 'override')
self.assertEqual('%s' % value, 'override')
self.assertEqual(repr(value), repr('override'))
def test_value_truthy(self):
value = Value('default')
self.assertTrue(bool(value))
def test_value_falsey(self):
value = Value()
self.assertFalse(bool(value))
@patch.dict(os.environ, clear=True, DJANGO_TEST='override')
def test_env_var(self):
value = Value('default')
self.assertEqual(value.setup('TEST'), 'override')
self.assertEqual(str(value), 'override')
self.assertNotEqual(value.setup('TEST'), value.default)
self.assertEqual(value.to_python(os.environ['DJANGO_TEST']),
value.setup('TEST'))
def test_value_reuse(self):
value1 = Value('default')
value2 = Value(value1)
self.assertEqual(value1.setup('TEST1'), 'default')
self.assertEqual(value2.setup('TEST2'), 'default')
with env(DJANGO_TEST1='override1', DJANGO_TEST2='override2'):
self.assertEqual(value1.setup('TEST1'), 'override1')
self.assertEqual(value2.setup('TEST2'), 'override2')
def test_value_var_equal(self):
value1 = Value('default')
value2 = Value('default')
self.assertEqual(value1, value2)
self.assertTrue(value1 in ['default'])
def test_env_var_prefix(self):
with patch.dict(os.environ, clear=True, ACME_TEST='override'):
value = Value('default', environ_prefix='ACME')
self.assertEqual(value.setup('TEST'), 'override')
with patch.dict(os.environ, clear=True, TEST='override'):
value = Value('default', environ_prefix='')
self.assertEqual(value.setup('TEST'), 'override')
with patch.dict(os.environ, clear=True, ACME_TEST='override'):
value = Value('default', environ_prefix='ACME_')
self.assertEqual(value.setup('TEST'), 'override')
def test_boolean_values_true(self):
value = BooleanValue(False)
for truthy in value.true_values:
with env(DJANGO_TEST=truthy):
self.assertTrue(bool(value.setup('TEST')))
def test_boolean_values_faulty(self):
self.assertRaises(ValueError, BooleanValue, 'false')
def test_boolean_values_false(self):
value = BooleanValue(True)
for falsy in value.false_values:
with env(DJANGO_TEST=falsy):
self.assertFalse(bool(value.setup('TEST')))
def test_boolean_values_nonboolean(self):
value = BooleanValue(True)
with env(DJANGO_TEST='nonboolean'):
self.assertRaises(ValueError, value.setup, 'TEST')
def test_boolean_values_assign_false_to_another_booleanvalue(self):
value1 = BooleanValue(False)
value2 = BooleanValue(value1)
self.assertFalse(value1.setup('TEST1'))
self.assertFalse(value2.setup('TEST2'))
def test_integer_values(self):
value = IntegerValue(1)
with env(DJANGO_TEST='2'):
self.assertEqual(value.setup('TEST'), 2)
with env(DJANGO_TEST='noninteger'):
self.assertRaises(ValueError, value.setup, 'TEST')
def test_positive_integer_values(self):
value = PositiveIntegerValue(1)
with env(DJANGO_TEST='2'):
self.assertEqual(value.setup('TEST'), 2)
with env(DJANGO_TEST='noninteger'):
self.assertRaises(ValueError, value.setup, 'TEST')
with env(DJANGO_TEST='-1'):
self.assertRaises(ValueError, value.setup, 'TEST')
def test_float_values(self):
value = FloatValue(1.0)
with env(DJANGO_TEST='2.0'):
self.assertEqual(value.setup('TEST'), 2.0)
with env(DJANGO_TEST='noninteger'):
self.assertRaises(ValueError, value.setup, 'TEST')
def test_decimal_values(self):
value = DecimalValue(decimal.Decimal(1))
with env(DJANGO_TEST='2'):
self.assertEqual(value.setup('TEST'), decimal.Decimal(2))
with env(DJANGO_TEST='nondecimal'):
self.assertRaises(ValueError, value.setup, 'TEST')
def test_failing_caster(self):
self.assertRaises(ImproperlyConfigured, FailingCasterValue)
def test_list_values_default(self):
value = ListValue()
with env(DJANGO_TEST='2,2'):
self.assertEqual(value.setup('TEST'), ['2', '2'])
with env(DJANGO_TEST='2, 2 ,'):
self.assertEqual(value.setup('TEST'), ['2', '2'])
with env(DJANGO_TEST=''):
self.assertEqual(value.setup('TEST'), [])
def test_list_values_separator(self):
value = ListValue(separator=':')
with env(DJANGO_TEST='/usr/bin:/usr/sbin:/usr/local/bin'):
self.assertEqual(value.setup('TEST'),
['/usr/bin', '/usr/sbin', '/usr/local/bin'])
def test_List_values_converter(self):
value = ListValue(converter=int)
with env(DJANGO_TEST='2,2'):
self.assertEqual(value.setup('TEST'), [2, 2])
value = ListValue(converter=float)
with env(DJANGO_TEST='2,2'):
self.assertEqual(value.setup('TEST'), [2.0, 2.0])
def test_list_values_custom_converter(self):
value = ListValue(converter=lambda x: x * 2)
with env(DJANGO_TEST='2,2'):
self.assertEqual(value.setup('TEST'), ['22', '22'])
def test_list_values_converter_exception(self):
value = ListValue(converter=int)
with env(DJANGO_TEST='2,b'):
self.assertRaises(ValueError, value.setup, 'TEST')
def test_tuple_values_default(self):
value = TupleValue()
with env(DJANGO_TEST='2,2'):
self.assertEqual(value.setup('TEST'), ('2', '2'))
with env(DJANGO_TEST='2, 2 ,'):
self.assertEqual(value.setup('TEST'), ('2', '2'))
with env(DJANGO_TEST=''):
self.assertEqual(value.setup('TEST'), ())
def test_single_nested_list_values_default(self):
value = SingleNestedListValue()
with env(DJANGO_TEST='2,3;4,5'):
expected = [['2', '3'], ['4', '5']]
self.assertEqual(value.setup('TEST'), expected)
with env(DJANGO_TEST='2;3;4;5'):
expected = [['2'], ['3'], ['4'], ['5']]
self.assertEqual(value.setup('TEST'), expected)
with env(DJANGO_TEST='2,3,4,5'):
expected = [['2', '3', '4', '5']]
self.assertEqual(value.setup('TEST'), expected)
with env(DJANGO_TEST='2, 3 , ; 4 , 5 ; '):
expected = [['2', '3'], ['4', '5']]
self.assertEqual(value.setup('TEST'), expected)
with env(DJANGO_TEST=''):
self.assertEqual(value.setup('TEST'), [])
def test_single_nested_list_values_separator(self):
value = SingleNestedListValue(seq_separator=':')
with env(DJANGO_TEST='2,3:4,5'):
self.assertEqual(value.setup('TEST'), [['2', '3'], ['4', '5']])
def test_single_nested_list_values_converter(self):
value = SingleNestedListValue(converter=int)
with env(DJANGO_TEST='2,3;4,5'):
self.assertEqual(value.setup('TEST'), [[2, 3], [4, 5]])
def test_single_nested_list_values_converter_default(self):
value = SingleNestedListValue([['2', '3'], ['4', '5']], converter=int)
self.assertEqual(value.value, [[2, 3], [4, 5]])
def test_single_nested_tuple_values_default(self):
value = SingleNestedTupleValue()
with env(DJANGO_TEST='2,3;4,5'):
expected = (('2', '3'), ('4', '5'))
self.assertEqual(value.setup('TEST'), expected)
with env(DJANGO_TEST='2;3;4;5'):
expected = (('2',), ('3',), ('4',), ('5',))
self.assertEqual(value.setup('TEST'), expected)
with env(DJANGO_TEST='2,3,4,5'):
expected = (('2', '3', '4', '5'),)
self.assertEqual(value.setup('TEST'), expected)
with env(DJANGO_TEST='2, 3 , ; 4 , 5 ; '):
expected = (('2', '3'), ('4', '5'))
self.assertEqual(value.setup('TEST'), expected)
with env(DJANGO_TEST=''):
self.assertEqual(value.setup('TEST'), ())
def test_single_nested_tuple_values_separator(self):
value = SingleNestedTupleValue(seq_separator=':')
with env(DJANGO_TEST='2,3:4,5'):
self.assertEqual(value.setup('TEST'), (('2', '3'), ('4', '5')))
def test_single_nested_tuple_values_converter(self):
value = SingleNestedTupleValue(converter=int)
with env(DJANGO_TEST='2,3;4,5'):
self.assertEqual(value.setup('TEST'), ((2, 3), (4, 5)))
def test_single_nested_tuple_values_converter_default(self):
value = SingleNestedTupleValue((('2', '3'), ('4', '5')), converter=int)
self.assertEqual(value.value, ((2, 3), (4, 5)))
def test_set_values_default(self):
value = SetValue()
with env(DJANGO_TEST='2,2'):
self.assertEqual(value.setup('TEST'), {'2', '2'})
with env(DJANGO_TEST='2, 2 ,'):
self.assertEqual(value.setup('TEST'), {'2', '2'})
with env(DJANGO_TEST=''):
self.assertEqual(value.setup('TEST'), set())
def test_dict_values_default(self):
value = DictValue()
with env(DJANGO_TEST='{2: 2}'):
self.assertEqual(value.setup('TEST'), {2: 2})
expected = {2: 2, '3': '3', '4': [1, 2, 3]}
with env(DJANGO_TEST="{2: 2, '3': '3', '4': [1, 2, 3]}"):
self.assertEqual(value.setup('TEST'), expected)
with env(DJANGO_TEST="""{
2: 2,
'3': '3',
'4': [1, 2, 3],
}"""):
self.assertEqual(value.setup('TEST'), expected)
with env(DJANGO_TEST=''):
self.assertEqual(value.setup('TEST'), {})
with env(DJANGO_TEST='spam'):
self.assertRaises(ValueError, value.setup, 'TEST')
def test_email_values(self):
value = EmailValue('spam@eg.gs')
with env(DJANGO_TEST='spam@sp.am'):
self.assertEqual(value.setup('TEST'), 'spam@sp.am')
with env(DJANGO_TEST='spam'):
self.assertRaises(ValueError, value.setup, 'TEST')
def test_url_values(self):
value = URLValue('http://eggs.spam')
with env(DJANGO_TEST='http://spam.eggs'):
self.assertEqual(value.setup('TEST'), 'http://spam.eggs')
with env(DJANGO_TEST='httb://spam.eggs'):
self.assertRaises(ValueError, value.setup, 'TEST')
def test_url_values_with_no_default(self):
value = URLValue() # no default
with env(DJANGO_TEST='http://spam.eggs'):
self.assertEqual(value.setup('TEST'), 'http://spam.eggs')
def test_url_values_with_wrong_default(self):
self.assertRaises(ValueError, URLValue, 'httb://spam.eggs')
def test_ip_values(self):
value = IPValue('0.0.0.0')
with env(DJANGO_TEST='127.0.0.1'):
self.assertEqual(value.setup('TEST'), '127.0.0.1')
with env(DJANGO_TEST='::1'):
self.assertEqual(value.setup('TEST'), '::1')
with env(DJANGO_TEST='spam.eggs'):
self.assertRaises(ValueError, value.setup, 'TEST')
def test_regex_values(self):
value = RegexValue('000--000', regex=r'\d+--\d+')
with env(DJANGO_TEST='123--456'):
self.assertEqual(value.setup('TEST'), '123--456')
with env(DJANGO_TEST='123456'):
self.assertRaises(ValueError, value.setup, 'TEST')
def test_path_values_with_check(self):
value = PathValue()
with env(DJANGO_TEST='/'):
self.assertEqual(value.setup('TEST'), '/')
with env(DJANGO_TEST='~/'):
self.assertEqual(value.setup('TEST'), os.path.expanduser('~'))
with env(DJANGO_TEST='/does/not/exist'):
self.assertRaises(ValueError, value.setup, 'TEST')
def test_path_values_no_check(self):
value = PathValue(check_exists=False)
with env(DJANGO_TEST='/'):
self.assertEqual(value.setup('TEST'), '/')
with env(DJANGO_TEST='~/spam/eggs'):
self.assertEqual(value.setup('TEST'),
os.path.join(os.path.expanduser('~'),
'spam', 'eggs'))
with env(DJANGO_TEST='/does/not/exist'):
self.assertEqual(value.setup('TEST'), '/does/not/exist')
def test_secret_value(self):
# no default allowed, only environment values are
self.assertRaises(ValueError, SecretValue, 'default')
value = SecretValue()
self.assertRaises(ValueError, value.setup, 'TEST')
with env(DJANGO_SECRET_KEY='123'):
self.assertEqual(value.setup('SECRET_KEY'), '123')
value = SecretValue(environ_name='FACEBOOK_API_SECRET',
environ_prefix=None,
late_binding=True)
self.assertRaises(ValueError, value.setup, 'TEST')
with env(FACEBOOK_API_SECRET='123'):
self.assertEqual(value.setup('TEST'), '123')
def test_database_url_value(self):
value = DatabaseURLValue()
self.assertEqual(value.default, {})
with env(DATABASE_URL='sqlite://'):
settings_value = value.setup('DATABASE_URL')
# Compare the embedded dicts in the "default" entry so that the difference can be seen if
# it fails ... DatabaseURLValue(|) uses an external app that can add additional entries
self.assertDictEqual(
{
'CONN_HEALTH_CHECKS': False,
'CONN_MAX_AGE': 0,
'DISABLE_SERVER_SIDE_CURSORS': False,
'ENGINE': 'django.db.backends.sqlite3',
'HOST': '',
'NAME': ':memory:',
'PASSWORD': '',
'PORT': '',
'USER': '',
},
settings_value['default']
)
def test_database_url_additional_args(self):
def mock_database_url_caster(self, url, engine=None):
return {'URL': url, 'ENGINE': engine}
with patch('configurations.values.DatabaseURLValue.caster',
mock_database_url_caster):
value = DatabaseURLValue(
engine='django_mysqlpool.backends.mysqlpool')
with env(DATABASE_URL='sqlite://'):
self.assertEqual(value.setup('DATABASE_URL'), {
'default': {
'URL': 'sqlite://',
'ENGINE': 'django_mysqlpool.backends.mysqlpool'
}
})
def test_email_url_value(self):
value = EmailURLValue()
self.assertEqual(value.default, {})
with env(EMAIL_URL='smtps://user@domain.com:password@smtp.example.com:587'): # noqa: E501
self.assertEqual(value.setup('EMAIL_URL'), {
'EMAIL_BACKEND': 'django.core.mail.backends.smtp.EmailBackend',
'EMAIL_FILE_PATH': '',
'EMAIL_HOST': 'smtp.example.com',
'EMAIL_HOST_PASSWORD': 'password',
'EMAIL_HOST_USER': 'user@domain.com',
'EMAIL_PORT': 587,
'EMAIL_TIMEOUT': None,
'EMAIL_USE_SSL': False,
'EMAIL_USE_TLS': True})
with env(EMAIL_URL='console://'):
self.assertEqual(value.setup('EMAIL_URL'), {
'EMAIL_BACKEND': 'django.core.mail.backends.console.EmailBackend', # noqa: E501
'EMAIL_FILE_PATH': '',
'EMAIL_HOST': None,
'EMAIL_HOST_PASSWORD': None,
'EMAIL_HOST_USER': None,
'EMAIL_PORT': None,
'EMAIL_TIMEOUT': None,
'EMAIL_USE_SSL': False,
'EMAIL_USE_TLS': False})
with env(EMAIL_URL='smtps://user@domain.com:password@smtp.example.com:wrong'): # noqa: E501
self.assertRaises(ValueError, value.setup, 'TEST')
def test_cache_url_value(self):
cache_setting = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache' if DJANGO_VERSION < (4,) else 'django.core.cache.backends.redis.RedisCache', # noqa: E501
'LOCATION': 'redis://host:6379/1',
}
}
cache_url = 'redis://user@host:6379/1'
value = CacheURLValue(cache_url)
self.assertEqual(value.default, cache_setting)
value = CacheURLValue()
self.assertEqual(value.default, {})
with env(CACHE_URL='redis://user@host:6379/1'):
self.assertEqual(value.setup('CACHE_URL'), cache_setting)
with env(CACHE_URL='wrong://user@host:port/1'):
with self.assertRaises(Exception) as cm:
value.setup('TEST')
self.assertEqual(cm.exception.args[0], 'Unknown backend: "wrong"')
with env(CACHE_URL='redis://user@host:port/1'):
with self.assertRaises(ValueError) as cm:
value.setup('TEST')
self.assertEqual(
cm.exception.args[0],
"Cannot interpret cache URL value 'redis://user@host:port/1'")
def test_search_url_value(self):
value = SearchURLValue()
self.assertEqual(value.default, {})
with env(SEARCH_URL='elasticsearch://127.0.0.1:9200/index'):
self.assertEqual(value.setup('SEARCH_URL'), {
'default': {
'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine', # noqa: E501
'URL': 'http://127.0.0.1:9200',
'INDEX_NAME': 'index',
}})
def test_backend_list_value(self):
backends = ['django.middleware.common.CommonMiddleware']
value = BackendsValue(backends)
self.assertEqual(value.setup('TEST'), backends)
backends = ['non.existing.Backend']
self.assertRaises(ValueError, BackendsValue, backends)
def test_tuple_value(self):
value = TupleValue(None)
self.assertEqual(value.default, ())
self.assertEqual(value.value, ())
value = TupleValue((1, 2))
self.assertEqual(value.default, (1, 2))
self.assertEqual(value.value, (1, 2))
def test_set_value(self):
value = SetValue()
self.assertEqual(value.default, set())
self.assertEqual(value.value, set())
value = SetValue([1, 2])
self.assertEqual(value.default, {1, 2})
self.assertEqual(value.value, {1, 2})
def test_setup_value(self):
class Target:
pass
value = EmailURLValue()
with env(EMAIL_URL='smtps://user@domain.com:password@smtp.example.com:587'): # noqa: E501
setup_value(Target, 'EMAIL', value)
self.assertEqual(Target.EMAIL, {
'EMAIL_BACKEND': 'django.core.mail.backends.smtp.EmailBackend',
'EMAIL_FILE_PATH': '',
'EMAIL_HOST': 'smtp.example.com',
'EMAIL_HOST_PASSWORD': 'password',
'EMAIL_HOST_USER': 'user@domain.com',
'EMAIL_PORT': 587,
'EMAIL_TIMEOUT': None,
'EMAIL_USE_SSL': False,
'EMAIL_USE_TLS': True
})
self.assertEqual(
Target.EMAIL_BACKEND,
'django.core.mail.backends.smtp.EmailBackend')
self.assertEqual(Target.EMAIL_FILE_PATH, '')
self.assertEqual(Target.EMAIL_HOST, 'smtp.example.com')
self.assertEqual(Target.EMAIL_HOST_PASSWORD, 'password')
self.assertEqual(Target.EMAIL_HOST_USER, 'user@domain.com')
self.assertEqual(Target.EMAIL_PORT, 587)
self.assertEqual(Target.EMAIL_USE_TLS, True)

2
tests/urls.py Normal file
View file

@ -0,0 +1,2 @@
urlpatterns = [
]

72
tox.ini Normal file
View file

@ -0,0 +1,72 @@
[tox]
skipsdist = true
usedevelop = true
minversion = 1.8
envlist =
py311-checkqa
docs
py{39}-dj{32,41,42}
py{310,py310}-dj{32,41,42,50,main}
py{311}-dj{41,42,50,51,main}
py{312}-dj{50,51,main}
py{313}-dj{50,51,main}
[gh-actions]
python =
3.9: py39
3.10: py310
3.11: py311,flake8,readme
3.12: py312
3.13: py313
pypy-3.10: pypy310
[testenv]
usedevelop = true
setenv =
DJANGO_SETTINGS_MODULE = tests.settings.main
DJANGO_CONFIGURATION = Test
COVERAGE_PROCESS_START = {toxinidir}/setup.cfg
deps =
dj32: django~=3.2.9
dj41: django~=4.1.3
dj42: django~=4.2.0
dj50: django~=5.0.0
dj51: django~=5.1.0
djmain: https://github.com/django/django/archive/main.tar.gz
py312: setuptools
py312: wheel
py313: setuptools
py313: wheel
coverage
coverage_enable_subprocess
extras = testing
commands =
python --version
{envbindir}/coverage run {envbindir}/django-cadmin test -v2 {posargs:tests}
coverage combine . tests docs
coverage report -m --skip-covered
coverage xml
[testenv:py311-checkqa]
commands =
flake8 {toxinidir}
check-manifest -v
python setup.py sdist
twine check dist/*
deps =
flake8
twine
check-manifest
[testenv:docs]
setenv =
deps =
-r docs/requirements.txt
commands =
sphinx-build \
-b html \
-a \
-W \
-n \
docs \
docs/_build/html