Repository: joke2k/django-environ

Issue:
db support for CockroachDB
I would like to propose a feature that django-eviron's `db()` parser shall support CockroachDB

[CockroachDB](https://www.cockroachlabs.com/) is a distributed quorum based database. Cockroach Labs have provided an [idiomatic driver for Django](https://github.com/cockroachdb/django-cockroachdb).

This driver does under the hood use psycopg, so in theory a postresql:// connection string could be used with `Env().db_url()` (I have not tried!) it is preferable to use the Cockroach driver as this improves resiliency and retry logic.

I am all too happy to submit the PR for this myself.

Hints from issue comments:
@stevewilliamsuk: Would also add that it is worth considering support for djongo as well: https://www.mongodb.com/compatibility/mongodb-and-django#connect-django-and-mongodb-using-djongo@sergeyklay: Implemented in https://github.com/joke2k/django-environ/pull/511 so I close this one. Thank you contributing!

Solution patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 4e28fa5a..342d5ce8 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -24,12 +24,12 @@ jobs:
 
     steps:
       - name: Checkout code
-        uses: actions/checkout@v4.1.1
+        uses: actions/checkout@v4.2.2
 
-      - name: Set up Python 3.10
-        uses: actions/setup-python@v4.7.1
+      - name: Set up Python 3.12
+        uses: actions/setup-python@v5.3.0
         with:
-          python-version: '3.10'
+          python-version: '3.12'
 
       - name: Install dependencies
         run: |
@@ -44,7 +44,7 @@ jobs:
 
       - name: Archive build artifacts
         if: success()
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           # To ensure that jobs don't overwrite existing artifacts,
           # use a different name per job.
@@ -64,12 +64,12 @@ jobs:
 
     steps:
       - name: Checkout code
-        uses: actions/checkout@v4.1.1
+        uses: actions/checkout@v4.2.2
 
-      - name: Set up Python 3.10
-        uses: actions/setup-python@v4.7.1
+      - name: Set up Python 3.12
+        uses: actions/setup-python@v5.3.0
         with:
-          python-version: '3.10'
+          python-version: '3.12'
 
       - name: Install in dev mode
         run: python -m pip install -e .
diff --git a/.github/workflows/change-pr-target.yml b/.github/workflows/change-pr-target.yml
index eb12a30c..af419d11 100644
--- a/.github/workflows/change-pr-target.yml
+++ b/.github/workflows/change-pr-target.yml
@@ -8,7 +8,7 @@ jobs:
   check-branch:
     runs-on: ubuntu-latest
     steps:
-      - uses: Vankka/pr-target-branch-action@v2
+      - uses: Vankka/pr-target-branch-action@v3
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         with:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1fe3f1d6..0d80bfe7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -47,32 +47,22 @@ jobs:
 
       matrix:
         python:
-          - '3.8'
           - '3.9'
           - '3.10'
           - '3.11'
           - '3.12'
-          - 'pypy-3.7'
+          - '3.13'
+          - 'pypy-3.10'
         os: [ ubuntu-latest, macos-latest, windows-latest ]
 
-        # These versions are no longer supported by Python team, and may
-        # eventually be dropped from GitHub Actions.  The support of these
-        # versions by django-environ will continue for as long as possible,
-        # and may be discontinued at any time.
-        include:
-          - python: '3.6'
-            os: ubuntu-20.04
-          - python: '3.7'
-            os: ubuntu-20.04
-
     steps:
       - name: Checkout code
-        uses: actions/checkout@v4.1.1
+        uses: actions/checkout@v4.2.2
         with:
           fetch-depth: 5
 
       - name: Set up Python ${{ matrix.python }}
-        uses: actions/setup-python@v4.7.1
+        uses: actions/setup-python@v5.3.0
         with:
           python-version: ${{ matrix.python }}
 
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 460c1e33..d8236f32 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -45,16 +45,16 @@ jobs:
 
     steps:
     - name: Checkout repository
-      uses: actions/checkout@v4.1.1
+      uses: actions/checkout@v4.2.2
 
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@v2
+      uses: github/codeql-action/init@v3
       with:
         languages: ${{ matrix.language }}
         
     - name: Autobuild
-      uses: github/codeql-action/autobuild@v2
+      uses: github/codeql-action/autobuild@v3
 
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@v2
+      uses: github/codeql-action/analyze@v3
diff --git a/.github/workflows/cs.yml b/.github/workflows/cs.yml
index 4daad7d5..30d41b99 100644
--- a/.github/workflows/cs.yml
+++ b/.github/workflows/cs.yml
@@ -25,12 +25,12 @@ jobs:
 
     steps:
       - name: Checkout code
-        uses: actions/checkout@v4.1.1
+        uses: actions/checkout@v4.2.2
 
-      - name: Set up Python 3.10
-        uses: actions/setup-python@v4.7.1
+      - name: Set up Python 3.12
+        uses: actions/setup-python@v5.3.0
         with:
-          python-version: '3.10'
+          python-version: '3.12'
 
       - name: Install dependencies
         run: |
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index efef795c..0ae19c36 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -26,12 +26,12 @@ jobs:
 
     steps:
       - name: Checkout code
-        uses: actions/checkout@v4.1.1
+        uses: actions/checkout@v4.2.2
 
-      - name: Set up Python 3.10
-        uses: actions/setup-python@v4.7.1
+      - name: Set up Python 3.12
+        uses: actions/setup-python@v5.3.0
         with:
-          python-version: '3.10'
+          python-version: '3.12'
 
       - name: Install dependencies
         run: |
@@ -46,7 +46,7 @@ jobs:
 
       - name: Archive docs artifacts
         if: always()
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: docs
           path: docs
diff --git a/.readthedocs.yml b/.readthedocs.yml
index 27f93688..191e9990 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yml
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
@@ -17,7 +17,7 @@ build:
   tools:
     # Keep version in sync with tox.ini (testenv:docs) and
     # docs.yml (GitHub Action Workflow).
-    python: '3.10'
+    python: '3.12'
 
 python:
   install:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 6051aa4a..a4403a12 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -5,16 +5,34 @@ All notable changes to this project will be documented in this file.
 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.11.3`_ - 0-Undefined-2023
+`v0.12.0`_ - 0-Undefined-2024
 -----------------------------
+Fixed
++++++
+- Include prefix in the ``ImproperlyConfigured`` error message
+  `#513 <https://github.com/joke2k/django-environ/issues/513>`_.
+
+Added
++++++
+- Add support for Python 3.12 and 3.13
+  `#538 <https://github.com/joke2k/django-environ/issues/538>`_.
+- Add support for Django 5.1
+  `#535 <https://github.com/joke2k/django-environ/issues/535>`_.
+
 Changed
 +++++++
-- Formally support Python 3.12.
 - Disabled inline comments handling by default due to potential side effects.
   While the feature itself is useful, the project's philosophy dictates that
   it should not be enabled by default for all users
   `#499 <https://github.com/joke2k/django-environ/issues/499>`_.
 
+Removed
++++++++
+- Removed support of Python 3.6, 3.7 and 3.8
+  `#538 <https://github.com/joke2k/django-environ/issues/538>`_.
+- Removed support of Django 1.x.
+  `#538 <https://github.com/joke2k/django-environ/issues/538>`_.
+
 
 `v0.11.2`_ - 1-September-2023
 -----------------------------
@@ -399,7 +417,7 @@ Added
 - Initial release.
 
 
-.. _v0.11.3: https://github.com/joke2k/django-environ/compare/v0.11.2...v0.11.3
+.. _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
 .. _v0.11.1: https://github.com/joke2k/django-environ/compare/v0.11.0...v0.11.1
 .. _v0.11.0: https://github.com/joke2k/django-environ/compare/v0.10.0...v0.11.0
@@ -417,4 +435,4 @@ Added
 .. _v0.4.1: https://github.com/joke2k/django-environ/compare/v0.4...v0.4.1
 .. _v0.4: https://github.com/joke2k/django-environ/compare/v0.3.1...v0.4
 .. _v0.3.1: https://github.com/joke2k/django-environ/compare/v0.3...v0.3.1
-.. _v0.3: https://github.com/joke2k/django-environ/compare/v0.2.1...v0.3
\ No newline at end of file
+.. _v0.3: https://github.com/joke2k/django-environ/compare/v0.2.1...v0.3
diff --git a/README.rst b/README.rst
index dbb572ba..ff8e0125 100644
--- a/README.rst
+++ b/README.rst
@@ -97,7 +97,8 @@ approach, some connection strings are expressed as url, so this package can pars
 it and return a ``urllib.parse.ParseResult``. These strings from ``os.environ``
 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://www.feldroy.com/books/two-scoops-of-django-3-x>`_
+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>`_
 book and explained in `12factor-django <https://wellfire.co/learn/easier-12-factor-django>`_
 article.
 
@@ -126,8 +127,8 @@ its documentation lives at `Read the Docs <https://django-environ.readthedocs.io
 the code on `GitHub <https://github.com/joke2k/django-environ>`_,
 and the latest release on `PyPI <https://pypi.org/project/django-environ/>`_.
 
-It’s rigorously tested on Python 3.6+, and officially supports
-Django 1.11, 2.2, 3.0, 3.1, 3.2, 4.0, 4.1, 4.2, and 5.0.
+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.
 
 If you'd like to contribute to ``django-environ`` you're most welcome!
 
diff --git a/docs/install.rst b/docs/install.rst
index 3dacca9e..b1a73c18 100644
--- a/docs/install.rst
+++ b/docs/install.rst
@@ -6,8 +6,8 @@ Installation
 Requirements
 ============
 
-* `Django <https://www.djangoproject.com/>`_ >= 1.11
-* `Python <https://www.python.org/>`_ >= 3.5
+* `Django <https://www.djangoproject.com/>`_ >= 2.2
+* `Python <https://www.python.org/>`_ >= 3.9
 
 Installing django-environ
 =========================
diff --git a/environ/__init__.py b/environ/__init__.py
index ddf05f92..c590d713 100644
--- a/environ/__init__.py
+++ b/environ/__init__.py
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021-2023, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
@@ -21,7 +21,7 @@
 __copyright__ = 'Copyright (C) 2013-2023 Daniele Faraglia'
 """The copyright notice of the package."""
 
-__version__ = '0.11.3'
+__version__ = '0.12.0'
 """The version of the package."""
 
 __license__ = 'MIT'
diff --git a/environ/environ.py b/environ/environ.py
index a3d64f2d..6595fc05 100644
--- a/environ/environ.py
+++ b/environ/environ.py
@@ -115,6 +115,7 @@ class Env:
         'psql': DJANGO_POSTGRES,
         'pgsql': DJANGO_POSTGRES,
         'postgis': 'django.contrib.gis.db.backends.postgis',
+        'cockroachdb': 'django_cockroachdb',
         'mysql': 'django.db.backends.mysql',
         'mysql2': 'django.db.backends.mysql',
         'mysql-connector': 'mysql.connector.django',
@@ -388,7 +389,7 @@ def get_value(self, var, cast=None, default=NOTSET, parse_default=False):
             value = self.ENVIRON[var_name]
         except KeyError as exc:
             if default is self.NOTSET:
-                error_msg = f'Set the {var} environment variable'
+                error_msg = f'Set the {var_name} environment variable'
                 raise ImproperlyConfigured(error_msg) from exc
 
             value = default
diff --git a/setup.py b/setup.py
index fd3cd811..e9272177 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@
 #
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021-2023, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
@@ -135,9 +135,6 @@ def get_version_string():
     'Development Status :: 5 - Production/Stable',
 
     'Framework :: Django',
-    'Framework :: Django :: 1.11',
-    'Framework :: Django :: 2.0',
-    'Framework :: Django :: 2.1',
     'Framework :: Django :: 2.2',
     'Framework :: Django :: 3.0',
     'Framework :: Django :: 3.1',
@@ -146,6 +143,7 @@ def get_version_string():
     'Framework :: Django :: 4.1',
     'Framework :: Django :: 4.2',
     'Framework :: Django :: 5.0',
+    'Framework :: Django :: 5.1',
 
     'Operating System :: OS Independent',
 
@@ -154,13 +152,11 @@ def get_version_string():
 
     'Programming Language :: Python',
     'Programming Language :: Python :: 3',
-    'Programming Language :: Python :: 3.6',
-    'Programming Language :: Python :: 3.7',
-    'Programming Language :: Python :: 3.8',
     'Programming Language :: Python :: 3.9',
     'Programming Language :: Python :: 3.10',
     'Programming Language :: Python :: 3.11',
     'Programming Language :: Python :: 3.12',
+    'Programming Language :: Python :: 3.13',
     'Programming Language :: Python :: Implementation :: CPython',
     'Programming Language :: Python :: Implementation :: PyPy',
 
@@ -185,11 +181,12 @@ def get_version_string():
     'testing': [
         'coverage[toml]>=5.0a4',  # Code coverage measurement for Python
         'pytest>=4.6.11',  # Our tests framework
+        'setuptools>=71.0.0',  # Needed as a dependency for some tests
     ],
     # Dependencies that are required to build documentation
     'docs': [
-        'furo>=2021.8.17b43,==2021.8.*',  # Sphinx documentation theme
-        'sphinx>=3.5.0',  # Python documentation generator
+        'furo>=2024.8.6',  # Sphinx documentation theme
+        'sphinx>=5.0',  # Python documentation generator
         'sphinx-notfound-page',  # Create a custom 404 page
     ],
 }
