Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-django-environ for
openSUSE:Factory checked in at 2026-03-23 17:11:38
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-django-environ (Old)
and /work/SRC/openSUSE:Factory/.python-django-environ.new.8177 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-django-environ"
Mon Mar 23 17:11:38 2026 rev:9 rq:1341706 version:0.13.0
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-django-environ/python-django-environ.changes
2026-02-17 16:47:59.393044140 +0100
+++
/work/SRC/openSUSE:Factory/.python-django-environ.new.8177/python-django-environ.changes
2026-03-23 17:11:57.706164012 +0100
@@ -1,0 +2,15 @@
+Sat Mar 21 14:44:05 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 0.13.0:
+ * Added optional warnings when defaults are used #582.
+ * Added choices argument support for value validation in
+ Env.str(...) #555.
+ * Added Valkey support via valkey:// and valkeys:// cache URL
+ schemes #554.
+ * Added support for rediss:// scheme in channels URL parsing
+ #573.
+ * Added django-prometheus database backend aliases to DB URL
+ parsing schemes #559.
+ * Declared support for Python 3.14 #580.
+
+-------------------------------------------------------------------
Old:
----
django_environ-0.12.1.tar.gz
New:
----
django_environ-0.13.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-django-environ.spec ++++++
--- /var/tmp/diff_new_pack.0nZDv1/_old 2026-03-23 17:11:58.338190336 +0100
+++ /var/tmp/diff_new_pack.0nZDv1/_new 2026-03-23 17:11:58.338190336 +0100
@@ -18,7 +18,7 @@
%{?sle15_python_module_pythons}
Name: python-django-environ
-Version: 0.12.1
+Version: 0.13.0
Release: 0
Summary: Django application configuration via environment variables
License: MIT
++++++ django_environ-0.12.1.tar.gz -> django_environ-0.13.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django_environ-0.12.1/.github/copilot-instructions.md
new/django_environ-0.13.0/.github/copilot-instructions.md
--- old/django_environ-0.12.1/.github/copilot-instructions.md 1970-01-01
01:00:00.000000000 +0100
+++ new/django_environ-0.13.0/.github/copilot-instructions.md 2026-02-17
22:58:34.000000000 +0100
@@ -0,0 +1,44 @@
+# Copilot instructions for `django-environ`
+
+## Build, test, and lint commands
+
+- Install local dev dependencies:
+ - `python3 -m venv .venv && . .venv/bin/activate`
+ - `python -m pip install -U pip tox tox-gh-actions setuptools`
+- Run the default local test workflow (matches CI entrypoint): `tox`
+- Run one tox environment explicitly (recommended for iteration): `tox -e
py312-django51`
+- Run a single test: `tox -e py312-django51 --
tests/test_env.py::TestEnv::test_int`
+- Lint: `tox -e lint`
+- Build/package checks: `tox -e build`
+- Docs and doctests: `tox -e docs`
+- Link checks in docs: `tox -e linkcheck`
+- Manifest validation: `tox -e manifest`
+- Coverage combine/report after test runs: `tox -e coverage-report`
+- Locally ZSH is used, keep in mind when you executing commands (like commits
messages using back-ticks)
+- For GitHub CLI commands that can invoke a pager (`gh pr view`, `gh issue
view`, etc.), use `GH_PAGER=cat` to avoid interactive blocking.
+
+## High-level architecture
+
+- Core implementation is concentrated in `environ/environ.py`:
+ - `Env` provides typed env access (`env()`, `env.bool()`, `env.db()`,
`env.cache()`, etc.).
+ - URL-to-Django-settings parsing is centralized in class methods and scheme
maps (`DB_SCHEMES`, `CACHE_SCHEMES`, `EMAIL_SCHEMES`, `SEARCH_SCHEMES`,
`CHANNELS_SCHEMES`).
+ - `Env.read_env()` loads `.env` values into `os.environ` using `setdefault`
semantics unless `overwrite=True`.
+ - `Path` is a small path helper used by users in Django settings modules.
+- `FileAwareEnv` (in `environ/environ.py`) swaps `ENVIRON` to
`FileAwareMapping` (`environ/fileaware_mapping.py`) so `FOO_FILE` takes
precedence over `FOO`.
+- Compatibility/backward-driver selection lives in `environ/compat.py`
(Django/redis/postgres/pymemcache driver decisions).
+- Public package API is exported via `environ/__init__.py` (`from .environ
import *`), and package metadata/version are also defined there and reused by
`setup.py`.
+- Tests are behavior-first and organized by parser domain (`tests/test_db.py`,
`tests/test_cache.py`, `tests/test_email.py`, `tests/test_search.py`, etc.).
+
+## Key repository conventions
+
+- Keep changes small and targeted; this project expects focused PRs against
`develop` (see `CONTRIBUTING.rst`).
+- For public-facing text (e.g., commit messages, PR/issue comments), always
write in English.
+- Use `develop` as the operational integration branch; do not create extra
release/aggregation branches unless explicitly requested.
+- When adding/changing URL parsing behavior, update both the relevant scheme
map(s) in `Env` and matching domain tests under `tests/`.
+- Preserve `.env` loading behavior in `read_env()`:
+ - default is non-overwriting (`setdefault`)
+ - comments parsing is opt-in (`parse_comments=True`)
+ - invalid lines should warn rather than crash.
+- Tests often isolate environment state by replacing `os.environ` /
`Env.ENVIRON` with fixtures (for example `FakeEnv.generate_data()`); follow
that pattern for deterministic tests.
+- CI matrix is driven by tox env naming (`py<python>-django<version>`), with
mapping in `[gh-actions]` in `tox.ini`; keep new env names consistent there.
+- For docs tasks, keep Python version assumptions aligned between tox docs
envs and GitHub workflows (`docs.yml`, comments in `tox.ini`).
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django_environ-0.12.1/.github/skills/django-environ-release/SKILL.md
new/django_environ-0.13.0/.github/skills/django-environ-release/SKILL.md
--- old/django_environ-0.12.1/.github/skills/django-environ-release/SKILL.md
1970-01-01 01:00:00.000000000 +0100
+++ new/django_environ-0.13.0/.github/skills/django-environ-release/SKILL.md
2026-02-18 01:49:59.000000000 +0100
@@ -0,0 +1,144 @@
+---
+name: django-environ-release
+description: Run and validate the django-environ release workflow (prepare,
verify, and publish).
+---
+
+# django-environ release skill
+
+Use this skill when preparing a new django-environ release (for example
`0.12.2`).
+
+## Prerequisites
+
+- Work from `develop` unless maintainers explicitly choose another branch.
+- Ensure local branch is up to date:
+
+```bash
+git checkout develop
+git fetch origin
+git pull --ff-only origin develop
+```
+
+- Confirm working tree is clean for tracked files.
+
+## 1) Update version and release notes
+
+- Bump version in `environ/__init__.py` (`__version__`).
+- Update `CHANGELOG.rst` with release entry and key changes mentioning
contributors if possibile.
+- If needed, align `RELEASE_NOTES.md` / roadmap references.
+
+## 2) Validate locally
+
+Run the project checks used by CI/release flow:
+
+```bash
+tox -e lint
+tox -e build
+pytest -q
+```
+
+Optional (recommended):
+
+```bash
+tox -e docs
+tox -e manifest
+```
+
+## 3) Commit and push
+
+- Create focused commits (version bump and notes can be split if useful).
+- Include Co-authored-by trailer when required by project policy.
+- Push branch and open PR targeting `develop`.
+
+## 4) Release readiness checklist
+
+- [ ] Version bumped and consistent
+- [ ] Changelog updated
+- [ ] Build artifacts pass (`tox -e build`)
+- [ ] Tests pass (`pytest -q`)
+- [ ] Lint passes (`tox -e lint`)
+- [ ] Docs build passes (`tox -e docs`, if doc changes)
+- [ ] PR merged into `develop`
+
+## 5) Publish (maintainer step)
+
+When maintainers are ready to publish:
+
+- Create/tag release commit for the new version.
+- Trigger release pipeline / upload to PyPI according to current maintainer
process.
+- Announce release with concise highlights.
+
+## 6) Copy/paste command blocks (required output)
+
+When using this skill, always provide a ready-to-run command list for:
+- merge into `main`
+- build artifacts
+- upload to TestPyPI
+- install from TestPyPI and run smoke test
+- upload to production PyPI
+
+Use command blocks like these (adjust version/tag as needed):
+
+```bash
+# Merge develop into main and push
+git checkout main
+git fetch origin
+git pull --ff-only origin main
+git merge --ff-only origin/develop
+git push origin main
+```
+
+```bash
+# Build distribution artifacts
+python -m pip install -U pip build twine
+rm -rf dist/ build/ *.egg-info
+python -m build
+twine check dist/*
+```
+
+```bash
+# Upload to TestPyPI
+python -m twine upload --repository testpypi dist/*
+```
+
+```bash
+# Install from TestPyPI and run smoke test
+python -m venv /tmp/django-environ-smoke
+. /tmp/django-environ-smoke/bin/activate
+python -m pip install -U pip
+python -m pip install --index-url https://test.pypi.org/simple/
--extra-index-url https://pypi.org/simple django-environ==<VERSION>
+python - <<'PY'
+import environ
+print(environ.__version__)
+env = environ.Env()
+print(env.str("DJANGO_ENV", default="ok"))
+PY
+deactivate
+```
+
+```bash
+# Upload to production PyPI
+python -m twine upload --repository django-environ dist/*
+```
+
+## 7) Social announcement draft (Twitter/X)
+
+At the end of the release flow, also generate a short Twitter/X draft
+highlighting the most interesting changes (max 280 chars), for example:
+
+```text
+django-environ v<VERSION> is out 🎉
+Highlights:
+✅ Optional warnings when defaults are used
+✅ New `choices` support in `Env.str(...)`
+✅ Valkey + rediss improvements
+✅ Docs improvements (including code copy button)
+
+Upgrade: https://pypi.org/project/django-environ/
+#django #python
+```
+
+## Notes specific to this repository
+
+- CI matrix is tox-driven (`tox.ini`).
+- Packaging is currently setup.py-based; v1.0 roadmap includes migration to
`pyproject.toml` (see #570 reference tasks).
+- Keep release changes minimal and avoid unrelated refactors.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_environ-0.12.1/.github/workflows/build.yml
new/django_environ-0.13.0/.github/workflows/build.yml
--- old/django_environ-0.12.1/.github/workflows/build.yml 2026-02-14
01:37:35.000000000 +0100
+++ new/django_environ-0.13.0/.github/workflows/build.yml 2026-02-17
22:04:46.000000000 +0100
@@ -24,10 +24,10 @@
steps:
- name: Checkout code
- uses: actions/[email protected]
+ uses: actions/[email protected]
- name: Set up Python 3.12
- uses: actions/[email protected]
+ uses: actions/[email protected]
with:
python-version: '3.12'
@@ -64,10 +64,10 @@
steps:
- name: Checkout code
- uses: actions/[email protected]
+ uses: actions/[email protected]
- name: Set up Python 3.12
- uses: actions/[email protected]
+ uses: actions/[email protected]
with:
python-version: '3.12'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_environ-0.12.1/.github/workflows/ci.yml
new/django_environ-0.13.0/.github/workflows/ci.yml
--- old/django_environ-0.12.1/.github/workflows/ci.yml 2025-01-13
16:27:16.000000000 +0100
+++ new/django_environ-0.13.0/.github/workflows/ci.yml 2026-02-17
22:04:46.000000000 +0100
@@ -52,17 +52,18 @@
- '3.11'
- '3.12'
- '3.13'
+ - '3.14'
- 'pypy-3.10'
os: [ ubuntu-latest, macos-latest, windows-latest ]
steps:
- name: Checkout code
- uses: actions/[email protected]
+ uses: actions/[email protected]
with:
fetch-depth: 5
- name: Set up Python ${{ matrix.python }}
- uses: actions/[email protected]
+ uses: actions/[email protected]
with:
python-version: ${{ matrix.python }}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_environ-0.12.1/.github/workflows/codeql.yml
new/django_environ-0.13.0/.github/workflows/codeql.yml
--- old/django_environ-0.12.1/.github/workflows/codeql.yml 2025-01-13
16:27:16.000000000 +0100
+++ new/django_environ-0.13.0/.github/workflows/codeql.yml 2026-02-17
22:04:46.000000000 +0100
@@ -45,16 +45,16 @@
steps:
- name: Checkout repository
- uses: actions/[email protected]
+ uses: actions/[email protected]
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@v3
+ uses: github/codeql-action/[email protected]
with:
languages: ${{ matrix.language }}
- name: Autobuild
- uses: github/codeql-action/autobuild@v3
+ uses: github/codeql-action/[email protected]
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v3
+ uses: github/codeql-action/[email protected]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_environ-0.12.1/.github/workflows/cs.yml
new/django_environ-0.13.0/.github/workflows/cs.yml
--- old/django_environ-0.12.1/.github/workflows/cs.yml 2025-01-13
16:27:16.000000000 +0100
+++ new/django_environ-0.13.0/.github/workflows/cs.yml 2026-02-17
22:04:46.000000000 +0100
@@ -25,10 +25,10 @@
steps:
- name: Checkout code
- uses: actions/[email protected]
+ uses: actions/[email protected]
- name: Set up Python 3.12
- uses: actions/[email protected]
+ uses: actions/[email protected]
with:
python-version: '3.12'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_environ-0.12.1/.github/workflows/docs.yml
new/django_environ-0.13.0/.github/workflows/docs.yml
--- old/django_environ-0.12.1/.github/workflows/docs.yml 2025-01-13
16:27:16.000000000 +0100
+++ new/django_environ-0.13.0/.github/workflows/docs.yml 2026-02-17
22:04:46.000000000 +0100
@@ -26,10 +26,10 @@
steps:
- name: Checkout code
- uses: actions/[email protected]
+ uses: actions/[email protected]
- name: Set up Python 3.12
- uses: actions/[email protected]
+ uses: actions/[email protected]
with:
python-version: '3.12'
@@ -46,7 +46,7 @@
- name: Archive docs artifacts
if: always()
- uses: actions/upload-artifact@v4
+ uses: actions/[email protected]
with:
name: docs
path: docs
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_environ-0.12.1/BACKERS.rst
new/django_environ-0.13.0/BACKERS.rst
--- old/django_environ-0.12.1/BACKERS.rst 2026-02-14 01:37:35.000000000
+0100
+++ new/django_environ-0.13.0/BACKERS.rst 2026-02-18 01:22:58.000000000
+0100
@@ -11,7 +11,7 @@
Support this project by becoming a sponsor. Your logo will show up here with a
link to your website. `Became sponsor
<https://opencollective.com/django-environ/contribute/sponsors-3474/checkout>`_.
-|ocsponsor0| |ocsponsor1|
+|ocsponsor|
Backers
-------
@@ -20,10 +20,7 @@
|ocbackerimage|
-.. |ocsponsor0| image::
https://opencollective.com/django-environ/sponsor/0/avatar.svg
- :target: https://opencollective.com/triplebyte
- :alt: Sponsor
-.. |ocsponsor1| image::
https://opencollective.com/static/images/become_sponsor.svg
+.. |ocsponsor| image::
https://opencollective.com/static/images/become_sponsor.svg
:target:
https://opencollective.com/django-environ/contribute/sponsors-3474/checkout
:alt: Become a Sponsor
.. |ocbackerimage| image::
https://opencollective.com/django-environ/backers.svg?width=890
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_environ-0.12.1/CHANGELOG.rst
new/django_environ-0.13.0/CHANGELOG.rst
--- old/django_environ-0.12.1/CHANGELOG.rst 2026-02-14 01:37:35.000000000
+0100
+++ new/django_environ-0.13.0/CHANGELOG.rst 2026-02-18 01:52:30.000000000
+0100
@@ -5,6 +5,36 @@
The format is inspired by `Keep a Changelog
<https://keepachangelog.com/en/1.0.0/>`_
and this project adheres to `Semantic Versioning
<https://semver.org/spec/v2.0.0.html>`_.
+`v0.13.0`_ - 18-February-2026
+-----------------------------
+Added
++++++
+- Added optional warnings when defaults are used
+ `#582 <https://github.com/joke2k/django-environ/pull/582>`_.
+- Added `choices` argument support for value validation in ``Env.str(...)``
+ `#555 <https://github.com/joke2k/django-environ/pull/555>`_.
+- Added Valkey support via ``valkey://`` and ``valkeys://`` cache URL schemes
+ `#554 <https://github.com/joke2k/django-environ/pull/554>`_.
+- Added support for ``rediss://`` scheme in channels URL parsing
+ `#573 <https://github.com/joke2k/django-environ/pull/573>`_.
+- Added django-prometheus database backend aliases to DB URL parsing schemes
+ `#559 <https://github.com/joke2k/django-environ/pull/559>`_.
+
+Changed
++++++++
+- Declared support for Python 3.14
+ `#580 <https://github.com/joke2k/django-environ/pull/580>`_.
+- Declared support for Django 5.2 and Django 6.0
+ `#578 <https://github.com/joke2k/django-environ/pull/578>`_.
+
+Fixed
++++++
+- Improved type hint coverage and related lint issues
+ `#546 <https://github.com/joke2k/django-environ/pull/546>`_.
+- Fixed typos in the FAQ page
+ `#445 <https://github.com/joke2k/django-environ/pull/445>`_.
+
+
`v0.12.1`_ - 13-February-2026
-----------------------------
Fixed
@@ -434,6 +464,7 @@
- Initial release.
+.. _v0.13.0: https://github.com/joke2k/django-environ/compare/v0.12.1...v0.13.0
.. _v0.12.1: https://github.com/joke2k/django-environ/compare/v0.12.0...v0.12.1
.. _v0.12.0: https://github.com/joke2k/django-environ/compare/v0.11.2...v0.12.0
.. _v0.11.2: https://github.com/joke2k/django-environ/compare/v0.11.1...v0.11.2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_environ-0.12.1/PKG-INFO
new/django_environ-0.13.0/PKG-INFO
--- old/django_environ-0.12.1/PKG-INFO 2026-02-14 01:49:57.984252500 +0100
+++ new/django_environ-0.13.0/PKG-INFO 2026-02-18 02:03:20.751607700 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: django-environ
-Version: 0.12.1
+Version: 0.13.0
Summary: A package that allows you to utilize 12factor inspired environment
variables to configure your Django application.
Home-page: https://django-environ.readthedocs.org
Author: Daniele Faraglia
@@ -27,6 +27,8 @@
Classifier: Framework :: Django :: 4.2
Classifier: Framework :: Django :: 5.0
Classifier: Framework :: Django :: 5.1
+Classifier: Framework :: Django :: 5.2
+Classifier: Framework :: Django :: 6.0
Classifier: Operating System :: OS Independent
Classifier: Intended Audience :: Developers
Classifier: Natural Language :: English
@@ -37,11 +39,11 @@
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Utilities
-Classifier: License :: OSI Approved :: MIT License
Requires-Python: >=3.9,<4
Description-Content-Type: text/x-rst
License-File: LICENSE.txt
@@ -53,6 +55,7 @@
Provides-Extra: docs
Requires-Dist: furo>=2024.8.6; extra == "docs"
Requires-Dist: sphinx>=5.0; extra == "docs"
+Requires-Dist: sphinx-copybutton; extra == "docs"
Requires-Dist: sphinx-notfound-page; extra == "docs"
Provides-Extra: develop
Requires-Dist: coverage[toml]>=5.0a4; extra == "develop"
@@ -60,6 +63,7 @@
Requires-Dist: setuptools>=71.0.0; extra == "develop"
Requires-Dist: furo>=2024.8.6; extra == "develop"
Requires-Dist: sphinx>=5.0; extra == "develop"
+Requires-Dist: sphinx-copybutton; extra == "develop"
Requires-Dist: sphinx-notfound-page; extra == "develop"
Dynamic: author
Dynamic: author-email
@@ -154,7 +158,7 @@
are loaded from a ``.env`` file and filled in ``os.environ`` with
``setdefault``
method, to avoid to overwrite the real environ.
A similar approach is used in
-`Two Scoops of Django
<https://web.archive.org/web/20240121133956/https://www.feldroy.com/books/two-scoops-of-django-3-x>`_
+`Two Scoops of Django <https://www.feldroy.com/two-scoops-of-django>`_
book and explained in `12factor-django
<https://dev.to/ale_jacques/django-drf-12-factor-app-with-examples-36jg>`_
article.
@@ -184,7 +188,7 @@
and the latest release on `PyPI <https://pypi.org/project/django-environ/>`_.
It’s rigorously tested on Python 3.9+, and officially supports
-Django 2.2, 3.0, 3.1, 3.2, 4.0, 4.1, 4.2, 5.0, and 5.1.
+Django 2.2, 3.0, 3.1, 3.2, 4.0, 4.1, 4.2, 5.0, 5.1, 5.2, and 6.0.
If you'd like to contribute to ``django-environ`` you're most welcome!
@@ -281,17 +285,34 @@
Release Information
===================
-v0.12.1 - 13-February-2026
+v0.13.0 - 18-February-2026
-----------------------------
+Added
++++++
+- Added optional warnings when defaults are used
+ `#582 <https://github.com/joke2k/django-environ/pull/582>`_.
+- Added `choices` argument support for value validation in ``Env.str(...)``
+ `#555 <https://github.com/joke2k/django-environ/pull/555>`_.
+- Added Valkey support via ``valkey://`` and ``valkeys://`` cache URL schemes
+ `#554 <https://github.com/joke2k/django-environ/pull/554>`_.
+- Added support for ``rediss://`` scheme in channels URL parsing
+ `#573 <https://github.com/joke2k/django-environ/pull/573>`_.
+- Added django-prometheus database backend aliases to DB URL parsing schemes
+ `#559 <https://github.com/joke2k/django-environ/pull/559>`_.
+
+Changed
++++++++
+- Declared support for Python 3.14
+ `#580 <https://github.com/joke2k/django-environ/pull/580>`_.
+- Declared support for Django 5.2 and Django 6.0
+ `#578 <https://github.com/joke2k/django-environ/pull/578>`_.
+
Fixed
+++++
-- Fixed PostgreSQL cluster URL parsing with bracketed IPv6 hosts in recent
- Python versions, preventing failures in runtime URL parsing and related
- regression tests
- `#574 <https://github.com/joke2k/django-environ/issues/574>`_.
-- Fixed debug logging in ``Env.get_value()`` to avoid evaluating lazy default
- objects when DEBUG logging is enabled
- `#571 <https://github.com/joke2k/django-environ/issues/571>`_.
+- Improved type hint coverage and related lint issues
+ `#546 <https://github.com/joke2k/django-environ/pull/546>`_.
+- Fixed typos in the FAQ page
+ `#445 <https://github.com/joke2k/django-environ/pull/445>`_.
`Full changelog
<https://django-environ.readthedocs.org/en/latest/changelog.html>`_.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_environ-0.12.1/README.rst
new/django_environ-0.13.0/README.rst
--- old/django_environ-0.12.1/README.rst 2026-02-14 01:37:35.000000000
+0100
+++ new/django_environ-0.13.0/README.rst 2026-02-18 01:59:08.000000000
+0100
@@ -98,7 +98,7 @@
are loaded from a ``.env`` file and filled in ``os.environ`` with
``setdefault``
method, to avoid to overwrite the real environ.
A similar approach is used in
-`Two Scoops of Django
<https://web.archive.org/web/20240121133956/https://www.feldroy.com/books/two-scoops-of-django-3-x>`_
+`Two Scoops of Django <https://www.feldroy.com/two-scoops-of-django>`_
book and explained in `12factor-django
<https://dev.to/ale_jacques/django-drf-12-factor-app-with-examples-36jg>`_
article.
@@ -128,7 +128,7 @@
and the latest release on `PyPI <https://pypi.org/project/django-environ/>`_.
It’s rigorously tested on Python 3.9+, and officially supports
-Django 2.2, 3.0, 3.1, 3.2, 4.0, 4.1, 4.2, 5.0, and 5.1.
+Django 2.2, 3.0, 3.1, 3.2, 4.0, 4.1, 4.2, 5.0, 5.1, 5.2, and 6.0.
If you'd like to contribute to ``django-environ`` you're most welcome!
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django_environ-0.12.1/django_environ.egg-info/PKG-INFO
new/django_environ-0.13.0/django_environ.egg-info/PKG-INFO
--- old/django_environ-0.12.1/django_environ.egg-info/PKG-INFO 2026-02-14
01:49:57.000000000 +0100
+++ new/django_environ-0.13.0/django_environ.egg-info/PKG-INFO 2026-02-18
02:03:20.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: django-environ
-Version: 0.12.1
+Version: 0.13.0
Summary: A package that allows you to utilize 12factor inspired environment
variables to configure your Django application.
Home-page: https://django-environ.readthedocs.org
Author: Daniele Faraglia
@@ -27,6 +27,8 @@
Classifier: Framework :: Django :: 4.2
Classifier: Framework :: Django :: 5.0
Classifier: Framework :: Django :: 5.1
+Classifier: Framework :: Django :: 5.2
+Classifier: Framework :: Django :: 6.0
Classifier: Operating System :: OS Independent
Classifier: Intended Audience :: Developers
Classifier: Natural Language :: English
@@ -37,11 +39,11 @@
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Utilities
-Classifier: License :: OSI Approved :: MIT License
Requires-Python: >=3.9,<4
Description-Content-Type: text/x-rst
License-File: LICENSE.txt
@@ -53,6 +55,7 @@
Provides-Extra: docs
Requires-Dist: furo>=2024.8.6; extra == "docs"
Requires-Dist: sphinx>=5.0; extra == "docs"
+Requires-Dist: sphinx-copybutton; extra == "docs"
Requires-Dist: sphinx-notfound-page; extra == "docs"
Provides-Extra: develop
Requires-Dist: coverage[toml]>=5.0a4; extra == "develop"
@@ -60,6 +63,7 @@
Requires-Dist: setuptools>=71.0.0; extra == "develop"
Requires-Dist: furo>=2024.8.6; extra == "develop"
Requires-Dist: sphinx>=5.0; extra == "develop"
+Requires-Dist: sphinx-copybutton; extra == "develop"
Requires-Dist: sphinx-notfound-page; extra == "develop"
Dynamic: author
Dynamic: author-email
@@ -154,7 +158,7 @@
are loaded from a ``.env`` file and filled in ``os.environ`` with
``setdefault``
method, to avoid to overwrite the real environ.
A similar approach is used in
-`Two Scoops of Django
<https://web.archive.org/web/20240121133956/https://www.feldroy.com/books/two-scoops-of-django-3-x>`_
+`Two Scoops of Django <https://www.feldroy.com/two-scoops-of-django>`_
book and explained in `12factor-django
<https://dev.to/ale_jacques/django-drf-12-factor-app-with-examples-36jg>`_
article.
@@ -184,7 +188,7 @@
and the latest release on `PyPI <https://pypi.org/project/django-environ/>`_.
It’s rigorously tested on Python 3.9+, and officially supports
-Django 2.2, 3.0, 3.1, 3.2, 4.0, 4.1, 4.2, 5.0, and 5.1.
+Django 2.2, 3.0, 3.1, 3.2, 4.0, 4.1, 4.2, 5.0, 5.1, 5.2, and 6.0.
If you'd like to contribute to ``django-environ`` you're most welcome!
@@ -281,17 +285,34 @@
Release Information
===================
-v0.12.1 - 13-February-2026
+v0.13.0 - 18-February-2026
-----------------------------
+Added
++++++
+- Added optional warnings when defaults are used
+ `#582 <https://github.com/joke2k/django-environ/pull/582>`_.
+- Added `choices` argument support for value validation in ``Env.str(...)``
+ `#555 <https://github.com/joke2k/django-environ/pull/555>`_.
+- Added Valkey support via ``valkey://`` and ``valkeys://`` cache URL schemes
+ `#554 <https://github.com/joke2k/django-environ/pull/554>`_.
+- Added support for ``rediss://`` scheme in channels URL parsing
+ `#573 <https://github.com/joke2k/django-environ/pull/573>`_.
+- Added django-prometheus database backend aliases to DB URL parsing schemes
+ `#559 <https://github.com/joke2k/django-environ/pull/559>`_.
+
+Changed
++++++++
+- Declared support for Python 3.14
+ `#580 <https://github.com/joke2k/django-environ/pull/580>`_.
+- Declared support for Django 5.2 and Django 6.0
+ `#578 <https://github.com/joke2k/django-environ/pull/578>`_.
+
Fixed
+++++
-- Fixed PostgreSQL cluster URL parsing with bracketed IPv6 hosts in recent
- Python versions, preventing failures in runtime URL parsing and related
- regression tests
- `#574 <https://github.com/joke2k/django-environ/issues/574>`_.
-- Fixed debug logging in ``Env.get_value()`` to avoid evaluating lazy default
- objects when DEBUG logging is enabled
- `#571 <https://github.com/joke2k/django-environ/issues/571>`_.
+- Improved type hint coverage and related lint issues
+ `#546 <https://github.com/joke2k/django-environ/pull/546>`_.
+- Fixed typos in the FAQ page
+ `#445 <https://github.com/joke2k/django-environ/pull/445>`_.
`Full changelog
<https://django-environ.readthedocs.org/en/latest/changelog.html>`_.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django_environ-0.12.1/django_environ.egg-info/SOURCES.txt
new/django_environ-0.13.0/django_environ.egg-info/SOURCES.txt
--- old/django_environ-0.12.1/django_environ.egg-info/SOURCES.txt
2026-02-14 01:49:57.000000000 +0100
+++ new/django_environ-0.13.0/django_environ.egg-info/SOURCES.txt
2026-02-18 02:03:20.000000000 +0100
@@ -7,11 +7,12 @@
MANIFEST.in
README.rst
SECURITY.rst
-setup.cfg
setup.py
tox.ini
.github/FUNDING.yml
+.github/copilot-instructions.md
.github/dependabot.yml
+.github/skills/django-environ-release/SKILL.md
.github/workflows/build.yml
.github/workflows/change-pr-target.yml
.github/workflows/ci.yml
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django_environ-0.12.1/django_environ.egg-info/requires.txt
new/django_environ-0.13.0/django_environ.egg-info/requires.txt
--- old/django_environ-0.12.1/django_environ.egg-info/requires.txt
2026-02-14 01:49:57.000000000 +0100
+++ new/django_environ-0.13.0/django_environ.egg-info/requires.txt
2026-02-18 02:03:20.000000000 +0100
@@ -5,11 +5,13 @@
setuptools>=71.0.0
furo>=2024.8.6
sphinx>=5.0
+sphinx-copybutton
sphinx-notfound-page
[docs]
furo>=2024.8.6
sphinx>=5.0
+sphinx-copybutton
sphinx-notfound-page
[testing]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_environ-0.12.1/docs/api.rst
new/django_environ-0.13.0/docs/api.rst
--- old/django_environ-0.12.1/docs/api.rst 2022-06-15 12:00:11.000000000
+0200
+++ new/django_environ-0.13.0/docs/api.rst 2026-02-18 01:22:58.000000000
+0100
@@ -25,6 +25,14 @@
The ``environ`` module
======================
+.. autoclass:: environ.NoValue
+ :members:
+ :no-undoc-members:
+
+.. autoclass:: environ.DefaultValueWarning
+ :members:
+ :no-undoc-members:
+
.. autoclass:: environ.Env
:members:
:no-undoc-members:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_environ-0.12.1/docs/conf.py
new/django_environ-0.13.0/docs/conf.py
--- old/django_environ-0.12.1/docs/conf.py 2026-02-14 01:37:35.000000000
+0100
+++ new/django_environ-0.13.0/docs/conf.py 2026-02-18 01:22:58.000000000
+0100
@@ -61,6 +61,7 @@
"sphinx.ext.intersphinx",
"sphinx.ext.todo",
"sphinx.ext.viewcode",
+ "sphinx_copybutton",
"notfound.extension",
]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_environ-0.12.1/docs/faq.rst
new/django_environ-0.13.0/docs/faq.rst
--- old/django_environ-0.12.1/docs/faq.rst 2023-03-03 00:03:41.000000000
+0100
+++ new/django_environ-0.13.0/docs/faq.rst 2026-02-18 00:15:07.000000000
+0100
@@ -5,14 +5,14 @@
#. **Can django-environ determine the location of .env file automatically?**
- django-environ will try to get and read ``.env`` file from the project
- root if you haven't specified the path for it when call
:meth:`.environ.Env.read_env`.
+ ``django-environ`` will try to get and read ``.env`` file from the project
+ root if you haven't specified the path for it when you call
:meth:`.environ.Env.read_env`.
However, this is not the recommended way. When it is possible always specify
- the path tho ``.env`` file. Alternatively, you can use a trick with a
+ the path to the ``.env`` file. Alternatively, you can use a trick with an
environment variable pointing to the actual location of ``.env`` file.
For details see ":ref:`multiple-env-files-label`".
-#. **What (where) is the root part of the project, is it part of the project
where are settings?**
+#. **What (where) is the root part of the project, is it the directory that
contains settings.py file?**
Where your ``manage.py`` file is (that is your project root directory).
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_environ-0.12.1/docs/tips.rst
new/django_environ-0.13.0/docs/tips.rst
--- old/django_environ-0.12.1/docs/tips.rst 2026-02-14 01:37:35.000000000
+0100
+++ new/django_environ-0.13.0/docs/tips.rst 2026-02-18 01:22:58.000000000
+0100
@@ -129,6 +129,24 @@
The next major release will disable it by default.
+Warn when defaults are used
+===========================
+
+If you want visibility when a missing environment variable falls back to a
+default value, enable warnings on the ``Env`` instance:
+
+.. code-block:: python
+
+ import environ
+
+ env = environ.Env()
+ env.warn_on_default = True
+ value = env("MISSING_VAR", default="fallback")
+
+When enabled, ``django-environ`` emits ``DefaultValueWarning`` for missing
+variables that return an explicit default.
+
+
Multiple redis cache locations
==============================
@@ -287,6 +305,29 @@
print(env.str('ESCAPED_CERT', multiline=False))
# ---BEGIN---\\n---END---
+Restrict string values with choices
+===================================
+
+You can restrict ``env.str()`` to an allowed list of values using
+``choices``. If the value is not in the provided list,
+``ImproperlyConfigured`` is raised.
+
+.. code-block:: python
+
+ import environ
+ from django.core.exceptions import ImproperlyConfigured
+
+ env = environ.Env()
+
+ # APP_ENV=prod
+ env.str("APP_ENV", choices=("dev", "prod", "staging")) # "prod"
+
+ # APP_ENV=unknown
+ try:
+ env.str("APP_ENV", choices=("dev", "prod", "staging"))
+ except ImproperlyConfigured:
+ ...
+
Proxy value
===========
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_environ-0.12.1/docs/types.rst
new/django_environ-0.13.0/docs/types.rst
--- old/django_environ-0.12.1/docs/types.rst 2023-08-30 14:48:23.000000000
+0200
+++ new/django_environ-0.13.0/docs/types.rst 2026-02-17 23:10:22.000000000
+0100
@@ -147,6 +147,7 @@
* ``pylibmc://``
* Redis: ``rediscache://``, ``redis://``, or ``rediss://``
+* Valkey: ``valkey://``, or ``valkeys://``
.. _environ-env-search-url:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_environ-0.12.1/environ/__init__.py
new/django_environ-0.13.0/environ/__init__.py
--- old/django_environ-0.12.1/environ/__init__.py 2026-02-14
01:48:41.000000000 +0100
+++ new/django_environ-0.13.0/environ/__init__.py 2026-02-18
01:52:13.000000000 +0100
@@ -22,7 +22,7 @@
__copyright__ = 'Copyright (C) 2013-2026 Daniele Faraglia'
"""The copyright notice of the package."""
-__version__ = '0.12.1'
+__version__ = '0.13.0'
"""The version of the package."""
__license__ = 'MIT'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_environ-0.12.1/environ/environ.py
new/django_environ-0.13.0/environ/environ.py
--- old/django_environ-0.12.1/environ/environ.py 2026-02-14
01:37:35.000000000 +0100
+++ new/django_environ-0.13.0/environ/environ.py 2026-02-18
01:22:58.000000000 +0100
@@ -19,6 +19,7 @@
import re
import sys
import warnings
+from typing import Dict, List, Tuple, Union
from urllib.parse import (
parse_qs,
ParseResult,
@@ -72,6 +73,10 @@
return f'<{self.__class__.__name__}>'
+class DefaultValueWarning(UserWarning):
+ """Warning used when returning an explicit default value."""
+
+
class Env:
"""Provide scheme-based lookups of environment variables so that each
caller doesn't have to pass in ``cast`` and ``default`` parameters.
@@ -107,7 +112,15 @@
BOOLEAN_TRUE_STRINGS = ('true', 'on', 'ok', 'y', 'yes', '1')
URL_CLASS = ParseResult
- POSTGRES_FAMILY = ['postgres', 'postgresql', 'psql', 'pgsql', 'postgis']
+ POSTGRES_FAMILY = [
+ 'postgres',
+ 'postgresql',
+ 'psql',
+ 'pgsql',
+ 'postgis',
+ 'prometheus_postgresql',
+ 'prometheus_postgis',
+ ]
DEFAULT_DATABASE_ENV = 'DATABASE_URL'
DB_SCHEMES = {
@@ -116,17 +129,22 @@
'psql': DJANGO_POSTGRES,
'pgsql': DJANGO_POSTGRES,
'postgis': 'django.contrib.gis.db.backends.postgis',
+ 'prometheus_postgresql': 'django_prometheus.db.backends.postgresql',
+ 'prometheus_postgis': 'django_prometheus.db.backends.postgis',
'cockroachdb': 'django_cockroachdb',
'mysql': 'django.db.backends.mysql',
'mysql2': 'django.db.backends.mysql',
'mysql-connector': 'mysql.connector.django',
'mysqlgis': 'django.contrib.gis.db.backends.mysql',
+ 'prometheus_mysql': 'django_prometheus.db.backends.mysql',
'mssql': 'mssql',
'oracle': 'django.db.backends.oracle',
'pyodbc': 'sql_server.pyodbc',
'redshift': 'django_redshift_backend',
'spatialite': 'django.contrib.gis.db.backends.spatialite',
+ 'prometheus_spatialite': 'django_prometheus.db.backends.spatialite',
'sqlite': 'django.db.backends.sqlite3',
+ 'prometheus_sqlite': 'django_prometheus.db.backends.sqlite3',
'ldap': 'ldapdb.backends.ldap',
}
_DB_BASE_OPTIONS = [
@@ -149,6 +167,8 @@
'rediscache': REDIS_DRIVER,
'redis': REDIS_DRIVER,
'rediss': REDIS_DRIVER,
+ 'valkey': REDIS_DRIVER,
+ 'valkeys': REDIS_DRIVER,
}
_CACHE_BASE_OPTIONS = [
'TIMEOUT',
@@ -195,12 +215,15 @@
CHANNELS_SCHEMES = {
"inmemory": "channels.layers.InMemoryChannelLayer",
"redis": "channels_redis.core.RedisChannelLayer",
- "redis+pubsub": "channels_redis.pubsub.RedisPubSubChannelLayer"
+ "rediss": "channels_redis.core.RedisChannelLayer",
+ "redis+pubsub": "channels_redis.pubsub.RedisPubSubChannelLayer",
+ "rediss+pubsub": "channels_redis.pubsub.RedisPubSubChannelLayer",
}
def __init__(self, **scheme):
self.smart_cast = True
self.escape_proxy = False
+ self.warn_on_default = False
self.prefix = ""
self.scheme = scheme
@@ -215,16 +238,31 @@
def __contains__(self, var):
return var in self.ENVIRON
- def str(self, var, default=NOTSET, multiline=False):
+ def str(
+ self,
+ var,
+ default: Union[str, NoValue] = NOTSET,
+ multiline=False,
+ choices=NOTSET) -> str:
"""
:rtype: str
"""
value = self.get_value(var, cast=str, default=default)
if multiline:
return re.sub(r'(\\r)?\\n', r'\n', value)
+ if choices is not self.NOTSET:
+ # if choices is provided, check that the value is in choices
+ if value not in choices:
+ raise ImproperlyConfigured(
+ f"Invalid value: {value} not in {choices}"
+ )
return value
- def bytes(self, var, default=NOTSET, encoding='utf8'):
+ def bytes(
+ self,
+ var,
+ default: Union[bytes, NoValue] = NOTSET,
+ encoding='utf8') -> bytes:
"""
:rtype: bytes
"""
@@ -233,19 +271,19 @@
return value.encode(encoding)
return value
- def bool(self, var, default=NOTSET):
+ def bool(self, var, default: Union[bool, NoValue] = NOTSET) -> bool:
"""
:rtype: bool
"""
return self.get_value(var, cast=bool, default=default)
- def int(self, var, default=NOTSET):
+ def int(self, var, default: Union[int, NoValue] = NOTSET) -> int:
"""
:rtype: int
"""
return self.get_value(var, cast=int, default=default)
- def float(self, var, default=NOTSET):
+ def float(self, var, default: Union[float, NoValue] = NOTSET) -> float:
"""
:rtype: float
"""
@@ -257,7 +295,7 @@
"""
return self.get_value(var, cast=json.loads, default=default)
- def list(self, var, cast=None, default=NOTSET):
+ def list(self, var, cast=None, default=NOTSET) -> List:
"""
:rtype: list
"""
@@ -267,7 +305,7 @@
default=default
)
- def tuple(self, var, cast=None, default=NOTSET):
+ def tuple(self, var, cast=None, default=NOTSET) -> Tuple:
"""
:rtype: tuple
"""
@@ -277,13 +315,13 @@
default=default
)
- def dict(self, var, cast=dict, default=NOTSET):
+ def dict(self, var, cast=dict, default=NOTSET) -> Dict:
"""
:rtype: dict
"""
return self.get_value(var, cast=cast, default=default)
- def url(self, var, default=NOTSET):
+ def url(self, var, default=NOTSET) -> ParseResult:
"""
:rtype: urllib.parse.ParseResult
"""
@@ -294,7 +332,11 @@
parse_default=True
)
- def db_url(self, var=DEFAULT_DATABASE_ENV, default=NOTSET, engine=None):
+ def db_url(
+ self,
+ var=DEFAULT_DATABASE_ENV,
+ default=NOTSET,
+ engine=None) -> Dict:
"""Returns a config dictionary, defaulting to DATABASE_URL.
The db method is an alias for db_url.
@@ -308,7 +350,11 @@
db = db_url
- def cache_url(self, var=DEFAULT_CACHE_ENV, default=NOTSET, backend=None):
+ def cache_url(
+ self,
+ var=DEFAULT_CACHE_ENV,
+ default=NOTSET,
+ backend=None) -> Dict:
"""Returns a config dictionary, defaulting to CACHE_URL.
The cache method is an alias for cache_url.
@@ -322,7 +368,11 @@
cache = cache_url
- def email_url(self, var=DEFAULT_EMAIL_ENV, default=NOTSET, backend=None):
+ def email_url(
+ self,
+ var=DEFAULT_EMAIL_ENV,
+ default=NOTSET,
+ backend=None) -> Dict:
"""Returns a config dictionary, defaulting to EMAIL_URL.
The email method is an alias for email_url.
@@ -336,7 +386,11 @@
email = email_url
- def search_url(self, var=DEFAULT_SEARCH_ENV, default=NOTSET, engine=None):
+ def search_url(
+ self,
+ var=DEFAULT_SEARCH_ENV,
+ default: Union[Dict, NoValue] = NOTSET,
+ engine=None) -> Dict:
"""Returns a config dictionary, defaulting to SEARCH_URL.
:rtype: dict
@@ -346,8 +400,11 @@
engine=engine
)
- def channels_url(self, var=DEFAULT_CHANNELS_ENV, default=NOTSET,
- backend=None):
+ def channels_url(
+ self,
+ var=DEFAULT_CHANNELS_ENV,
+ default: Union[Dict, NoValue] = NOTSET,
+ backend=None) -> Dict:
"""Returns a config dictionary, defaulting to CHANNELS_URL.
:rtype: dict
@@ -359,7 +416,11 @@
channels = channels_url
- def path(self, var, default=NOTSET, **kwargs):
+ def path(
+ self,
+ var,
+ default: Union['Path', NoValue] = NOTSET,
+ **kwargs) -> 'Path':
"""
:rtype: Path
"""
@@ -414,6 +475,13 @@
raise ImproperlyConfigured(error_msg) from exc
value = default
+ if self.warn_on_default:
+ warnings.warn(
+ f'{var_name} environment variable not set; '
+ 'using default value',
+ DefaultValueWarning,
+ stacklevel=2,
+ )
# Resolve any proxied values
prefix = b'$' if isinstance(value, bytes) else '$'
@@ -699,19 +767,28 @@
else:
config['LOCATION'] = locations
+ if backend:
+ config['BACKEND'] = backend
+
if url.query:
config_options = {}
+ # Django Redis cache backend expects options in lower case
+ # while "django_redis" expects them in upper case
+ backend = config['BACKEND']
+ if backend == 'django.core.cache.backends.redis.RedisCache':
+ key_modifier = 'lower'
+ else:
+ key_modifier = 'upper'
+
for k, v in parse_qs(url.query).items():
- opt = {k.upper(): _cast(v[0])}
+ key = getattr(k, key_modifier)()
+ opt = {key: _cast(v[0])}
if k.upper() in cls._CACHE_BASE_OPTIONS:
config.update(opt)
else:
config_options.update(opt)
config['OPTIONS'] = config_options
- if backend:
- config['BACKEND'] = backend
-
return config
@classmethod
@@ -787,9 +864,10 @@
raise ImproperlyConfigured(f"Invalid channels schema {url.scheme}")
else:
config["BACKEND"] = cls.CHANNELS_SCHEMES[url.scheme]
- if url.scheme in ("redis", "redis+pubsub"):
+ if url.scheme.startswith("redis"):
+ redis_scheme, *_ = url.scheme.split("+")
config["CONFIG"] = {
- "hosts": [url._replace(scheme="redis").geturl()]
+ "hosts": [url._replace(scheme=redis_scheme).geturl()]
}
return config
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_environ-0.12.1/setup.cfg
new/django_environ-0.13.0/setup.cfg
--- old/django_environ-0.12.1/setup.cfg 2026-02-14 01:49:57.985591200 +0100
+++ new/django_environ-0.13.0/setup.cfg 2026-02-18 02:03:20.753000700 +0100
@@ -1,6 +1,3 @@
-[bdist_wheel]
-universal = 1
-
[egg_info]
tag_build =
tag_date = 0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_environ-0.12.1/setup.py
new/django_environ-0.13.0/setup.py
--- old/django_environ-0.12.1/setup.py 2026-02-14 01:37:35.000000000 +0100
+++ new/django_environ-0.13.0/setup.py 2026-02-18 01:22:58.000000000 +0100
@@ -145,6 +145,8 @@
'Framework :: Django :: 4.2',
'Framework :: Django :: 5.0',
'Framework :: Django :: 5.1',
+ 'Framework :: Django :: 5.2',
+ 'Framework :: Django :: 6.0',
'Operating System :: OS Independent',
@@ -158,13 +160,13 @@
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
+ 'Programming Language :: Python :: 3.14',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Utilities',
- 'License :: OSI Approved :: MIT License',
]
# Dependencies that are downloaded by pip on installation and why.
@@ -188,6 +190,7 @@
'docs': [
'furo>=2024.8.6', # Sphinx documentation theme
'sphinx>=5.0', # Python documentation generator
+ 'sphinx-copybutton', # Add copy button to code blocks
'sphinx-notfound-page', # Create a custom 404 page
],
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_environ-0.12.1/tests/test_cache.py
new/django_environ-0.13.0/tests/test_cache.py
--- old/django_environ-0.12.1/tests/test_cache.py 2026-02-14
01:37:35.000000000 +0100
+++ new/django_environ-0.13.0/tests/test_cache.py 2026-02-17
23:00:29.000000000 +0100
@@ -20,7 +20,7 @@
)
-def test_base_options_parsing():
+def test_base_options_parsing_memcache():
url = ('memcache://127.0.0.1:11211/?timeout=0&'
'key_prefix=cache_&key_function=foo.get_key&version=1')
url = Env.cache_url_config(url)
@@ -30,10 +30,29 @@
assert url['TIMEOUT'] == 0
assert url['VERSION'] == 1
- url = 'redis://127.0.0.1:6379/?timeout=None'
- url = Env.cache_url_config(url)
- assert url['TIMEOUT'] is None
[email protected]('redis_driver,timeout_key',
+ [
+ ('django.core.cache.backends.redis.RedisCache', 'timeout'),
+ ('django_redis.cache.RedisCache', 'TIMEOUT'),
+ ],
+ ids=[
+ 'django',
+ 'django_redis',
+ ],
+)
+def test_base_options_parsing_redis(redis_driver, timeout_key):
+ mocked_cache_schemes = Env.CACHE_SCHEMES.copy()
+ mocked_cache_schemes.update({
+ 'rediscache': redis_driver,
+ 'redis': redis_driver,
+ 'rediss': redis_driver,
+ })
+ with mock.patch.object(Env, 'CACHE_SCHEMES', mocked_cache_schemes):
+ url = 'redis://127.0.0.1:6379/?timeout=None'
+ url = Env.cache_url_config(url)
+
+ assert url[timeout_key] is None
@pytest.mark.parametrize(
@@ -135,27 +154,63 @@
else:
assert driver == redis_cache
-def test_redis_parsing():
- url = ('rediscache://127.0.0.1:6379/1?client_class='
- 'django_redis.client.DefaultClient&password=secret')
- url = Env.cache_url_config(url)
- assert url['BACKEND'] == REDIS_DRIVER
- assert url['LOCATION'] == 'redis://127.0.0.1:6379/1'
- assert url['OPTIONS'] == {
- 'CLIENT_CLASS': 'django_redis.client.DefaultClient',
- 'PASSWORD': 'secret',
- }
[email protected]('redis_driver,client_class_key,password_key',
+ [
+ ('django.core.cache.backends.redis.RedisCache', 'client_class',
'password'),
+ ('django_redis.cache.RedisCache', 'CLIENT_CLASS', 'PASSWORD'),
+ ],
+ ids=[
+ 'django',
+ 'django_redis',
+ ],
+)
+def test_redis_parsing(redis_driver, client_class_key, password_key):
+ mocked_cache_schemes = Env.CACHE_SCHEMES.copy()
+ mocked_cache_schemes.update({
+ 'rediscache': redis_driver,
+ 'redis': redis_driver,
+ 'rediss': redis_driver,
+ })
+ with mock.patch.object(Env, 'CACHE_SCHEMES', mocked_cache_schemes):
+ url = ('rediscache://127.0.0.1:6379/1?client_class='
+ 'django_redis.client.DefaultClient&password=secret')
+ url = Env.cache_url_config(url)
+
+ assert url['BACKEND'] == redis_driver
+ assert url['LOCATION'] == 'redis://127.0.0.1:6379/1'
+ assert url['OPTIONS'] == {
+ client_class_key: 'django_redis.client.DefaultClient',
+ password_key: 'secret',
+ }
-def test_redis_socket_url():
- url = 'redis://:redispass@/path/to/socket.sock?db=0'
- url = Env.cache_url_config(url)
- assert REDIS_DRIVER == url['BACKEND']
- assert url['LOCATION'] == 'unix://:redispass@/path/to/socket.sock'
- assert url['OPTIONS'] == {
- 'DB': 0
- }
[email protected]('redis_driver,db_key',
+ [
+ ('django.core.cache.backends.redis.RedisCache', 'db'),
+ ('django_redis.cache.RedisCache', 'DB'),
+ ],
+ ids=[
+ 'django',
+ 'django_redis',
+ ],
+)
+def test_redis_socket_url(redis_driver, db_key):
+ mocked_cache_schemes = Env.CACHE_SCHEMES.copy()
+ mocked_cache_schemes.update({
+ 'rediscache': redis_driver,
+ 'redis': redis_driver,
+ 'rediss': redis_driver,
+ })
+ with mock.patch.object(Env, 'CACHE_SCHEMES', mocked_cache_schemes):
+ url = 'redis://:redispass@/path/to/socket.sock?db=0'
+ url = Env.cache_url_config(url)
+
+ assert url['BACKEND'] == redis_driver
+ assert url['LOCATION'] == 'unix://:redispass@/path/to/socket.sock'
+ assert url['OPTIONS'] == {
+ db_key: 0
+ }
def test_options_parsing():
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_environ-0.12.1/tests/test_channels.py
new/django_environ-0.13.0/tests/test_channels.py
--- old/django_environ-0.12.1/tests/test_channels.py 2026-02-14
01:37:35.000000000 +0100
+++ new/django_environ-0.13.0/tests/test_channels.py 2026-02-17
22:04:46.000000000 +0100
@@ -22,11 +22,20 @@
assert result["BACKEND"] == "channels_redis.core.RedisChannelLayer"
assert result["CONFIG"]["hosts"][0] ==
"redis://user:password@localhost:6379/0"
+ url = "rediss://user:password@localhost:6379/0"
+ result = Env.channels_url_config(url)
+ assert result["BACKEND"] == "channels_redis.core.RedisChannelLayer"
+ assert result["CONFIG"]["hosts"][0] ==
"rediss://user:password@localhost:6379/0"
+
url = "redis+pubsub://user:password@localhost:6379/0"
result = Env.channels_url_config(url)
assert result["BACKEND"] == "channels_redis.pubsub.RedisPubSubChannelLayer"
assert result["CONFIG"]["hosts"][0] ==
"redis://user:password@localhost:6379/0"
+ url = "rediss+pubsub://user:password@localhost:6379/0"
+ result = Env.channels_url_config(url)
+ assert result["BACKEND"] == "channels_redis.pubsub.RedisPubSubChannelLayer"
+ assert result["CONFIG"]["hosts"][0] ==
"rediss://user:password@localhost:6379/0"
def test_channels_backend_override():
result = Env.channels_url_config("unsupported://",
backend="custom.backend")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_environ-0.12.1/tests/test_env.py
new/django_environ-0.13.0/tests/test_env.py
--- old/django_environ-0.12.1/tests/test_env.py 2026-02-14 01:37:35.000000000
+0100
+++ new/django_environ-0.13.0/tests/test_env.py 2026-02-18 01:22:58.000000000
+0100
@@ -9,13 +9,15 @@
import os
import tempfile
+from unittest import mock
import logging
import io
+import warnings
from urllib.parse import quote
import pytest
-from environ import Env, Path
+from environ import DefaultValueWarning, Env, Path
from environ.compat import (
DJANGO_POSTGRES,
ImproperlyConfigured,
@@ -102,6 +104,19 @@
def test_not_present_with_default(self):
assert self.env('not_present', default=3) == 3
+ def test_not_present_with_default_warning_disabled(self):
+ with warnings.catch_warnings(record=True) as warns:
+ warnings.simplefilter('always')
+ assert self.env('not_present', default=3) == 3
+ assert warns == []
+
+ def test_not_present_with_default_warning_enabled(self):
+ self.env.warn_on_default = True
+ with pytest.warns(
+ DefaultValueWarning,
+ match='not_present environment variable not set'):
+ assert self.env('not_present', default=3) == 3
+
def test_not_present_without_default(self):
with pytest.raises(ImproperlyConfigured) as excinfo:
self.env('not_present')
@@ -114,22 +129,31 @@
assert 'I_AM_NOT_A_VAR' not in self.env
@pytest.mark.parametrize(
- 'var,val,multiline',
+ 'var,val,multiline,choices',
[
- ('STR_VAR', 'bar', False),
- ('MULTILINE_STR_VAR', 'foo\\nbar', False),
- ('MULTILINE_STR_VAR', 'foo\nbar', True),
- ('MULTILINE_QUOTED_STR_VAR', '---BEGIN---\\r\\n---END---', False),
- ('MULTILINE_QUOTED_STR_VAR', '---BEGIN---\n---END---', True),
- ('MULTILINE_ESCAPED_STR_VAR', '---BEGIN---\\\\n---END---', False),
- ('MULTILINE_ESCAPED_STR_VAR', '---BEGIN---\\\n---END---', True),
+ ('STR_VAR', 'bar', False, Env.NOTSET),
+ ('STR_VAR', 'bar', False, ['foo', 'bar']),
+ ('STR_VAR', 'bar', False, ['pow', 'foo']),
+ ('MULTILINE_STR_VAR', 'foo\\nbar', False, Env.NOTSET),
+ ('MULTILINE_STR_VAR', 'foo\\nbar', False, ['foo\\nbar', '***']),
+ ('MULTILINE_STR_VAR', 'foo\\nbar', False, ['***', '***']),
+ ('MULTILINE_STR_VAR', 'foo\nbar', True, Env.NOTSET),
+ ('MULTILINE_QUOTED_STR_VAR', '---BEGIN---\\r\\n---END---', False,
Env.NOTSET),
+ ('MULTILINE_QUOTED_STR_VAR', '---BEGIN---\n---END---', True,
Env.NOTSET),
+ ('MULTILINE_ESCAPED_STR_VAR', '---BEGIN---\\\\n---END---', False,
Env.NOTSET),
+ ('MULTILINE_ESCAPED_STR_VAR', '---BEGIN---\\\n---END---', True,
Env.NOTSET),
],
)
- def test_str(self, var, val, multiline):
- assert isinstance(self.env(var), str)
- if not multiline:
- assert self.env(var) == val
- assert self.env.str(var, multiline=multiline) == val
+ def test_str(self, var, val, multiline, choices):
+ if choices is Env.NOTSET or val in choices:
+ assert isinstance(self.env(var), str)
+ if not multiline:
+ assert self.env(var) == val
+ assert self.env.str(var, multiline=multiline) == val
+ else:
+ with pytest.raises(ImproperlyConfigured) as excinfo:
+ self.env.str(var, multiline=multiline, choices=choices)
+ assert str(excinfo.value) == f"Invalid value: {val} not in
{choices}"
@pytest.mark.parametrize(
'var,val,default',
@@ -355,26 +379,40 @@
(Env.DEFAULT_CACHE_ENV,
'django.core.cache.backends.memcached.MemcachedCache',
'127.0.0.1:11211', None),
- ('CACHE_REDIS', REDIS_DRIVER,
+ ('CACHE_REDIS',
+ 'django.core.cache.backends.redis.RedisCache',
+ 'redis://127.0.0.1:6379/1',
+ {'client_class': 'django_redis.client.DefaultClient',
+ 'password': 'secret'}),
+ ('CACHE_REDIS',
+ 'django_redis.cache.RedisCache',
'redis://127.0.0.1:6379/1',
{'CLIENT_CLASS': 'django_redis.client.DefaultClient',
'PASSWORD': 'secret'}),
],
ids=[
'memcached',
- 'redis',
+ 'django', # Django Redis cache backend
+ 'redis_django', # django_redis backend
],
)
def test_cache_url_value(self, var, backend, location, options):
- config = self.env.cache_url(var)
+ mocked_cache_schemes = Env.CACHE_SCHEMES.copy()
+ mocked_cache_schemes.update({
+ 'rediscache': backend,
+ 'redis': backend,
+ 'rediss': backend,
+ })
+ with mock.patch.object(Env, 'CACHE_SCHEMES', mocked_cache_schemes):
+ config = self.env.cache_url(var)
- assert config['BACKEND'] == backend
- assert config['LOCATION'] == location
+ assert config['BACKEND'] == backend
+ assert config['LOCATION'] == location
- if options is None:
- assert 'OPTIONS' not in config
- else:
- assert config['OPTIONS'] == options
+ if options is None:
+ assert 'OPTIONS' not in config
+ else:
+ assert config['OPTIONS'] == options
def test_email_url_value(self):
email_config = self.env.email_url()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_environ-0.12.1/tox.ini
new/django_environ-0.13.0/tox.ini
--- old/django_environ-0.12.1/tox.ini 2026-02-14 01:37:35.000000000 +0100
+++ new/django_environ-0.13.0/tox.ini 2026-02-17 22:04:46.000000000 +0100
@@ -21,6 +21,8 @@
manifest
py{39,310,311,312,313}-django{22,30,31,32,40,41,42}
py{310,311,312,313}-django{50,51}
+ py{310,311,312,313,314}-django{52}
+ py{312,313,314}-django{60}
pypy-django{22,30,31,32}
[gh-actions]
@@ -30,6 +32,7 @@
3.11: py311
3.12: py312
3.13: py313
+ 3.14: py314
pypy-3.10: pypy
[testenv]
@@ -43,6 +46,10 @@
django40: Django>=4.0,<4.1
django41: Django>=4.1,<4.2
django42: Django>=4.2,<5.0
+ django50: Django>=5.0,<5.1
+ django51: Django>=5.1,<5.2
+ django52: Django>=5.2,<6.0
+ django60: Django>=6.0,<6.1
commands_pre =
python -m pip install --upgrade pip
python -m pip install .
@@ -145,13 +152,13 @@
description = Build and test package distribution
skip_install = true
deps =
+ build
twine
check-wheel-contents
commands_pre =
python -m pip install -U pip setuptools wheel
- python setup.py bdist_wheel -d {envtmpdir}/build
- python setup.py sdist -d {envtmpdir}/build
commands =
+ python -m build --outdir {envtmpdir}/build
twine check {envtmpdir}/build/*
check-wheel-contents {envtmpdir}/build