Compare commits

...

92 commits

Author SHA1 Message Date
dependabot[bot]
b73ad6d9c9
Bump django from 5.2.11 to 5.2.12 (#299)
Some checks failed
test / formatting (push) Has been cancelled
test / typecheck (push) Has been cancelled
test / test (4.2, 3.10) (push) Has been cancelled
test / test (4.2, 3.11) (push) Has been cancelled
test / test (4.2, 3.12) (push) Has been cancelled
test / test (5.2, 3.10) (push) Has been cancelled
test / test (5.2, 3.11) (push) Has been cancelled
test / test (5.2, 3.12) (push) Has been cancelled
test / test (5.2, 3.13) (push) Has been cancelled
test / test (5.2, 3.14) (push) Has been cancelled
test / test (6.0, 3.12) (push) Has been cancelled
test / test (6.0, 3.13) (push) Has been cancelled
test / test (6.0, 3.14) (push) Has been cancelled
Bumps [django](https://github.com/django/django) from 5.2.11 to 5.2.12.
- [Commits](https://github.com/django/django/compare/5.2.11...5.2.12)

---
updated-dependencies:
- dependency-name: django
  dependency-version: 5.2.12
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-05 11:29:02 +00:00
pre-commit-ci[bot]
088b969573
[pre-commit.ci] pre-commit autoupdate (#298)
Some checks failed
test / formatting (push) Has been cancelled
test / typecheck (push) Has been cancelled
test / test (4.2, 3.10) (push) Has been cancelled
test / test (4.2, 3.11) (push) Has been cancelled
test / test (4.2, 3.12) (push) Has been cancelled
test / test (5.2, 3.10) (push) Has been cancelled
test / test (5.2, 3.11) (push) Has been cancelled
test / test (5.2, 3.12) (push) Has been cancelled
test / test (5.2, 3.13) (push) Has been cancelled
test / test (5.2, 3.14) (push) Has been cancelled
test / test (6.0, 3.12) (push) Has been cancelled
test / test (6.0, 3.13) (push) Has been cancelled
test / test (6.0, 3.14) (push) Has been cancelled
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.15.1 → v0.15.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.1...v0.15.4)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-03-03 10:50:11 +00:00
pre-commit-ci[bot]
e77149f799
[pre-commit.ci] pre-commit autoupdate (#297)
Some checks failed
test / formatting (push) Has been cancelled
test / typecheck (push) Has been cancelled
test / test (4.2, 3.10) (push) Has been cancelled
test / test (4.2, 3.11) (push) Has been cancelled
test / test (4.2, 3.12) (push) Has been cancelled
test / test (5.2, 3.10) (push) Has been cancelled
test / test (5.2, 3.11) (push) Has been cancelled
test / test (5.2, 3.12) (push) Has been cancelled
test / test (5.2, 3.13) (push) Has been cancelled
test / test (5.2, 3.14) (push) Has been cancelled
test / test (6.0, 3.12) (push) Has been cancelled
test / test (6.0, 3.13) (push) Has been cancelled
test / test (6.0, 3.14) (push) Has been cancelled
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.15.0 → v0.15.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.0...v0.15.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-02-18 20:23:54 +00:00
Matt Seymour
6beffe6de6 Fix a regression in adding tests/ dir to source package 2026-02-18 20:22:03 +00:00
dependabot[bot]
f9c31305ae
Bump wheel from 0.45.1 to 0.46.2 (#296)
Some checks failed
test / formatting (push) Has been cancelled
test / typecheck (push) Has been cancelled
test / test (4.2, 3.10) (push) Has been cancelled
test / test (4.2, 3.11) (push) Has been cancelled
test / test (4.2, 3.12) (push) Has been cancelled
test / test (5.2, 3.10) (push) Has been cancelled
test / test (5.2, 3.11) (push) Has been cancelled
test / test (5.2, 3.12) (push) Has been cancelled
test / test (5.2, 3.13) (push) Has been cancelled
test / test (5.2, 3.14) (push) Has been cancelled
test / test (6.0, 3.12) (push) Has been cancelled
test / test (6.0, 3.13) (push) Has been cancelled
test / test (6.0, 3.14) (push) Has been cancelled
Bumps [wheel](https://github.com/pypa/wheel) from 0.45.1 to 0.46.2.
- [Release notes](https://github.com/pypa/wheel/releases)
- [Changelog](https://github.com/pypa/wheel/blob/main/docs/news.rst)
- [Commits](https://github.com/pypa/wheel/compare/0.45.1...0.46.2)

---
updated-dependencies:
- dependency-name: wheel
  dependency-version: 0.46.2
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-16 16:20:30 +00:00
dependabot[bot]
5337838ac3
Bump urllib3 from 2.6.2 to 2.6.3 (#295)
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.6.2 to 2.6.3.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.6.2...2.6.3)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-version: 2.6.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-16 14:37:21 +00:00
dependabot[bot]
6fc366459f
Bump django from 5.2.9 to 5.2.11 (#294)
Bumps [django](https://github.com/django/django) from 5.2.9 to 5.2.11.
- [Commits](https://github.com/django/django/compare/5.2.9...5.2.11)

---
updated-dependencies:
- dependency-name: django
  dependency-version: 5.2.11
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-16 14:33:26 +00:00
dependabot[bot]
19805c97f3
Bump cryptography from 46.0.3 to 46.0.5 (#293)
Bumps [cryptography](https://github.com/pyca/cryptography) from 46.0.3 to 46.0.5.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/46.0.3...46.0.5)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 46.0.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-16 14:31:12 +00:00
Matt Seymour
1b102cd428
Update project URLs in pyproject.toml 2026-02-16 14:28:06 +00:00
pre-commit-ci[bot]
e41afda72b
[pre-commit.ci] pre-commit autoupdate (#291)
Some checks failed
test / formatting (push) Has been cancelled
test / typecheck (push) Has been cancelled
test / test (4.2, 3.10) (push) Has been cancelled
test / test (4.2, 3.11) (push) Has been cancelled
test / test (4.2, 3.12) (push) Has been cancelled
test / test (5.2, 3.10) (push) Has been cancelled
test / test (5.2, 3.11) (push) Has been cancelled
test / test (5.2, 3.12) (push) Has been cancelled
test / test (5.2, 3.13) (push) Has been cancelled
test / test (5.2, 3.14) (push) Has been cancelled
test / test (6.0, 3.12) (push) Has been cancelled
test / test (6.0, 3.13) (push) Has been cancelled
test / test (6.0, 3.14) (push) Has been cancelled
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.14.13 → v0.15.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.13...v0.15.0)
- [github.com/crate-ci/typos: v1.42.0 → v1](https://github.com/crate-ci/typos/compare/v1.42.0...v1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-02-09 19:36:23 +00:00
Matt Seymour
dba6077081
Update .pre-commit-config.yaml to use pinned version numbers. (#289)
Some checks failed
test / formatting (push) Has been cancelled
test / typecheck (push) Has been cancelled
test / test (4.2, 3.10) (push) Has been cancelled
test / test (4.2, 3.11) (push) Has been cancelled
test / test (4.2, 3.12) (push) Has been cancelled
test / test (5.2, 3.10) (push) Has been cancelled
test / test (5.2, 3.11) (push) Has been cancelled
test / test (5.2, 3.12) (push) Has been cancelled
test / test (5.2, 3.13) (push) Has been cancelled
test / test (5.2, 3.14) (push) Has been cancelled
test / test (6.0, 3.12) (push) Has been cancelled
test / test (6.0, 3.13) (push) Has been cancelled
test / test (6.0, 3.14) (push) Has been cancelled
2026-01-19 10:20:19 +00:00
Matt Seymour
e6f4ccc49d Add pytest to dependencies 2026-01-08 10:58:17 +00:00
Aarni Koskela
a87f12185e Enable annotations future; use futuristic annotations 2026-01-08 10:58:17 +00:00
Fábio C. Barrionuevo da Luz
1ca8ccf7db
Add project URLs to pyproject.toml (#287) 2026-01-08 10:12:50 +00:00
Matt Seymour
afad969e94 Merge branch 'ruff' 2026-01-05 10:41:27 +00:00
Matt Seymour
6273af6660 Simplify release workflow by removing created event type. 2026-01-05 10:41:03 +00:00
Matt Seymour
0a59175489 Add formatting step to workflow, integrate ruff check, and update job dependencies 2026-01-05 10:40:44 +00:00
Matt Seymour
51b51776a9
Merge branch 'master' into ruff 2026-01-05 10:29:27 +00:00
Yohaan Narayanan
d8f63c8801
Add tests directory to source distribution (#285) 2026-01-04 16:19:05 -08:00
Matt Seymour
36ec238b03 Update CHANGELOG.md 2026-01-03 09:53:03 +00:00
Matt Seymour
84fd0508ff Switch to modern dict type hints for improved readability and consistency. 2025-12-19 13:14:11 +00:00
Aarni Koskela
2bf2118232 Add crate-ci/typos to pre-commit; fix typos 2025-12-19 12:06:17 +02:00
Aarni Koskela
527962ec04 Switch linting and formatting from flake8+isort+black to ruff 2025-12-19 12:05:33 +02:00
Matt Seymour
992999193d Merge branch 'akx/ci-split' 2025-12-19 09:19:01 +00:00
Matt Seymour
59b3cd6283 Update workflow to be uv compatable 2025-12-19 09:17:33 +00:00
Matt Seymour
37b252e678 Migrate release workflow to release events, update actions, and integrate uv for builds. 2025-12-16 15:54:59 +00:00
Matt Seymour
a01d6b1551 Update project to use uv. 2025-12-16 14:18:42 +00:00
Matt Seymour
989429838a Expand test matrix to support Python 3.14 and Django 6.0, update compatibility exclusions 2025-12-15 16:15:57 +00:00
GabrielBarrantes
438ec393ab
Update license to BSD-3-Clause in setup.py (#279) 2025-12-15 15:58:23 +00:00
pre-commit-ci[bot]
8a3c372deb
[pre-commit.ci] pre-commit autoupdate (#278)
updates:
- [github.com/pycqa/isort: 6.0.1 → 7.0.0](https://github.com/pycqa/isort/compare/6.0.1...7.0.0)
- https://github.com/psf/blackhttps://github.com/psf/black-pre-commit-mirror
- [github.com/psf/black-pre-commit-mirror: 25.9.0 → 25.12.0](https://github.com/psf/black-pre-commit-mirror/compare/25.9.0...25.12.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-12-15 15:56:33 +00:00
pre-commit-ci[bot]
390d524112
[pre-commit.ci] pre-commit autoupdate (#276)
updates:
- [github.com/psf/black: 25.1.0 → 25.9.0](https://github.com/psf/black/compare/25.1.0...25.9.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-09-22 18:45:50 +01:00
Aarni Koskela
58ed5a2276 CI: run typechecks only once, not for each Django/Python version 2025-08-20 12:15:31 +03:00
pre-commit-ci[bot]
7cc1121b29
[pre-commit.ci] pre-commit autoupdate (#270)
updates:
- [github.com/pre-commit/pre-commit-hooks: v5.0.0 → v6.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v5.0.0...v6.0.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-08-11 21:06:04 +01:00
Matt Seymour
0819957676 Allow conn_max_age to accept None for flexibility in configuration 2025-07-11 17:15:17 +01:00
pre-commit-ci[bot]
34170b5aef
[pre-commit.ci] pre-commit autoupdate (#268)
updates:
- [github.com/pycqa/flake8: 7.2.0 → 7.3.0](https://github.com/pycqa/flake8/compare/7.2.0...7.3.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-07-11 16:22:42 +01:00
Matt Seymour
ab20a3aebd
Update setup.py 2025-07-02 10:28:15 +01:00
Matt Seymour
7d47e451a0
Update CHANGELOG.md 2025-07-02 10:28:00 +01:00
Ed Morley
ae2fabf95e
Re-drop dependency on typing_extensions (#269)
Since it was only being used for `TypedDict`, which was added in
Python 3.8 and `dj-database-url` v2.3.0+ only supports Python 3.9+.

See:
https://docs.python.org/3.12/library/typing.html#typing.TypedDict
https://github.com/jazzband/dj-database-url/blob/master/CHANGELOG.md#v230-2024-10-23

Note: This is a repeat of #257, since that PR was lost as part
of merges that occurred during the release of 3.0.0:
https://github.com/jazzband/dj-database-url/pull/257#issuecomment-3023857093
2025-07-01 18:53:01 +01:00
Matt Seymour
7c47649053
Update CHANGELOG.md 2025-06-23 08:54:54 +01:00
Matt Seymour
3e8b69aafc Update python supported version
Pre-empt python 3.14 support.
2025-06-20 20:19:31 +01:00
Matt Seymour
4977c7d0d6
Update package version to 3.0.0 2025-05-18 20:46:58 +01:00
Matt Seymour
a139abcf22
Update project dependencies
Adds typing_extensions
2025-05-18 20:46:51 +01:00
Matt Seymour
e4ccc63ec6
Remove Python 3.9 support from setup.py
Python 3.9 is no longer listed as a supported version in setup.py.
2025-05-09 10:29:33 +01:00
Matt Seymour
9598fb0d85
Add support for Django 5.2 in classifiers
This commit updates the setup.py file to include Django 5.2 in the list of supported framework versions.
2025-05-09 10:29:16 +01:00
Matt Seymour
cdb5d645ec
Update tests 2025-04-30 14:13:38 +01:00
Matt Seymour
15ac40b0a7
Fix pyright issues 2025-04-30 14:08:18 +01:00
pre-commit-ci[bot]
79d929b4e0 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2025-04-30 12:29:23 +00:00
Matt Seymour
c469885ebf
Merge branch 'master' into master 2025-04-30 13:29:15 +01:00
pre-commit-ci[bot]
e90f975526
[pre-commit.ci] pre-commit autoupdate (#262)
updates:
- [github.com/pycqa/isort: 6.0.0 → 6.0.1](https://github.com/pycqa/isort/compare/6.0.0...6.0.1)
- [github.com/pycqa/flake8: 7.1.1 → 7.2.0](https://github.com/pycqa/flake8/compare/7.1.1...7.2.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-04-29 09:39:28 +01:00
Adam Johnson
5f5af49340
Fix type errors (#261)
First, Mypy reported a lot of issues for the tests not including  `-> None` annotations. After adding them, there were lots of issues due to the `url` variables being reassigned from `str` to `DBConfig` objects, fixed by inlining the URLs into the `parse()` calls.
2025-02-16 07:53:52 +00:00
pre-commit-ci[bot]
8501773f73
[pre-commit.ci] pre-commit autoupdate (#259)
updates:
- [github.com/pycqa/isort: 5.13.2 → 6.0.0](https://github.com/pycqa/isort/compare/5.13.2...6.0.0)
- [github.com/psf/black: 24.10.0 → 25.1.0](https://github.com/psf/black/compare/24.10.0...25.1.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-02-03 18:13:35 +00:00
Ed Morley
358746c41d
Drop dependency on typing_extensions (#257)
Since it was only being used for `TypedDict`, which was added in
Python 3.8 and `dj-database-url` v2.3.0+ only supports Python 3.9+.

See:
https://docs.python.org/3.12/library/typing.html#typing.TypedDict
https://github.com/jazzband/dj-database-url/blob/master/CHANGELOG.md#v230-2024-10-23
2024-11-22 12:31:02 +00:00
Matt Seymour
0f8cfdf1c9
Update test.yml 2024-10-23 10:57:17 +01:00
Matt Seymour
3505200b18
Update CHANGELOG.md 2024-10-23 10:55:15 +01:00
Matt Seymour
396344b251
Update setup.py
Bump version 2.3.0 ready for next release.
2024-10-23 10:53:25 +01:00
Matt Seymour
64660c7c5f
Update setup.py
Updated supported versions of python and django
2024-10-23 10:52:32 +01:00
pre-commit-ci[bot]
eff9f27514
[pre-commit.ci] pre-commit autoupdate (#256)
updates:
- [github.com/psf/black: 24.8.0 → 24.10.0](https://github.com/psf/black/compare/24.8.0...24.10.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-10-23 10:50:06 +01:00
Tom Parker-Shemilt
08d9cf0978
Add Python 3.13 and Django 5.1 testing (#255)
* Add Python 3.13 and Django 5.1 testing
* Upgrade to modern actions
2024-10-10 23:00:30 +01:00
Tom Parker-Shemilt
99128b507b
Make config test options not unknown types (#252)
* Make config test options not unknown types
* Run pyright as part of tests
2024-10-10 22:45:31 +01:00
pre-commit-ci[bot]
45ac3bbf89
[pre-commit.ci] pre-commit autoupdate (#254)
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.6.0 → v5.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.6.0...v5.0.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-10-08 14:33:05 -05:00
Lino
8d32603e18
Update CHANGELOG.md
As requested in https://github.com/jazzband/dj-database-url/issues/250
2024-10-03 05:46:26 +02:00
pre-commit-ci[bot]
ed306f37ca
[pre-commit.ci] pre-commit autoupdate (#251)
updates:
- [github.com/psf/black: 24.4.2 → 24.8.0](https://github.com/psf/black/compare/24.4.2...24.8.0)
- [github.com/pycqa/flake8: 7.1.0 → 7.1.1](https://github.com/pycqa/flake8/compare/7.1.0...7.1.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-08-27 09:26:28 +01:00
pre-commit-ci[bot]
3aad9adc71
[pre-commit.ci] pre-commit autoupdate (#249)
updates:
- [github.com/pycqa/flake8: 7.0.0 → 7.1.0](https://github.com/pycqa/flake8/compare/7.0.0...7.1.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-06-19 10:52:48 +01:00
Matt Seymour
51773f3a89
Update setup.py 2024-05-28 13:23:37 +01:00
boxydog
5a91d7a2c7
Update README.rst (#247)
Add `postgresql` as an alternative URL, which works and is in the code.
2024-05-28 13:11:20 +01:00
pre-commit-ci[bot]
cda5b0c3ce
[pre-commit.ci] pre-commit autoupdate (#245)
updates:
- [github.com/psf/black: 24.4.0 → 24.4.2](https://github.com/psf/black/compare/24.4.0...24.4.2)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-05-28 13:07:41 +01:00
pre-commit-ci[bot]
7906f3bf80
[pre-commit.ci] pre-commit autoupdate (#244)
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.5.0 → v4.6.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.5.0...v4.6.0)
- [github.com/psf/black: 24.2.0 → 24.4.0](https://github.com/psf/black/compare/24.2.0...24.4.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-04-22 11:47:10 +01:00
pre-commit-ci[bot]
5106a19129
[pre-commit.ci] pre-commit autoupdate (#242)
updates:
- [github.com/psf/black: 23.12.1 → 24.2.0](https://github.com/psf/black/compare/23.12.1...24.2.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-03-14 16:15:34 +00:00
Alexander Gerbik
b27bd63e82 Generic backend registration 2024-02-29 14:09:10 +01:00
Gert Burger
bcf163e3ab
Check list of schemes to determine if search_path option should be added (#243) 2024-02-20 00:02:06 +00:00
pre-commit-ci[bot]
86f04ef5c6
[pre-commit.ci] pre-commit autoupdate (#241)
updates:
- [github.com/pycqa/flake8: 6.1.0 → 7.0.0](https://github.com/pycqa/flake8/compare/6.1.0...7.0.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-01-09 15:57:45 +00:00
Matt Seymour
fc0dca1b25
Update django 5.0 python compatability (#239) 2023-12-27 16:47:04 +00:00
pre-commit-ci[bot]
ebb86f9b9a
[pre-commit.ci] pre-commit autoupdate (#238)
updates:
- [github.com/psf/black: 23.12.0 → 23.12.1](https://github.com/psf/black/compare/23.12.0...23.12.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-12-27 16:34:07 +00:00
pre-commit-ci[bot]
1412cef6b5
[pre-commit.ci] pre-commit autoupdate (#237)
updates:
- [github.com/pycqa/isort: 5.13.0 → 5.13.2](https://github.com/pycqa/isort/compare/5.13.0...5.13.2)
- [github.com/psf/black: 23.11.0 → 23.12.0](https://github.com/psf/black/compare/23.11.0...23.12.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-12-21 12:50:55 +00:00
pre-commit-ci[bot]
186900d937
[pre-commit.ci] pre-commit autoupdate (#236)
updates:
- [github.com/pycqa/isort: 5.12.0 → 5.13.0](https://github.com/pycqa/isort/compare/5.12.0...5.13.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-12-12 16:25:10 +00:00
Tom Parker-Shemilt
d23a2ca4c5
OPTIONS cannot be None given our code (#232)
As per the notes in https://github.com/jazzband/dj-database-url/issues/218
2023-12-09 09:41:09 +00:00
truongvan
0879f127b9
Enhance Query String Parsing for Server-Side Binding in Django 4.2 with psycopg 3.1.8+ Resolves #234 (#235)
* Refactor parsing of query string to handle int, bool, and text types
2023-12-09 09:39:35 +00:00
pre-commit-ci[bot]
9a74cc5283
[pre-commit.ci] pre-commit autoupdate (#233)
updates:
- [github.com/psf/black: 23.10.1 → 23.11.0](https://github.com/psf/black/compare/23.10.1...23.11.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-11-13 18:26:14 +00:00
Fabian Binz
44bfba5fa2
Add disable_server_side_cursors parameter (#231) 2023-11-08 22:21:29 +00:00
pre-commit-ci[bot]
ee2cb860f5
[pre-commit.ci] pre-commit autoupdate (#229)
updates:
- [github.com/psf/black: 23.10.0 → 23.10.1](https://github.com/psf/black/compare/23.10.0...23.10.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-10-30 17:54:47 +00:00
pre-commit-ci[bot]
9ea2808ed2
[pre-commit.ci] pre-commit autoupdate (#228)
updates:
- [github.com/psf/black: 23.9.1 → 23.10.0](https://github.com/psf/black/compare/23.9.1...23.10.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-10-23 19:24:42 +01:00
pre-commit-ci[bot]
2d46742db5
[pre-commit.ci] pre-commit autoupdate (#227)
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.4.0 → v4.5.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.4.0...v4.5.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-10-10 09:23:03 +01:00
pre-commit-ci[bot]
4a5a75c0f2
[pre-commit.ci] pre-commit autoupdate (#223)
updates:
- [github.com/psf/black: 23.3.0 → 23.9.1](https://github.com/psf/black/compare/23.3.0...23.9.1)
- [github.com/pycqa/flake8: 6.0.0 → 6.1.0](https://github.com/pycqa/flake8/compare/6.0.0...6.1.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-09-26 18:55:22 +01:00
Nikita Sobolev
f600d9089b
Do not leak key loop variable into global namespace (#226)
This is a common problem for module level loops / helpers / etc.

`key` is importable name, it is even imported when using `from dj_database_url import *`. This is clearly not what we want.
2023-09-26 18:52:20 +01:00
Matt Seymour
9292e1fa8a
Update setup.py 2023-08-15 14:11:14 +01:00
Matt Seymour
321a7c207a
Update release.yml
Update version pinning of pypi-publish github action
2023-08-15 13:06:29 +01:00
Matt Seymour
ed68bff3c8
Update CHANGELOG.md 2023-08-15 12:29:47 +01:00
Enrico Stahn
13be1aa9b9
fix: parse options with numerical values as int (#225)
* fix: parse options with numerical values as int

fixes #222

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-08-15 12:28:05 +01:00
Matt Seymour
9b0f325910
Bump version 2.0.0 and update CHANGELOG.md 2023-04-27 16:28:38 +01:00
Tom Parker-Shemilt
a7f94c8354
Redo as a package to fix mypy issues (#215)
* Demo the mypy types issue

* Redo as a package

* Update setup.py

---------

Co-authored-by: Matt Seymour <mattaseymour@gmail.com>
2023-04-13 10:59:57 +01:00
pre-commit-ci[bot]
bac900f78a
[pre-commit.ci] pre-commit autoupdate (#216)
updates:
- [github.com/psf/black: 23.1.0 → 23.3.0](https://github.com/psf/black/compare/23.1.0...23.3.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-04-13 10:53:03 +01:00
Michał Górny
84d75e9087
setup.py: Fix project_urls parameter name (#212) 2023-04-02 12:29:31 +01:00
17 changed files with 1784 additions and 464 deletions

View file

@ -1,5 +0,0 @@
[flake8]
max-line-length = 88
extend-ignore = E203
per-file-ignores=
test_dj_database_url.py: E501, E265

View file

@ -1,9 +1,8 @@
name: Release
on:
push:
tags:
- '*'
release:
types: [published]
jobs:
build:
@ -11,29 +10,30 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.10'
- name: Install dependencies
- name: Set version from release/tag
id: version
run: |
python -m pip install -U pip
python -m pip install -U setuptools wheel twine
VERSION=${GITHUB_REF#refs/*/}
VERSION=${VERSION#v}
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "version=$VERSION" >> $GITHUB_OUTPUT
- uses: astral-sh/setup-uv@v7
with:
python-version: 3.12
- name: Build package
run: |
python setup.py --version
python setup.py sdist --format=gztar bdist_wheel
twine check dist/*
uv version ${{ env.VERSION }}
uv build
uvx twine check dist/*
- name: Upload packages to Jazzband
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@master
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: jazzband
password: ${{ secrets.JAZZBAND_RELEASE_KEY }}

View file

@ -1,49 +1,74 @@
name: test
on: [push, pull_request]
jobs:
formatting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
python-version: "3.12"
- name: Run ruff
run: uvx ruff check
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
python-version: "3.12"
- name: Run mypy
run: uvx mypy dj_database_url
- name: Run pyright
run: uvx pyright dj_database_url
test:
runs-on: ubuntu-latest
needs: [formatting, typecheck]
strategy:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
django-version: ["3.2", "4.0", "4.1"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
django-version: ["4.2", "5.2", "6.0"]
exclude:
# Python 3.7 is not compatible with 4.0
- python-version: "3.7"
django-version: "4.0"
# Python 3.7 is not compatible with 4.1
- python-version: "3.7"
django-version: "4.1"
# Python 3.11 is not compatible with 3.2
# django 4.x is not compatible with python 3.13 or higher
- python-version: "3.13"
django-version: "4.2"
- python-version: "3.14"
django-version: "4.2"
# django 6.x is not compatible with python 3.11 or lower
- python-version: "3.10"
django-version: "6.0"
- python-version: "3.11"
django-version: "3.2"
# Python 3.11 is not compatible with 4.0
- python-version: "3.11"
django-version: "4.0"
steps:
- uses: actions/checkout@v3
django-version: "6.0"
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
steps:
- uses: actions/checkout@v4
- name: Install uv and set the Python version
uses: astral-sh/setup-uv@v7
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
activate-environment: 'true'
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install "Django~=${{ matrix.django-version }}.0" .
- name: Run mypy
run: |
python -m mypy dj_database_url.py
uv pip install "Django~=${{ matrix.django-version }}.0"
- name: Run Tests
run: |
echo "$(python --version) / Django $(django-admin --version)"
coverage run --source=dj_database_url --branch -m unittest discover
coverage report
coverage xml
uvx coverage run --source=dj_database_url --branch -m unittest discover -v
uvx coverage report
uvx coverage xml
- uses: codecov/codecov-action@v3
- uses: codecov/codecov-action@v4

View file

@ -1,2 +0,0 @@
[settings]
profile = black

View file

@ -1,23 +1,21 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v6.0.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/pycqa/isort
rev: "5.12.0"
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.4
hooks:
- id: isort
args: ["--profile", "black"]
- id: ruff-check
args:
- --fix
- id: ruff-format
args:
- --quiet
- repo: https://github.com/psf/black
rev: 23.1.0
- repo: https://github.com/crate-ci/typos
rev: v1
hooks:
- id: black
args: [--target-version=py38]
- repo: https://github.com/pycqa/flake8
rev: '6.0.0'
hooks:
- id: flake8
- id: typos

View file

@ -1,38 +1,69 @@
# CHANGELOG
## v1.3.0 (2023-03-27)
## v3.1.0 (2026-01-03)
* Add support for Django 6.0
* Update CI structure.
* Migrate to UV for dependency management and builds.
* Python >3.10 support.
## v3.0.1 (2025-07-01)
* Drop dependency on `typing_extensions`.
## v3.0.0 (2025-05-18)
> Bumping to version 3; changes to code do break some API compatibility.
* Implement a new decorator registry pattern to implement checks on database connection string.
* You can now support and implement your own database strings by extending the @register functionality.
* Update supported python versions and django versions.
## v2.3.0 (2024-10-23)
* Remove Python 3.8 support.
* Remove Django 3 support.
* Add python 3.13 support.
* Add Django 5.1 to the testing library.
## v2.2.0 (2024-05-28)
* Add disable_server_side_cursors parameter
* Enhance Query String Parsing for Server-Side Binding in Django 4.2 with psycopg 3.1.8+
* Update django 5.0 python compatibility by @mattseymour in #239
* Improved internals
* Improved documentation
## v2.1.0 (2023-08-15)
* Add value to int parsing when deconstructing url string.
## v2.0.0 (2023-04-27)
* Update project setup such that we now install it as a package.
_Notes_: while this does not alter the underlying application code, we are bumping to
2.0 incase there are unforeseen knock-on use-case issues.
## v1.3.0 (2023-03-27)
* Cosmetic changes to the generation of schemes.
* Bump isort version - 5.11.5.
* raise warning message if database_url is not set.
* CONN_MAX_AGE fix type - Optional[int].
* raise a warning message if `database_url` is not set.
* `CONN_MAX_AGE` fix type - `Optional[int]`.
## v1.2.0 (2022-12-13)
* Add the ability to add test databases.
* Improve url parsing and encoding.
* Fix missing parameter conn_health_check in check function.
* Fix missing parameter `conn_health_check` in check function.
## v1.1.0 (2022-12-12)
* Option for connection health checks parameter.
* Update supported version python 3.11.
* Code changes, various improvments.
* Add project links to setup.py
* Code changes, various improvements.
* Add project links to `setup.py`.
## v1.0.0 (2022-06-18)
Initial release of code now dj-database-urls is part of jazzband.
Initial release of code, dj-database-urls is now part of [Jazzband](https://jazzband.co/).
* Add support for cockroachdb.
* Add support for the offical MSSQL connector.
* Add support for the official MSSQL connector.
* Update License to be compatible with Jazzband.
* Remove support for Python < 3.5 including Python 2.7
* Update source code to Black format.
* Update CI using pre-commit
## v0.5.0 (2018-03-01)
- Use str port for mssql
- Added license
- Add mssql to readme
@ -52,17 +83,15 @@ Initial release of code now dj-database-urls is part of jazzband.
- Added SpatiaLite in README.rst
## v0.4.1 (2016-04-06)
- Enable CA providing for MySQL URIs
- Update Readme
- Update trove classifiers
- Updated setup.py description
## v0.4.0 (2016-02-04)
- Update readme
- Fix for python3
- Handle search path config in connect url for postgres
- Handle search path config in connection url for postgres
- Add tox config to ease testing against multiple Python versions
- Simplified the querystring parse logic
- Cleaned up querystring parsing
@ -79,27 +108,25 @@ Initial release of code now dj-database-urls is part of jazzband.
- Added support for python mysql-connector
## v0.3.0 (2014-03-10)
- Add .gitignore file
- Remove .pyc file
- Remove travis-ci unsupported python version Per docs http://docs.travis-ci.com/user/languages/python/ "Travis CI support Python versions 2.6, 2.7, 3.2 and 3.3"
- Add `.gitignore` file
- Remove `.pyc` file
- Remove travis-ci unsupported python versions (Travis CI supports Python versions 2.6, 2.7, 3.2 and 3.3)
- Fix cleardb test
- Add setup.cfg for wheel support
- Add `setup.cfg` for wheel support
- Add trove classifiers for python versions
- Replace Python 3.1 with Python 3.3
- Add MySQL (GIS) support
- Ability to set different engine
## v0.2.2 (2013-07-17)
- Added spatialite to uses_netloc too
- Added spatialite backend
- Replacing tab with spaces
- Handling special case of sqlite://:memory:
- Handling special case of `sqlite://:memory:`
- Empty sqlite path will now use a :memory: database
- Fixing test to actually use the result of the parse
- Adding in tests to ensure sqlite in-memory databases work
- Fixed too-short title underline
- Fixed a too-short title underline
- Added :target: attribute to Travis status image in README
- Added docs for default argument to config
- Add "pgsql" as a PostgreSQL URL scheme.
@ -107,7 +134,6 @@ Initial release of code now dj-database-urls is part of jazzband.
- fixed url
## v0.2.1 (2012-06-19)
- Add python3 support
- Adding travis status and tests
- Adding test environment variables
@ -119,28 +145,23 @@ Initial release of code now dj-database-urls is part of jazzband.
- RedHat's OpenShift platform uses the 'postgresql' scheme
- Registered postgis URL scheme
- Added `postgis://` url scheme
- Use get() on os.environ instead of an if
- Use `get()` on `os.environ` instead of an `if`.
## v0.2.0 (2012-05-30)
- Fix parse(s)
## v0.1.4 (2012-05-30)
- Add defaults for env
- Set the DATABASES dict rather than assigning to it
## v0.1.3 (2012-05-01)
- Add note to README on supported databases
- Add a note to README on supported databases
- Add support for SQLite
- Clean dependencies
## v0.1.2 (2012-04-30)
- Update readme
- Refactor config and use new parse function
- Refactor config and use a new parse function
## v0.1.1 (2012-04-30) First release
🐍 ✨

View file

@ -1 +0,0 @@
include py.typed

View file

@ -22,12 +22,6 @@ also a `conn_max_age` argument to easily enable Django's connection pool.
If you'd rather not use an environment variable, you can pass a URL in directly
instead to ``dj_database_url.parse``.
Supported Databases
-------------------
Support currently exists for PostgreSQL, PostGIS, MySQL, MySQL (GIS),
Oracle, Oracle (GIS), Redshift, CockroachDB, Timescale, Timescale (GIS) and SQLite.
Installation
------------
@ -148,6 +142,63 @@ and should instead be passed as:
DATABASES['default'] = dj_database_url.config(default='postgres://...', test_options={'NAME': 'mytestdatabase'})
Supported Databases
-------------------
Support currently exists for PostgreSQL, PostGIS, MySQL, MySQL (GIS),
Oracle, Oracle (GIS), Redshift, CockroachDB, Timescale, Timescale (GIS) and SQLite.
If you want to use
some non-default backends, you need to register them first:
.. code-block:: python
import dj_database_url
# registration should be performed only once
dj_database_url.register("mysql-connector", "mysql.connector.django")
assert dj_database_url.parse("mysql-connector://user:password@host:port/db-name") == {
"ENGINE": "mysql.connector.django",
# ...other connection params
}
Some backends need further config adjustments (e.g. oracle and mssql
expect ``PORT`` to be a string). For such cases you can provide a
post-processing function to ``register()`` (note that ``register()`` is
used as a **decorator(!)** in this case):
.. code-block:: python
import dj_database_url
@dj_database_url.register("mssql", "sql_server.pyodbc")
def stringify_port(config):
config["PORT"] = str(config["PORT"])
@dj_database_url.register("redshift", "django_redshift_backend")
def apply_current_schema(config):
options = config["OPTIONS"]
schema = options.pop("currentSchema", None)
if schema:
options["options"] = f"-c search_path={schema}"
@dj_database_url.register("snowflake", "django_snowflake")
def adjust_snowflake_config(config):
config.pop("PORT", None)
config["ACCOUNT"] = config.pop("HOST")
name, _, schema = config["NAME"].partition("/")
if schema:
config["SCHEMA"] = schema
config["NAME"] = name
options = config.get("OPTIONS", {})
warehouse = options.pop("warehouse", None)
if warehouse:
config["WAREHOUSE"] = warehouse
role = options.pop("role", None)
if role:
config["ROLE"] = role
URL schema
----------
@ -155,6 +206,7 @@ URL schema
| Engine | Django Backend | URL |
+======================+===============================================+==================================================+
| PostgreSQL | ``django.db.backends.postgresql`` [1]_ | ``postgres://USER:PASSWORD@HOST:PORT/NAME`` [2]_ |
| | | ``postgresql://USER:PASSWORD@HOST:PORT/NAME`` |
+----------------------+-----------------------------------------------+--------------------------------------------------+
| PostGIS | ``django.contrib.gis.db.backends.postgis`` | ``postgis://USER:PASSWORD@HOST:PORT/NAME`` |
+----------------------+-----------------------------------------------+--------------------------------------------------+

View file

@ -1,184 +0,0 @@
import logging
import os
import urllib.parse as urlparse
from typing import Any, Dict, Optional, Union
from typing_extensions import TypedDict
DEFAULT_ENV = "DATABASE_URL"
SCHEMES = {
"postgres": "django.db.backends.postgresql",
"postgresql": "django.db.backends.postgresql",
"pgsql": "django.db.backends.postgresql",
"postgis": "django.contrib.gis.db.backends.postgis",
"mysql": "django.db.backends.mysql",
"mysql2": "django.db.backends.mysql",
"mysqlgis": "django.contrib.gis.db.backends.mysql",
"mysql-connector": "mysql.connector.django",
"mssql": "sql_server.pyodbc",
"mssqlms": "mssql",
"spatialite": "django.contrib.gis.db.backends.spatialite",
"sqlite": "django.db.backends.sqlite3",
"oracle": "django.db.backends.oracle",
"oraclegis": "django.contrib.gis.db.backends.oracle",
"redshift": "django_redshift_backend",
"cockroach": "django_cockroachdb",
"timescale": "timescale.db.backends.postgresql",
"timescalegis": "timescale.db.backends.postgis",
}
# Register database schemes in URLs.
for key in SCHEMES.keys():
urlparse.uses_netloc.append(key)
# From https://docs.djangoproject.com/en/4.0/ref/settings/#databases
class DBConfig(TypedDict, total=False):
ATOMIC_REQUESTS: bool
AUTOCOMMIT: bool
CONN_MAX_AGE: Optional[int]
CONN_HEALTH_CHECKS: bool
DISABLE_SERVER_SIDE_CURSORS: bool
ENGINE: str
HOST: str
NAME: str
OPTIONS: Optional[Dict[str, Any]]
PASSWORD: str
PORT: Union[str, int]
TEST: Dict[str, Any]
TIME_ZONE: str
USER: str
def config(
env: str = DEFAULT_ENV,
default: Optional[str] = None,
engine: Optional[str] = None,
conn_max_age: Optional[int] = 0,
conn_health_checks: bool = False,
ssl_require: bool = False,
test_options: Optional[Dict] = None,
) -> DBConfig:
"""Returns configured DATABASE dictionary from DATABASE_URL."""
s = os.environ.get(env, default)
if s is None:
logging.warning(
"No %s environment variable set, and so no databases setup" % env
)
if s:
return parse(
s, engine, conn_max_age, conn_health_checks, ssl_require, test_options
)
return {}
def parse(
url: str,
engine: Optional[str] = None,
conn_max_age: Optional[int] = 0,
conn_health_checks: bool = False,
ssl_require: bool = False,
test_options: Optional[dict] = None,
) -> DBConfig:
"""Parses a database URL."""
if url == "sqlite://:memory:":
# this is a special case, because if we pass this URL into
# urlparse, urlparse will choke trying to interpret "memory"
# as a port number
return {"ENGINE": SCHEMES["sqlite"], "NAME": ":memory:"}
# note: no other settings are required for sqlite
# otherwise parse the url as normal
parsed_config: DBConfig = {}
if test_options is None:
test_options = {}
spliturl = urlparse.urlsplit(url)
# Split query strings from path.
path = spliturl.path[1:]
query = urlparse.parse_qs(spliturl.query)
# If we are using sqlite and we have no path, then assume we
# want an in-memory database (this is the behaviour of sqlalchemy)
if spliturl.scheme == "sqlite" and path == "":
path = ":memory:"
# Handle postgres percent-encoded paths.
hostname = spliturl.hostname or ""
if "%" in hostname:
# Switch to url.netloc to avoid lower cased paths
hostname = spliturl.netloc
if "@" in hostname:
hostname = hostname.rsplit("@", 1)[1]
# Use URL Parse library to decode % encodes
hostname = urlparse.unquote(hostname)
# Lookup specified engine.
if engine is None:
engine = SCHEMES.get(spliturl.scheme)
if engine is None:
raise ValueError(
"No support for '%s'. We support: %s"
% (spliturl.scheme, ", ".join(sorted(SCHEMES.keys())))
)
port = (
str(spliturl.port)
if spliturl.port
and engine in (SCHEMES["oracle"], SCHEMES["mssql"], SCHEMES["mssqlms"])
else spliturl.port
)
# Update with environment configuration.
parsed_config.update(
{
"NAME": urlparse.unquote(path or ""),
"USER": urlparse.unquote(spliturl.username or ""),
"PASSWORD": urlparse.unquote(spliturl.password or ""),
"HOST": hostname,
"PORT": port or "",
"CONN_MAX_AGE": conn_max_age,
"CONN_HEALTH_CHECKS": conn_health_checks,
"ENGINE": engine,
}
)
if test_options:
parsed_config.update(
{
'TEST': test_options,
}
)
# Pass the query string into OPTIONS.
options: Dict[str, Any] = {}
for key, values in query.items():
if spliturl.scheme == "mysql" and key == "ssl-ca":
options["ssl"] = {"ca": values[-1]}
continue
options[key] = values[-1]
if ssl_require:
options["sslmode"] = "require"
# Support for Postgres Schema URLs
if "currentSchema" in options and engine in (
"django.contrib.gis.db.backends.postgis",
"django.db.backends.postgresql_psycopg2",
"django.db.backends.postgresql",
"django_redshift_backend",
"timescale.db.backends.postgresql",
"timescale.db.backends.postgis",
):
options["options"] = "-c search_path={0}".format(options.pop("currentSchema"))
if options:
parsed_config["OPTIONS"] = options
return parsed_config

253
dj_database_url/__init__.py Normal file
View file

@ -0,0 +1,253 @@
import logging
import os
import urllib.parse as urlparse
from collections.abc import Callable
from typing import Any, TypedDict
DEFAULT_ENV = "DATABASE_URL"
ENGINE_SCHEMES: dict[str, "Engine"] = {}
# From https://docs.djangoproject.com/en/stable/ref/settings/#databases
class DBConfig(TypedDict, total=False):
ATOMIC_REQUESTS: bool
AUTOCOMMIT: bool
CONN_MAX_AGE: int | None
CONN_HEALTH_CHECKS: bool
DISABLE_SERVER_SIDE_CURSORS: bool
ENGINE: str
HOST: str
NAME: str
OPTIONS: dict[str, Any]
PASSWORD: str
PORT: str | int
TEST: dict[str, Any]
TIME_ZONE: str
USER: str
PostprocessCallable = Callable[[DBConfig], None]
OptionType = int | str | bool
class ParseError(ValueError):
def __str__(self) -> str:
return (
"This string is not a valid url, possibly because some of its parts"
" is not properly urllib.parse.quote()'ed."
)
class UnknownSchemeError(ValueError):
def __init__(self, scheme: str):
self.scheme = scheme
def __str__(self) -> str:
schemes = ", ".join(sorted(ENGINE_SCHEMES.keys()))
return (
f"Scheme '{self.scheme}://' is unknown."
" Did you forget to register custom backend?"
f" Following schemes have registered backends: {schemes}."
)
def default_postprocess(parsed_config: DBConfig) -> None:
pass
class Engine:
def __init__(
self,
backend: str,
postprocess: PostprocessCallable = default_postprocess,
):
self.backend = backend
self.postprocess = postprocess
def register(
scheme: str, backend: str
) -> Callable[[PostprocessCallable], PostprocessCallable]:
engine = Engine(backend)
if scheme not in ENGINE_SCHEMES:
urlparse.uses_netloc.append(scheme)
ENGINE_SCHEMES[scheme] = engine
def inner(func: PostprocessCallable) -> PostprocessCallable:
engine.postprocess = func
return func
return inner
register("spatialite", "django.contrib.gis.db.backends.spatialite")
register("mysql-connector", "mysql.connector.django")
register("mysqlgis", "django.contrib.gis.db.backends.mysql")
register("oraclegis", "django.contrib.gis.db.backends.oracle")
register("cockroach", "django_cockroachdb")
@register("sqlite", "django.db.backends.sqlite3")
def default_to_in_memory_db(parsed_config: DBConfig) -> None:
# mimic sqlalchemy behaviour
if not parsed_config.get("NAME"):
parsed_config["NAME"] = ":memory:"
@register("oracle", "django.db.backends.oracle")
@register("mssqlms", "mssql")
@register("mssql", "sql_server.pyodbc")
def stringify_port(parsed_config: DBConfig) -> None:
parsed_config["PORT"] = str(parsed_config.get("PORT", ""))
@register("mysql", "django.db.backends.mysql")
@register("mysql2", "django.db.backends.mysql")
def apply_ssl_ca(parsed_config: DBConfig) -> None:
options = parsed_config.get("OPTIONS", {})
ca = options.pop("ssl-ca", None)
if ca:
options["ssl"] = {"ca": ca}
@register("postgres", "django.db.backends.postgresql")
@register("postgresql", "django.db.backends.postgresql")
@register("pgsql", "django.db.backends.postgresql")
@register("postgis", "django.contrib.gis.db.backends.postgis")
@register("redshift", "django_redshift_backend")
@register("timescale", "timescale.db.backends.postgresql")
@register("timescalegis", "timescale.db.backends.postgis")
def apply_current_schema(parsed_config: DBConfig) -> None:
options = parsed_config.get("OPTIONS", {})
schema = options.pop("currentSchema", None)
if schema:
options["options"] = f"-c search_path={schema}"
def config(
env: str = DEFAULT_ENV,
default: str | None = None,
engine: str | None = None,
conn_max_age: int | None = 0,
conn_health_checks: bool = False,
disable_server_side_cursors: bool = False,
ssl_require: bool = False,
test_options: dict[str, Any] | None = None,
) -> DBConfig:
"""Returns configured DATABASE dictionary from DATABASE_URL."""
s = os.environ.get(env, default)
if s is None:
logging.warning(
"No %s environment variable set, and so no databases setup", env
)
if s:
return parse(
s,
engine,
conn_max_age,
conn_health_checks,
disable_server_side_cursors,
ssl_require,
test_options,
)
return {}
def parse(
url: str,
engine: str | None = None,
conn_max_age: int | None = 0,
conn_health_checks: bool = False,
disable_server_side_cursors: bool = False,
ssl_require: bool = False,
test_options: dict[str, Any] | None = None,
) -> DBConfig:
"""Parses a database URL and returns configured DATABASE dictionary."""
settings = _convert_to_settings(
engine,
conn_max_age,
conn_health_checks,
disable_server_side_cursors,
ssl_require,
test_options,
)
if url == "sqlite://:memory:":
# this is a special case, because if we pass this URL into
# urlparse, urlparse will choke trying to interpret "memory"
# as a port number
return {"ENGINE": ENGINE_SCHEMES["sqlite"].backend, "NAME": ":memory:"}
# note: no other settings are required for sqlite
try:
split_result = urlparse.urlsplit(url)
engine_obj = ENGINE_SCHEMES.get(split_result.scheme)
if engine_obj is None:
raise UnknownSchemeError(split_result.scheme)
path = split_result.path[1:]
query = urlparse.parse_qs(split_result.query)
options = {k: _parse_option_values(v) for k, v in query.items()}
parsed_config: DBConfig = {
"ENGINE": engine_obj.backend,
"USER": urlparse.unquote(split_result.username or ""),
"PASSWORD": urlparse.unquote(split_result.password or ""),
"HOST": urlparse.unquote(split_result.hostname or ""),
"PORT": split_result.port or "",
"NAME": urlparse.unquote(path),
"OPTIONS": options,
}
except UnknownSchemeError:
raise
except ValueError:
raise ParseError() from None
# Guarantee that config has options, possibly empty, when postprocess() is called
assert isinstance(parsed_config["OPTIONS"], dict)
engine_obj.postprocess(parsed_config)
# Update the final config with any settings passed in explicitly.
parsed_config["OPTIONS"].update(settings.pop("OPTIONS", {}))
parsed_config.update(settings)
if not parsed_config["OPTIONS"]:
parsed_config.pop("OPTIONS")
return parsed_config
def _parse_option_values(values: list[str]) -> OptionType | list[OptionType]:
parsed_values = [_parse_value(v) for v in values]
return parsed_values[0] if len(parsed_values) == 1 else parsed_values
def _parse_value(value: str) -> OptionType:
if value.isdigit():
return int(value)
if value.lower() in ("true", "false"):
return value.lower() == "true"
return value
def _convert_to_settings(
engine: str | None,
conn_max_age: int | None,
conn_health_checks: bool,
disable_server_side_cursors: bool,
ssl_require: bool,
test_options: dict[str, Any] | None,
) -> DBConfig:
settings: DBConfig = {
"CONN_MAX_AGE": conn_max_age,
"CONN_HEALTH_CHECKS": conn_health_checks,
"DISABLE_SERVER_SIDE_CURSORS": disable_server_side_cursors,
}
if engine:
settings["ENGINE"] = engine
if ssl_require:
settings["OPTIONS"] = {}
settings["OPTIONS"]["sslmode"] = "require"
if test_options:
settings["TEST"] = test_options
return settings

View file

@ -1,8 +1,79 @@
[tool.black]
skip-string-normalization = 1
[project]
name = "dj-database-url"
version = "0.0.0"
description = "Use Database URLs in your Django Application."
authors = [
{ name = "Jazzband community" }
]
readme = "README.rst"
requires-python = ">=3.10"
license = "BSD-3-Clause"
license-files = ["LICENSE"]
dependencies = [
"django>=4.2",
]
classifiers = [
"Environment :: Web Environment",
"Framework :: Django",
"Framework :: Django :: 4.2",
"Framework :: Django :: 5.2",
"Framework :: Django :: 6",
"Intended Audience :: Developers",
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
"Topic :: Software Development :: Libraries :: Python Modules",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
]
[project.urls]
Homepage = "https://jazzband.co/projects/dj-database-url"
Changelog = "https://github.com/jazzband/dj-database-url/blob/master/CHANGELOG.md"
Funding = "https://psfmember.org/civicrm/contribute/transact/?reset=1&id=34"
Bug = "https://github.com/jazzband/dj-database-url/issues"
[build-system]
requires = ["uv_build>=0.9.17,<0.10.0"]
build-backend = "uv_build"
[tool.uv.build-backend]
module-name = "dj_database_url"
module-root = ""
source-include = ["dj_database_url/py.typed", "tests/**"]
[tool.ruff]
line-length = 88
[tool.ruff.lint]
extend-select = ["B", "I"]
[tool.ruff.format]
quote-style = "preserve"
[tool.mypy]
show_error_codes=true
disallow_untyped_defs=true
disallow_untyped_calls=true
warn_redundant_casts=true
[tool.pyright]
typeCheckingMode = "strict"
[dependency-groups]
dev = [
"coverage>=7.13.0",
"mypy>=1.19.1",
"pyright>=1.1.407",
"pytest>=9.0.2",
"ruff>=0.14.10",
"setuptools>=80.9.0",
"twine>=6.2.0",
"wheel>=0.45.1",
]

View file

@ -1,2 +0,0 @@
coverage
mypy

View file

@ -1,47 +0,0 @@
from pathlib import Path
from setuptools import setup
readme = Path("README.rst").read_text()
setup(
name="dj-database-url",
version="1.3.0",
url="https://github.com/jazzband/dj-database-url",
license="BSD",
author="Original Author: Kenneth Reitz, Maintained by: JazzBand Community",
description="Use Database URLs in your Django Application.",
long_description=readme,
long_description_content_type="text/x-rst",
py_modules=["dj_database_url"],
install_requires=["Django>=3.2", "typing_extensions >= 3.10.0.0"],
zip_safe=False,
include_package_data=True,
platforms="any",
project_links={
"GitHub": "https://github.com/jazzband/dj-database-url/",
"Release log": (
"https://github.com/jazzband/dj-database-url/blob/master/CHANGELOG.md"
),
},
classifiers=[
"Environment :: Web Environment",
"Framework :: Django",
"Framework :: Django :: 3.2",
"Framework :: Django :: 4.0",
"Framework :: Django :: 4.1",
"Intended Audience :: Developers",
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
"Topic :: Software Development :: Libraries :: Python Modules",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"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",
],
)

0
tests/__init__.py Normal file
View file

View file

@ -1,6 +1,10 @@
# pyright: reportTypedDictNotRequiredAccess=false
import os
import re
import unittest
from unittest import mock
from urllib.parse import uses_netloc
import dj_database_url
@ -8,9 +12,10 @@ POSTGIS_URL = "postgis://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compu
class DatabaseTestSuite(unittest.TestCase):
def test_postgres_parsing(self):
url = "postgres://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722r2kuvn"
url = dj_database_url.parse(url)
def test_postgres_parsing(self) -> None:
url = dj_database_url.parse(
"postgres://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722r2kuvn"
)
assert url["ENGINE"] == "django.db.backends.postgresql"
assert url["NAME"] == "d8r82722r2kuvn"
@ -19,9 +24,10 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["PASSWORD"] == "wegauwhgeuioweg"
assert url["PORT"] == 5431
def test_postgres_unix_socket_parsing(self):
url = "postgres://%2Fvar%2Frun%2Fpostgresql/d8r82722r2kuvn"
url = dj_database_url.parse(url)
def test_postgres_unix_socket_parsing(self) -> None:
url = dj_database_url.parse(
"postgres://%2Fvar%2Frun%2Fpostgresql/d8r82722r2kuvn"
)
assert url["ENGINE"] == "django.db.backends.postgresql"
assert url["NAME"] == "d8r82722r2kuvn"
@ -30,8 +36,9 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["PASSWORD"] == ""
assert url["PORT"] == ""
url = "postgres://%2FUsers%2Fpostgres%2FRuN/d8r82722r2kuvn"
url = dj_database_url.parse(url)
url = dj_database_url.parse(
"postgres://%2FUsers%2Fpostgres%2FRuN/d8r82722r2kuvn"
)
assert url["ENGINE"] == "django.db.backends.postgresql"
assert url["HOST"] == "/Users/postgres/RuN"
@ -39,9 +46,10 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["PASSWORD"] == ""
assert url["PORT"] == ""
def test_postgres_google_cloud_parsing(self):
url = "postgres://uf07k1i6d8ia0v:wegauwhgeuioweg@%2Fcloudsql%2Fproject_id%3Aregion%3Ainstance_id/d8r82722r2kuvn"
url = dj_database_url.parse(url)
def test_postgres_google_cloud_parsing(self) -> None:
url = dj_database_url.parse(
"postgres://uf07k1i6d8ia0v:wegauwhgeuioweg@%2Fcloudsql%2Fproject_id%3Aregion%3Ainstance_id/d8r82722r2kuvn"
)
assert url["ENGINE"] == "django.db.backends.postgresql"
assert url["NAME"] == "d8r82722r2kuvn"
@ -50,9 +58,10 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["PASSWORD"] == "wegauwhgeuioweg"
assert url["PORT"] == ""
def test_ipv6_parsing(self):
url = "postgres://ieRaekei9wilaim7:wegauwhgeuioweg@[2001:db8:1234::1234:5678:90af]:5431/d8r82722r2kuvn"
url = dj_database_url.parse(url)
def test_ipv6_parsing(self) -> None:
url = dj_database_url.parse(
"postgres://ieRaekei9wilaim7:wegauwhgeuioweg@[2001:db8:1234::1234:5678:90af]:5431/d8r82722r2kuvn"
)
assert url["ENGINE"] == "django.db.backends.postgresql"
assert url["NAME"] == "d8r82722r2kuvn"
@ -61,9 +70,10 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["PASSWORD"] == "wegauwhgeuioweg"
assert url["PORT"] == 5431
def test_postgres_search_path_parsing(self):
url = "postgres://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722r2kuvn?currentSchema=otherschema"
url = dj_database_url.parse(url)
def test_postgres_search_path_parsing(self) -> None:
url = dj_database_url.parse(
"postgres://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722r2kuvn?currentSchema=otherschema"
)
assert url["ENGINE"] == "django.db.backends.postgresql"
assert url["NAME"] == "d8r82722r2kuvn"
assert url["HOST"] == "ec2-107-21-253-135.compute-1.amazonaws.com"
@ -73,9 +83,10 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["OPTIONS"]["options"] == "-c search_path=otherschema"
assert "currentSchema" not in url["OPTIONS"]
def test_postgres_parsing_with_special_characters(self):
url = "postgres://%23user:%23password@ec2-107-21-253-135.compute-1.amazonaws.com:5431/%23database"
url = dj_database_url.parse(url)
def test_postgres_parsing_with_special_characters(self) -> None:
url = dj_database_url.parse(
"postgres://%23user:%23password@ec2-107-21-253-135.compute-1.amazonaws.com:5431/%23database"
)
assert url["ENGINE"] == "django.db.backends.postgresql"
assert url["NAME"] == "#database"
@ -84,9 +95,26 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["PASSWORD"] == "#password"
assert url["PORT"] == 5431
def test_postgis_parsing(self):
url = "postgis://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722r2kuvn"
url = dj_database_url.parse(url)
def test_postgres_parsing_with_int_bool_str_query_string(self) -> None:
url = dj_database_url.parse(
"postgres://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722r2kuvn?server_side_binding=true&timeout=20&service=my_service&passfile=.my_pgpass"
)
assert url["ENGINE"] == "django.db.backends.postgresql"
assert url["NAME"] == "d8r82722r2kuvn"
assert url["HOST"] == "ec2-107-21-253-135.compute-1.amazonaws.com"
assert url["USER"] == "uf07k1i6d8ia0v"
assert url["PASSWORD"] == "wegauwhgeuioweg"
assert url["PORT"] == 5431
assert url["OPTIONS"]["server_side_binding"] is True
assert url["OPTIONS"]["timeout"] == 20
assert url["OPTIONS"]["service"] == "my_service"
assert url["OPTIONS"]["passfile"] == ".my_pgpass"
def test_postgis_parsing(self) -> None:
url = dj_database_url.parse(
"postgis://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722r2kuvn"
)
assert url["ENGINE"] == "django.contrib.gis.db.backends.postgis"
assert url["NAME"] == "d8r82722r2kuvn"
@ -95,9 +123,10 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["PASSWORD"] == "wegauwhgeuioweg"
assert url["PORT"] == 5431
def test_postgis_search_path_parsing(self):
url = "postgis://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722r2kuvn?currentSchema=otherschema"
url = dj_database_url.parse(url)
def test_postgis_search_path_parsing(self) -> None:
url = dj_database_url.parse(
"postgis://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722r2kuvn?currentSchema=otherschema"
)
assert url["ENGINE"] == "django.contrib.gis.db.backends.postgis"
assert url["NAME"] == "d8r82722r2kuvn"
assert url["HOST"] == "ec2-107-21-253-135.compute-1.amazonaws.com"
@ -107,9 +136,10 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["OPTIONS"]["options"] == "-c search_path=otherschema"
assert "currentSchema" not in url["OPTIONS"]
def test_mysql_gis_parsing(self):
url = "mysqlgis://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722r2kuvn"
url = dj_database_url.parse(url)
def test_mysql_gis_parsing(self) -> None:
url = dj_database_url.parse(
"mysqlgis://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722r2kuvn"
)
assert url["ENGINE"] == "django.contrib.gis.db.backends.mysql"
assert url["NAME"] == "d8r82722r2kuvn"
@ -118,9 +148,10 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["PASSWORD"] == "wegauwhgeuioweg"
assert url["PORT"] == 5431
def test_mysql_connector_parsing(self):
url = "mysql-connector://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722r2kuvn"
url = dj_database_url.parse(url)
def test_mysql_connector_parsing(self) -> None:
url = dj_database_url.parse(
"mysql-connector://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722r2kuvn"
)
assert url["ENGINE"] == "mysql.connector.django"
assert url["NAME"] == "d8r82722r2kuvn"
@ -129,7 +160,7 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["PASSWORD"] == "wegauwhgeuioweg"
assert url["PORT"] == 5431
def test_config_test_options(self):
def test_config_test_options(self) -> None:
with mock.patch.dict(
os.environ,
{
@ -143,9 +174,10 @@ class DatabaseTestSuite(unittest.TestCase):
assert url['TEST']['NAME'] == 'mytestdatabase'
def test_cleardb_parsing(self):
url = "mysql://bea6eb025ca0d8:69772142@us-cdbr-east.cleardb.com/heroku_97681db3eff7580?reconnect=true"
url = dj_database_url.parse(url)
def test_cleardb_parsing(self) -> None:
url = dj_database_url.parse(
"mysql://bea6eb025ca0d8:69772142@us-cdbr-east.cleardb.com/heroku_97681db3eff7580?reconnect=true"
)
assert url["ENGINE"] == "django.db.backends.mysql"
assert url["NAME"] == "heroku_97681db3eff7580"
@ -154,7 +186,7 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["PASSWORD"] == "69772142"
assert url["PORT"] == ""
def test_database_url(self):
def test_database_url(self) -> None:
with mock.patch.dict(os.environ, clear=True):
a = dj_database_url.config()
assert not a
@ -174,28 +206,46 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["PASSWORD"] == "wegauwhgeuioweg"
assert url["PORT"] == 5431
def test_empty_sqlite_url(self):
url = "sqlite://"
url = dj_database_url.parse(url)
def test_empty_sqlite_url(self) -> None:
url = dj_database_url.parse("sqlite://")
assert url["ENGINE"] == "django.db.backends.sqlite3"
assert url["NAME"] == ":memory:"
def test_memory_sqlite_url(self):
url = "sqlite://:memory:"
url = dj_database_url.parse(url)
def test_memory_sqlite_url(self) -> None:
url = dj_database_url.parse("sqlite://:memory:")
assert url["ENGINE"] == "django.db.backends.sqlite3"
assert url["NAME"] == ":memory:"
def test_parse_engine_setting(self):
def test_sqlite_relative_url(self) -> None:
url = "sqlite:///db.sqlite3"
config = dj_database_url.parse(url)
assert config["ENGINE"] == "django.db.backends.sqlite3"
assert config["NAME"] == "db.sqlite3"
def test_sqlite_absolute_url(self) -> None:
# 4 slashes are needed:
# two are part of scheme
# one separates host:port from path
# and the fourth goes to "NAME" value
url = "sqlite:////db.sqlite3"
config = dj_database_url.parse(url)
assert config["ENGINE"] == "django.db.backends.sqlite3"
assert config["NAME"] == "/db.sqlite3"
def test_parse_engine_setting(self) -> None:
engine = "django_mysqlpool.backends.mysqlpool"
url = "mysql://bea6eb025ca0d8:69772142@us-cdbr-east.cleardb.com/heroku_97681db3eff7580?reconnect=true"
url = dj_database_url.parse(url, engine)
url = dj_database_url.parse(
"mysql://bea6eb025ca0d8:69772142@us-cdbr-east.cleardb.com/heroku_97681db3eff7580?reconnect=true",
engine,
)
assert url["ENGINE"] == engine
def test_config_engine_setting(self):
def test_config_engine_setting(self) -> None:
engine = "django_mysqlpool.backends.mysqlpool"
with mock.patch.dict(
os.environ,
@ -207,15 +257,17 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["ENGINE"] == engine
def test_parse_conn_max_age_setting(self):
def test_parse_conn_max_age_setting(self) -> None:
conn_max_age = 600
url = "mysql://bea6eb025ca0d8:69772142@us-cdbr-east.cleardb.com/heroku_97681db3eff7580?reconnect=true"
url = dj_database_url.parse(url, conn_max_age=conn_max_age)
url = dj_database_url.parse(
"mysql://bea6eb025ca0d8:69772142@us-cdbr-east.cleardb.com/heroku_97681db3eff7580?reconnect=true",
conn_max_age=conn_max_age,
)
assert url["CONN_MAX_AGE"] == conn_max_age
def test_config_conn_max_age_setting(self):
conn_max_age = 600
def test_config_conn_max_age_setting_none(self) -> None:
conn_max_age = None
with mock.patch.dict(
os.environ,
{
@ -226,7 +278,7 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["CONN_MAX_AGE"] == conn_max_age
def test_database_url_with_options(self):
def test_database_url_with_options(self) -> None:
# Test full options
with mock.patch.dict(
os.environ,
@ -257,7 +309,7 @@ class DatabaseTestSuite(unittest.TestCase):
url = dj_database_url.config()
assert "OPTIONS" not in url
def test_mysql_database_url_with_sslca_options(self):
def test_mysql_database_url_with_sslca_options(self) -> None:
with mock.patch.dict(
os.environ,
{
@ -284,9 +336,8 @@ class DatabaseTestSuite(unittest.TestCase):
url = dj_database_url.config()
assert "OPTIONS" not in url
def test_oracle_parsing(self):
url = "oracle://scott:tiger@oraclehost:1521/hr"
url = dj_database_url.parse(url)
def test_oracle_parsing(self) -> None:
url = dj_database_url.parse("oracle://scott:tiger@oraclehost:1521/hr")
assert url["ENGINE"] == "django.db.backends.oracle"
assert url["NAME"] == "hr"
@ -295,9 +346,8 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["PASSWORD"] == "tiger"
assert url["PORT"] == "1521"
def test_oracle_gis_parsing(self):
url = "oraclegis://scott:tiger@oraclehost:1521/hr"
url = dj_database_url.parse(url)
def test_oracle_gis_parsing(self) -> None:
url = dj_database_url.parse("oraclegis://scott:tiger@oraclehost:1521/hr")
assert url["ENGINE"] == "django.contrib.gis.db.backends.oracle"
assert url["NAME"] == "hr"
@ -306,14 +356,13 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["PASSWORD"] == "tiger"
assert url["PORT"] == 1521
def test_oracle_dsn_parsing(self):
url = (
def test_oracle_dsn_parsing(self) -> None:
url = dj_database_url.parse(
"oracle://scott:tiger@/"
"(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)"
"(HOST=oraclehost)(PORT=1521)))"
"(CONNECT_DATA=(SID=hr)))"
)
url = dj_database_url.parse(url)
assert url["ENGINE"] == "django.db.backends.oracle"
assert url["USER"] == "scott"
@ -329,9 +378,8 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["NAME"] == dsn
def test_oracle_tns_parsing(self):
url = "oracle://scott:tiger@/tnsname"
url = dj_database_url.parse(url)
def test_oracle_tns_parsing(self) -> None:
url = dj_database_url.parse("oracle://scott:tiger@/tnsname")
assert url["ENGINE"] == "django.db.backends.oracle"
assert url["USER"] == "scott"
@ -340,9 +388,10 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["HOST"] == ""
assert url["PORT"] == ""
def test_redshift_parsing(self):
url = "redshift://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5439/d8r82722r2kuvn?currentSchema=otherschema"
url = dj_database_url.parse(url)
def test_redshift_parsing(self) -> None:
url = dj_database_url.parse(
"redshift://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5439/d8r82722r2kuvn?currentSchema=otherschema"
)
assert url["ENGINE"] == "django_redshift_backend"
assert url["NAME"] == "d8r82722r2kuvn"
@ -353,9 +402,10 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["OPTIONS"]["options"] == "-c search_path=otherschema"
assert "currentSchema" not in url["OPTIONS"]
def test_mssql_parsing(self):
url = "mssql://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com/d8r82722r2kuvn?driver=ODBC Driver 13 for SQL Server"
url = dj_database_url.parse(url)
def test_mssql_parsing(self) -> None:
url = dj_database_url.parse(
"mssql://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com/d8r82722r2kuvn?driver=ODBC Driver 13 for SQL Server"
)
assert url["ENGINE"] == "sql_server.pyodbc"
assert url["NAME"] == "d8r82722r2kuvn"
@ -366,9 +416,10 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["OPTIONS"]["driver"] == "ODBC Driver 13 for SQL Server"
assert "currentSchema" not in url["OPTIONS"]
def test_mssql_instance_port_parsing(self):
url = "mssql://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com\\insnsnss:12345/d8r82722r2kuvn?driver=ODBC Driver 13 for SQL Server"
url = dj_database_url.parse(url)
def test_mssql_instance_port_parsing(self) -> None:
url = dj_database_url.parse(
"mssql://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com\\insnsnss:12345/d8r82722r2kuvn?driver=ODBC Driver 13 for SQL Server"
)
assert url["ENGINE"] == "sql_server.pyodbc"
assert url["NAME"] == "d8r82722r2kuvn"
@ -379,9 +430,10 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["OPTIONS"]["driver"] == "ODBC Driver 13 for SQL Server"
assert "currentSchema" not in url["OPTIONS"]
def test_cockroach(self):
url = "cockroach://testuser:testpass@testhost:26257/cockroach?sslmode=verify-full&sslrootcert=/certs/ca.crt&sslcert=/certs/client.myprojectuser.crt&sslkey=/certs/client.myprojectuser.key"
url = dj_database_url.parse(url)
def test_cockroach(self) -> None:
url = dj_database_url.parse(
"cockroach://testuser:testpass@testhost:26257/cockroach?sslmode=verify-full&sslrootcert=/certs/ca.crt&sslcert=/certs/client.myprojectuser.crt&sslkey=/certs/client.myprojectuser.key"
)
assert url['ENGINE'] == 'django_cockroachdb'
assert url['NAME'] == 'cockroach'
assert url['HOST'] == 'testhost'
@ -393,9 +445,10 @@ class DatabaseTestSuite(unittest.TestCase):
assert url['OPTIONS']['sslcert'] == '/certs/client.myprojectuser.crt'
assert url['OPTIONS']['sslkey'] == '/certs/client.myprojectuser.key'
def test_mssqlms_parsing(self):
url = "mssqlms://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com/d8r82722r2kuvn?driver=ODBC Driver 13 for SQL Server"
url = dj_database_url.parse(url)
def test_mssqlms_parsing(self) -> None:
url = dj_database_url.parse(
"mssqlms://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com/d8r82722r2kuvn?driver=ODBC Driver 13 for SQL Server"
)
assert url["ENGINE"] == "mssql"
assert url["NAME"] == "d8r82722r2kuvn"
@ -406,9 +459,10 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["OPTIONS"]["driver"] == "ODBC Driver 13 for SQL Server"
assert "currentSchema" not in url["OPTIONS"]
def test_timescale_parsing(self):
url = "timescale://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722r2kuvn"
url = dj_database_url.parse(url)
def test_timescale_parsing(self) -> None:
url = dj_database_url.parse(
"timescale://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722r2kuvn"
)
assert url["ENGINE"] == "timescale.db.backends.postgresql"
assert url["NAME"] == "d8r82722r2kuvn"
@ -417,9 +471,10 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["PASSWORD"] == "wegauwhgeuioweg"
assert url["PORT"] == 5431
def test_timescale_unix_socket_parsing(self):
url = "timescale://%2Fvar%2Frun%2Fpostgresql/d8r82722r2kuvn"
url = dj_database_url.parse(url)
def test_timescale_unix_socket_parsing(self) -> None:
url = dj_database_url.parse(
"timescale://%2Fvar%2Frun%2Fpostgresql/d8r82722r2kuvn"
)
assert url["ENGINE"] == "timescale.db.backends.postgresql"
assert url["NAME"] == "d8r82722r2kuvn"
@ -428,8 +483,9 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["PASSWORD"] == ""
assert url["PORT"] == ""
url = "timescale://%2FUsers%2Fpostgres%2FRuN/d8r82722r2kuvn"
url = dj_database_url.parse(url)
url = dj_database_url.parse(
"timescale://%2FUsers%2Fpostgres%2FRuN/d8r82722r2kuvn"
)
assert url["ENGINE"] == "timescale.db.backends.postgresql"
assert url["HOST"] == "/Users/postgres/RuN"
@ -437,9 +493,10 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["PASSWORD"] == ""
assert url["PORT"] == ""
def test_timescale_ipv6_parsing(self):
url = "timescale://ieRaekei9wilaim7:wegauwhgeuioweg@[2001:db8:1234::1234:5678:90af]:5431/d8r82722r2kuvn"
url = dj_database_url.parse(url)
def test_timescale_ipv6_parsing(self) -> None:
url = dj_database_url.parse(
"timescale://ieRaekei9wilaim7:wegauwhgeuioweg@[2001:db8:1234::1234:5678:90af]:5431/d8r82722r2kuvn"
)
assert url["ENGINE"] == "timescale.db.backends.postgresql"
assert url["NAME"] == "d8r82722r2kuvn"
@ -448,9 +505,10 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["PASSWORD"] == "wegauwhgeuioweg"
assert url["PORT"] == 5431
def test_timescale_search_path_parsing(self):
url = "timescale://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722r2kuvn?currentSchema=otherschema"
url = dj_database_url.parse(url)
def test_timescale_search_path_parsing(self) -> None:
url = dj_database_url.parse(
"timescale://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722r2kuvn?currentSchema=otherschema"
)
assert url["ENGINE"] == "timescale.db.backends.postgresql"
assert url["NAME"] == "d8r82722r2kuvn"
assert url["HOST"] == "ec2-107-21-253-135.compute-1.amazonaws.com"
@ -460,9 +518,10 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["OPTIONS"]["options"] == "-c search_path=otherschema"
assert "currentSchema" not in url["OPTIONS"]
def test_timescale_parsing_with_special_characters(self):
url = "timescale://%23user:%23password@ec2-107-21-253-135.compute-1.amazonaws.com:5431/%23database"
url = dj_database_url.parse(url)
def test_timescale_parsing_with_special_characters(self) -> None:
url = dj_database_url.parse(
"timescale://%23user:%23password@ec2-107-21-253-135.compute-1.amazonaws.com:5431/%23database"
)
assert url["ENGINE"] == "timescale.db.backends.postgresql"
assert url["NAME"] == "#database"
@ -471,9 +530,10 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["PASSWORD"] == "#password"
assert url["PORT"] == 5431
def test_timescalegis_parsing(self):
url = "timescalegis://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722r2kuvn"
url = dj_database_url.parse(url)
def test_timescalegis_parsing(self) -> None:
url = dj_database_url.parse(
"timescalegis://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722r2kuvn"
)
assert url["ENGINE"] == "timescale.db.backends.postgis"
assert url["NAME"] == "d8r82722r2kuvn"
@ -482,9 +542,10 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["PASSWORD"] == "wegauwhgeuioweg"
assert url["PORT"] == 5431
def test_timescalegis_unix_socket_parsing(self):
url = "timescalegis://%2Fvar%2Frun%2Fpostgresql/d8r82722r2kuvn"
url = dj_database_url.parse(url)
def test_timescalegis_unix_socket_parsing(self) -> None:
url = dj_database_url.parse(
"timescalegis://%2Fvar%2Frun%2Fpostgresql/d8r82722r2kuvn"
)
assert url["ENGINE"] == "timescale.db.backends.postgis"
assert url["NAME"] == "d8r82722r2kuvn"
@ -493,8 +554,9 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["PASSWORD"] == ""
assert url["PORT"] == ""
url = "timescalegis://%2FUsers%2Fpostgres%2FRuN/d8r82722r2kuvn"
url = dj_database_url.parse(url)
url = dj_database_url.parse(
"timescalegis://%2FUsers%2Fpostgres%2FRuN/d8r82722r2kuvn"
)
assert url["ENGINE"] == "timescale.db.backends.postgis"
assert url["HOST"] == "/Users/postgres/RuN"
@ -502,9 +564,10 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["PASSWORD"] == ""
assert url["PORT"] == ""
def test_timescalegis_ipv6_parsing(self):
url = "timescalegis://ieRaekei9wilaim7:wegauwhgeuioweg@[2001:db8:1234::1234:5678:90af]:5431/d8r82722r2kuvn"
url = dj_database_url.parse(url)
def test_timescalegis_ipv6_parsing(self) -> None:
url = dj_database_url.parse(
"timescalegis://ieRaekei9wilaim7:wegauwhgeuioweg@[2001:db8:1234::1234:5678:90af]:5431/d8r82722r2kuvn"
)
assert url["ENGINE"] == "timescale.db.backends.postgis"
assert url["NAME"] == "d8r82722r2kuvn"
@ -513,9 +576,10 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["PASSWORD"] == "wegauwhgeuioweg"
assert url["PORT"] == 5431
def test_timescalegis_search_path_parsing(self):
url = "timescalegis://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722r2kuvn?currentSchema=otherschema"
url = dj_database_url.parse(url)
def test_timescalegis_search_path_parsing(self) -> None:
url = dj_database_url.parse(
"timescalegis://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722r2kuvn?currentSchema=otherschema"
)
assert url["ENGINE"] == "timescale.db.backends.postgis"
assert url["NAME"] == "d8r82722r2kuvn"
assert url["HOST"] == "ec2-107-21-253-135.compute-1.amazonaws.com"
@ -525,9 +589,10 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["OPTIONS"]["options"] == "-c search_path=otherschema"
assert "currentSchema" not in url["OPTIONS"]
def test_timescalegis_parsing_with_special_characters(self):
url = "timescalegis://%23user:%23password@ec2-107-21-253-135.compute-1.amazonaws.com:5431/%23database"
url = dj_database_url.parse(url)
def test_timescalegis_parsing_with_special_characters(self) -> None:
url = dj_database_url.parse(
"timescalegis://%23user:%23password@ec2-107-21-253-135.compute-1.amazonaws.com:5431/%23database"
)
assert url["ENGINE"] == "timescale.db.backends.postgis"
assert url["NAME"] == "#database"
@ -536,7 +601,7 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["PASSWORD"] == "#password"
assert url["PORT"] == 5431
def test_persistent_connection_variables(self):
def test_persistent_connection_variables(self) -> None:
url = dj_database_url.parse(
"sqlite://myfile.db", conn_max_age=600, conn_health_checks=True
)
@ -544,7 +609,7 @@ class DatabaseTestSuite(unittest.TestCase):
assert url["CONN_MAX_AGE"] == 600
assert url["CONN_HEALTH_CHECKS"] is True
def test_sqlite_memory_persistent_connection_variables(self):
def test_sqlite_memory_persistent_connection_variables(self) -> None:
# memory sqlite ignores connection.close(), so persistent connection
# variables arent required
url = dj_database_url.parse(
@ -558,13 +623,13 @@ class DatabaseTestSuite(unittest.TestCase):
os.environ,
{"DATABASE_URL": "postgres://user:password@instance.amazonaws.com:5431/d8r8?"},
)
def test_persistent_connection_variables_config(self):
def test_persistent_connection_variables_config(self) -> None:
url = dj_database_url.config(conn_max_age=600, conn_health_checks=True)
assert url["CONN_MAX_AGE"] == 600
assert url["CONN_HEALTH_CHECKS"] is True
def test_no_env_variable(self):
def test_no_env_variable(self) -> None:
with self.assertLogs() as cm:
with mock.patch.dict(os.environ, clear=True):
url = dj_database_url.config()
@ -573,18 +638,61 @@ class DatabaseTestSuite(unittest.TestCase):
'WARNING:root:No DATABASE_URL environment variable set, and so no databases setup'
], cm.output
def test_bad_url_parsing(self):
with self.assertRaisesRegex(ValueError, "No support for 'foo'. We support: "):
dj_database_url.parse("foo://bar")
def test_credentials_unquoted__raise_value_error(self) -> None:
expected_message = (
"This string is not a valid url, possibly because some of its parts "
r"is not properly urllib.parse.quote()'ed."
)
with self.assertRaisesRegex(ValueError, re.escape(expected_message)):
dj_database_url.parse("postgres://user:passw#ord!@localhost/foobar")
def test_credentials_quoted__ok(self) -> None:
url = "postgres://user%40domain:p%23ssword!@localhost/foobar"
config = dj_database_url.parse(url)
assert config["USER"] == "user@domain"
assert config["PASSWORD"] == "p#ssword!"
def test_unknown_scheme__raise_value_error(self) -> None:
expected_message = (
"Scheme 'unknown-scheme://' is unknown. "
"Did you forget to register custom backend? Following schemes have registered backends:"
)
with self.assertRaisesRegex(ValueError, re.escape(expected_message)):
dj_database_url.parse("unknown-scheme://user:password@localhost/foobar")
def test_register_multiple_times__no_duplicates_in_uses_netloc(self) -> None:
# make sure that when register() function is misused,
# it won't pollute urllib.parse.uses_netloc list with duplicates.
# Otherwise, it might cause performance issue if some code assumes that
# that list is short and performs linear search on it.
dj_database_url.register("django.contrib.db.backends.bag_end", "bag-end")
dj_database_url.register("django.contrib.db.backends.bag_end", "bag-end")
assert len(uses_netloc) == len(set(uses_netloc))
@mock.patch.dict(
os.environ,
{"DATABASE_URL": "postgres://user:password@instance.amazonaws.com:5431/d8r8?"},
)
def test_ssl_require(self):
def test_ssl_require(self) -> None:
url = dj_database_url.config(ssl_require=True)
assert url["OPTIONS"] == {'sslmode': 'require'}
def test_options_int_values(self) -> None:
"""Ensure that options with integer values are parsed correctly."""
url = dj_database_url.parse(
"mysql://user:pw@127.0.0.1:15036/db?connect_timeout=3"
)
assert url["OPTIONS"] == {'connect_timeout': 3}
@mock.patch.dict(
os.environ,
{"DATABASE_URL": "postgres://user:password@instance.amazonaws.com:5431/d8r8?"},
)
def test_server_side_cursors__config(self) -> None:
url = dj_database_url.config(disable_server_side_cursors=True)
assert url["DISABLE_SERVER_SIDE_CURSORS"] is True
if __name__ == "__main__":
unittest.main()

1033
uv.lock Normal file

File diff suppressed because it is too large Load diff