@@ -232,7 +229,7 @@ def get_version_string():
         platforms=['any'],
         include_package_data=True,
         zip_safe=False,
-        python_requires='>=3.6,<4',
+        python_requires='>=3.9,<4',
         install_requires=INSTALL_REQUIRES,
         dependency_links=DEPENDENCY_LINKS,
         extras_require=EXTRAS_REQUIRE,
diff --git a/tox.ini b/tox.ini
index fa3da3df..a0b8dc28 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021-2023, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
@@ -18,28 +18,23 @@ envlist =
     docs
     lint
     manifest
-    py{36,37,38,39,310,311}-django{111,22}
-    py{36,37,38,39,310,311}-django{30,31,32}
-    py{38,39,310,311}-django{40,41,42}
-    py{310,311}-django{50}
-    pypy-django{111,22,30,31,32}
+    py{39,310,311,312,313}-django{22,30,31,32,40,41,42}
+    py{310,311,312,313}-django{50,51}
+    pypy-django{22,30,31,32}
 
 [gh-actions]
 python =
-    3.6: py36
-    3.7: py37
-    3.8: py38
     3.9: py39
     3.10: py310
     3.11: py311
     3.12: py312
-    pypy-3.7: pypy
+    3.13: py313
+    pypy-3.10: pypy
 
 [testenv]
 description = Unit tests
 extras = testing
 deps =
-    django111: Django>=1.11,<2
     django22: Django>=2.2,<3.0
     django30: Django>=3.0,<3.1
     django31: Django>=3.1,<3.2
@@ -93,7 +88,7 @@ commands =
 description = Check external links in the package documentation
 # Keep basepython in sync with .readthedocs.yml and docs.yml
 # (GitHub Action Workflow).
-basepython = python3.10
+basepython = python3.12
 extras = docs
 commands =
     {envpython} -m sphinx \
@@ -111,7 +106,7 @@ isolated_build = true
 description = Build package documentation (HTML)
 # Keep basepython in sync with .readthedocs.yml and docs.yml
 # (GitHub Action Workflow).
-basepython = python3.10
+basepython = python3.12
 extras = docs
 commands =
     {envpython} -m sphinx \


Test patch:
diff --git a/tests/test_db.py b/tests/test_db.py
index 8101a4a7..a6995fd2 100644
--- a/tests/test_db.py
+++ b/tests/test_db.py
@@ -59,6 +59,14 @@
          '',
          ''
          ),
+        # cockroachdb://username:secret@test.example.com:26258/dbname
+        ('cockroachdb://username:secret@test.example.com:26258/dbname',
+         'django_cockroachdb',
+         'dbname',
+         'test.example.com',
+         'username',
+         'secret',
+         26258),
         # mysqlgis://user:password@host:port/dbname
         ('mysqlgis://enigma:secret@example.com:5431/dbname',
          'django.contrib.gis.db.backends.mysql',
@@ -156,6 +164,7 @@
         'postgis',
         'postgres_cluster',
         'postgres_no_ports',
+        'cockroachdb',
         'mysqlgis',
         'cleardb',
         'mysql_no_password',
diff --git a/tests/test_env.py b/tests/test_env.py
index 85e0499f..ac383773 100644
--- a/tests/test_env.py
+++ b/tests/test_env.py
@@ -407,6 +407,13 @@ def test_prefix(self):
         self.env.prefix = 'PREFIX_'
         assert self.env('TEST') == 'foo'
 
+    def test_prefix_and_not_present_without_default(self):
+        self.env.prefix = 'PREFIX_'
+        with pytest.raises(ImproperlyConfigured) as excinfo:
+            self.env('not_present')
+        assert str(excinfo.value) == 'Set the PREFIX_not_present environment variable'
+        assert excinfo.value.__cause__ is not None
+
 
 class TestFileEnv(TestEnv):
     def setup_method(self, method):