Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-dj-database-url for
openSUSE:Factory checked in at 2025-07-14 10:52:09
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-dj-database-url (Old)
and /work/SRC/openSUSE:Factory/.python-dj-database-url.new.7373 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-dj-database-url"
Mon Jul 14 10:52:09 2025 rev:12 rq:1292511 version:3.0.1
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-dj-database-url/python-dj-database-url.changes
2024-10-24 15:43:26.321824967 +0200
+++
/work/SRC/openSUSE:Factory/.python-dj-database-url.new.7373/python-dj-database-url.changes
2025-07-14 10:57:58.336226937 +0200
@@ -1,0 +2,9 @@
+Sun Jul 13 13:38:08 UTC 2025 - Dirk Müller <[email protected]>
+
+- update to 3.0.1:
+ * Re-drop dependency on `typing_extensions`
+- update to 3.0.0:
+ * Drop dependency on `typing_extensions`
+ * Fix type errors
+
+-------------------------------------------------------------------
Old:
----
dj_database_url-2.3.0.tar.gz
New:
----
dj_database_url-3.0.1.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-dj-database-url.spec ++++++
--- /var/tmp/diff_new_pack.eu1s4K/_old 2025-07-14 10:57:58.956252641 +0200
+++ /var/tmp/diff_new_pack.eu1s4K/_new 2025-07-14 10:57:58.960252807 +0200
@@ -1,7 +1,7 @@
#
# spec file for package python-dj-database-url
#
-# Copyright (c) 2024 SUSE LLC
+# Copyright (c) 2025 SUSE LLC
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -18,7 +18,7 @@
%{?sle15_python_module_pythons}
Name: python-dj-database-url
-Version: 2.3.0
+Version: 3.0.1
Release: 0
Summary: Utility to use database URLs in Django applications
License: BSD-3-Clause
@@ -34,7 +34,6 @@
BuildRequires: fdupes
BuildRequires: python-rpm-macros
Requires: python-Django > 4.2
-Requires: python-typing_extensions >= 3.10
BuildArch: noarch
%python_subpackages
++++++ dj_database_url-2.3.0.tar.gz -> dj_database_url-3.0.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/dj_database_url-2.3.0/PKG-INFO
new/dj_database_url-3.0.1/PKG-INFO
--- old/dj_database_url-2.3.0/PKG-INFO 2024-10-23 12:01:44.752528700 +0200
+++ new/dj_database_url-3.0.1/PKG-INFO 2025-07-02 11:29:20.456748700 +0200
@@ -1,6 +1,6 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
Name: dj-database-url
-Version: 2.3.0
+Version: 3.0.1
Summary: Use Database URLs in your Django Application.
Home-page: https://github.com/jazzband/dj-database-url
Author: Original Author: Kenneth Reitz, Maintained by: JazzBand Community
@@ -13,6 +13,7 @@
Classifier: Framework :: Django :: 4.2
Classifier: Framework :: Django :: 5.0
Classifier: Framework :: Django :: 5.1
+Classifier: Framework :: Django :: 5.2
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
@@ -21,15 +22,25 @@
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
Description-Content-Type: text/x-rst
License-File: LICENSE
Requires-Dist: Django>=4.2
-Requires-Dist: typing_extensions>=3.10.0.0
+Dynamic: author
+Dynamic: classifier
+Dynamic: description
+Dynamic: description-content-type
+Dynamic: home-page
+Dynamic: license
+Dynamic: license-file
+Dynamic: platform
+Dynamic: project-url
+Dynamic: requires-dist
+Dynamic: summary
DJ-Database-URL
~~~~~~~~~~~~~~~
@@ -55,12 +66,6 @@
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
------------
@@ -181,6 +186,63 @@
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
----------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/dj_database_url-2.3.0/README.rst
new/dj_database_url-3.0.1/README.rst
--- old/dj_database_url-2.3.0/README.rst 2024-10-23 12:01:36.000000000
+0200
+++ new/dj_database_url-3.0.1/README.rst 2025-07-02 11:29:13.000000000
+0200
@@ -22,12 +22,6 @@
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 @@
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
----------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/dj_database_url-2.3.0/dj_database_url/__init__.py
new/dj_database_url-3.0.1/dj_database_url/__init__.py
--- old/dj_database_url-2.3.0/dj_database_url/__init__.py 2024-10-23
12:01:36.000000000 +0200
+++ new/dj_database_url-3.0.1/dj_database_url/__init__.py 2025-07-02
11:29:13.000000000 +0200
@@ -1,50 +1,13 @@
import logging
import os
import urllib.parse as urlparse
-from typing import Any, Dict, Optional, Union
-
-from typing_extensions import TypedDict
+from typing import Any, Callable, Dict, List, Optional, TypedDict, Union
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",
-}
-
-SCHEMES_WITH_SEARCH_PATH = [
- "postgres",
- "postgresql",
- "pgsql",
- "postgis",
- "redshift",
- "timescale",
- "timescalegis",
-]
-
-# Register database schemes in URLs.
-for key in SCHEMES.keys():
- urlparse.uses_netloc.append(key)
-del key # pyright: ignore[reportPossiblyUnboundVariable]
+ENGINE_SCHEMES: Dict[str, "Engine"] = {}
-# From https://docs.djangoproject.com/en/4.0/ref/settings/#databases
+# From https://docs.djangoproject.com/en/stable/ref/settings/#databases
class DBConfig(TypedDict, total=False):
ATOMIC_REQUESTS: bool
AUTOCOMMIT: bool
@@ -62,11 +25,109 @@
USER: str
+PostprocessCallable = Callable[[DBConfig], None]
+OptionType = Union[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) -> None:
+ 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,
+ ) -> None:
+ 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: Optional[str] = None,
engine: Optional[str] = None,
- conn_max_age: Optional[int] = 0,
+ conn_max_age: int = 0,
conn_health_checks: bool = False,
disable_server_side_cursors: bool = False,
ssl_require: bool = False,
@@ -77,7 +138,7 @@
if s is None:
logging.warning(
- "No %s environment variable set, and so no databases setup" % env
+ "No %s environment variable set, and so no databases setup", env
)
if s:
@@ -97,107 +158,95 @@
def parse(
url: str,
engine: Optional[str] = None,
- conn_max_age: Optional[int] = 0,
+ conn_max_age: int = 0,
conn_health_checks: bool = False,
disable_server_side_cursors: bool = False,
ssl_require: bool = False,
test_options: Optional[Dict[str, Any]] = None,
) -> DBConfig:
- """Parses a database URL."""
+ """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": SCHEMES["sqlite"], "NAME": ":memory:"}
+ return {"ENGINE": ENGINE_SCHEMES["sqlite"].backend, "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,
- "DISABLE_SERVER_SIDE_CURSORS": disable_server_side_cursors,
- "ENGINE": engine,
+ 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,
}
- )
- if test_options:
- parsed_config.update(
- {
- 'TEST': test_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)
- # 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
-
- value = values[-1]
- if value.isdigit():
- options[key] = int(value)
- elif value.lower() in ("true", "false"):
- options[key] = value.lower() == "true"
- else:
- options[key] = value
-
- if ssl_require:
- options["sslmode"] = "require"
-
- # Support for Postgres Schema URLs
- if "currentSchema" in options and spliturl.scheme in
SCHEMES_WITH_SEARCH_PATH:
- options["options"] = "-c
search_path={0}".format(options.pop("currentSchema"))
+ if not parsed_config["OPTIONS"]:
+ parsed_config.pop("OPTIONS")
+ return parsed_config
- if options:
- parsed_config["OPTIONS"] = options
- return parsed_config
+def _parse_option_values(values: List[str]) -> Union[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: Optional[str],
+ conn_max_age: int,
+ conn_health_checks: bool,
+ disable_server_side_cursors: bool,
+ ssl_require: bool,
+ test_options: Optional[dict[str, Any]],
+) -> 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
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/dj_database_url-2.3.0/dj_database_url.egg-info/PKG-INFO
new/dj_database_url-3.0.1/dj_database_url.egg-info/PKG-INFO
--- old/dj_database_url-2.3.0/dj_database_url.egg-info/PKG-INFO 2024-10-23
12:01:44.000000000 +0200
+++ new/dj_database_url-3.0.1/dj_database_url.egg-info/PKG-INFO 2025-07-02
11:29:20.000000000 +0200
@@ -1,6 +1,6 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
Name: dj-database-url
-Version: 2.3.0
+Version: 3.0.1
Summary: Use Database URLs in your Django Application.
Home-page: https://github.com/jazzband/dj-database-url
Author: Original Author: Kenneth Reitz, Maintained by: JazzBand Community
@@ -13,6 +13,7 @@
Classifier: Framework :: Django :: 4.2
Classifier: Framework :: Django :: 5.0
Classifier: Framework :: Django :: 5.1
+Classifier: Framework :: Django :: 5.2
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
@@ -21,15 +22,25 @@
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
Description-Content-Type: text/x-rst
License-File: LICENSE
Requires-Dist: Django>=4.2
-Requires-Dist: typing_extensions>=3.10.0.0
+Dynamic: author
+Dynamic: classifier
+Dynamic: description
+Dynamic: description-content-type
+Dynamic: home-page
+Dynamic: license
+Dynamic: license-file
+Dynamic: platform
+Dynamic: project-url
+Dynamic: requires-dist
+Dynamic: summary
DJ-Database-URL
~~~~~~~~~~~~~~~
@@ -55,12 +66,6 @@
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
------------
@@ -181,6 +186,63 @@
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
----------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/dj_database_url-2.3.0/dj_database_url.egg-info/requires.txt
new/dj_database_url-3.0.1/dj_database_url.egg-info/requires.txt
--- old/dj_database_url-2.3.0/dj_database_url.egg-info/requires.txt
2024-10-23 12:01:44.000000000 +0200
+++ new/dj_database_url-3.0.1/dj_database_url.egg-info/requires.txt
2025-07-02 11:29:20.000000000 +0200
@@ -1,2 +1 @@
Django>=4.2
-typing_extensions>=3.10.0.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/dj_database_url-2.3.0/setup.py
new/dj_database_url-3.0.1/setup.py
--- old/dj_database_url-2.3.0/setup.py 2024-10-23 12:01:36.000000000 +0200
+++ new/dj_database_url-3.0.1/setup.py 2025-07-02 11:29:13.000000000 +0200
@@ -6,7 +6,7 @@
setup(
name="dj-database-url",
- version="2.3.0",
+ version="3.0.1",
url="https://github.com/jazzband/dj-database-url",
license="BSD",
author="Original Author: Kenneth Reitz, Maintained by: JazzBand Community",
@@ -14,7 +14,7 @@
long_description=readme,
long_description_content_type="text/x-rst",
packages=["dj_database_url"],
- install_requires=["Django>=4.2", "typing_extensions >= 3.10.0.0"],
+ install_requires=["Django>=4.2"],
include_package_data=True,
package_data={
"dj_database_url": ["py.typed"],
@@ -32,6 +32,7 @@
"Framework :: Django :: 4.2",
"Framework :: Django :: 5.0",
"Framework :: Django :: 5.1",
+ "Framework :: Django :: 5.2",
"Intended Audience :: Developers",
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
@@ -40,10 +41,10 @@
"Topic :: Software Development :: Libraries :: Python Modules",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
- "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 :: 3.14",
],
)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/dj_database_url-2.3.0/tests/test_dj_database_url.py
new/dj_database_url-3.0.1/tests/test_dj_database_url.py
--- old/dj_database_url-2.3.0/tests/test_dj_database_url.py 2024-10-23
12:01:36.000000000 +0200
+++ new/dj_database_url-3.0.1/tests/test_dj_database_url.py 2025-07-02
11:29:13.000000000 +0200
@@ -1,8 +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
@@ -10,9 +12,10 @@
class DatabaseTestSuite(unittest.TestCase):
- def test_postgres_parsing(self):
- url =
"postgres://uf07k1i6d8ia0v:[email protected]:5431/d8r82722r2kuvn"
- url = dj_database_url.parse(url)
+ def test_postgres_parsing(self) -> None:
+ url = dj_database_url.parse(
+
"postgres://uf07k1i6d8ia0v:[email protected]:5431/d8r82722r2kuvn"
+ )
assert url["ENGINE"] == "django.db.backends.postgresql"
assert url["NAME"] == "d8r82722r2kuvn"
@@ -21,9 +24,10 @@
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"
@@ -32,8 +36,9 @@
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"
@@ -41,9 +46,10 @@
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"
@@ -52,9 +58,10 @@
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"
@@ -63,9 +70,10 @@
assert url["PASSWORD"] == "wegauwhgeuioweg"
assert url["PORT"] == 5431
- def test_postgres_search_path_parsing(self):
- url =
"postgres://uf07k1i6d8ia0v:[email protected]: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:[email protected]: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"
@@ -75,9 +83,10 @@
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:%[email protected]:5431/%23database"
- url = dj_database_url.parse(url)
+ def test_postgres_parsing_with_special_characters(self) -> None:
+ url = dj_database_url.parse(
+
"postgres://%23user:%[email protected]:5431/%23database"
+ )
assert url["ENGINE"] == "django.db.backends.postgresql"
assert url["NAME"] == "#database"
@@ -86,9 +95,10 @@
assert url["PASSWORD"] == "#password"
assert url["PORT"] == 5431
- def test_postgres_parsing_with_int_bool_str_query_string(self):
- url =
"postgres://uf07k1i6d8ia0v:[email protected]:5431/d8r82722r2kuvn?server_side_binding=true&timeout=20&service=my_service&passfile=.my_pgpass"
- 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:[email protected]: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"
@@ -101,9 +111,10 @@
assert url["OPTIONS"]["service"] == "my_service"
assert url["OPTIONS"]["passfile"] == ".my_pgpass"
- def test_postgis_parsing(self):
- url =
"postgis://uf07k1i6d8ia0v:[email protected]:5431/d8r82722r2kuvn"
- url = dj_database_url.parse(url)
+ def test_postgis_parsing(self) -> None:
+ url = dj_database_url.parse(
+
"postgis://uf07k1i6d8ia0v:[email protected]:5431/d8r82722r2kuvn"
+ )
assert url["ENGINE"] == "django.contrib.gis.db.backends.postgis"
assert url["NAME"] == "d8r82722r2kuvn"
@@ -112,9 +123,10 @@
assert url["PASSWORD"] == "wegauwhgeuioweg"
assert url["PORT"] == 5431
- def test_postgis_search_path_parsing(self):
- url =
"postgis://uf07k1i6d8ia0v:[email protected]: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:[email protected]: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"
@@ -124,9 +136,10 @@
assert url["OPTIONS"]["options"] == "-c search_path=otherschema"
assert "currentSchema" not in url["OPTIONS"]
- def test_mysql_gis_parsing(self):
- url =
"mysqlgis://uf07k1i6d8ia0v:[email protected]:5431/d8r82722r2kuvn"
- url = dj_database_url.parse(url)
+ def test_mysql_gis_parsing(self) -> None:
+ url = dj_database_url.parse(
+
"mysqlgis://uf07k1i6d8ia0v:[email protected]:5431/d8r82722r2kuvn"
+ )
assert url["ENGINE"] == "django.contrib.gis.db.backends.mysql"
assert url["NAME"] == "d8r82722r2kuvn"
@@ -135,9 +148,10 @@
assert url["PASSWORD"] == "wegauwhgeuioweg"
assert url["PORT"] == 5431
- def test_mysql_connector_parsing(self):
- url =
"mysql-connector://uf07k1i6d8ia0v:[email protected]:5431/d8r82722r2kuvn"
- url = dj_database_url.parse(url)
+ def test_mysql_connector_parsing(self) -> None:
+ url = dj_database_url.parse(
+
"mysql-connector://uf07k1i6d8ia0v:[email protected]:5431/d8r82722r2kuvn"
+ )
assert url["ENGINE"] == "mysql.connector.django"
assert url["NAME"] == "d8r82722r2kuvn"
@@ -146,7 +160,7 @@
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,
{
@@ -160,9 +174,10 @@
assert url['TEST']['NAME'] == 'mytestdatabase'
- def test_cleardb_parsing(self):
- url =
"mysql://bea6eb025ca0d8:[email protected]/heroku_97681db3eff7580?reconnect=true"
- url = dj_database_url.parse(url)
+ def test_cleardb_parsing(self) -> None:
+ url = dj_database_url.parse(
+
"mysql://bea6eb025ca0d8:[email protected]/heroku_97681db3eff7580?reconnect=true"
+ )
assert url["ENGINE"] == "django.db.backends.mysql"
assert url["NAME"] == "heroku_97681db3eff7580"
@@ -171,7 +186,7 @@
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
@@ -191,28 +206,46 @@
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:[email protected]/heroku_97681db3eff7580?reconnect=true"
- url = dj_database_url.parse(url, engine)
+ url = dj_database_url.parse(
+
"mysql://bea6eb025ca0d8:[email protected]/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,
@@ -224,14 +257,16 @@
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:[email protected]/heroku_97681db3eff7580?reconnect=true"
- url = dj_database_url.parse(url, conn_max_age=conn_max_age)
+ url = dj_database_url.parse(
+
"mysql://bea6eb025ca0d8:[email protected]/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):
+ def test_config_conn_max_age_setting(self) -> None:
conn_max_age = 600
with mock.patch.dict(
os.environ,
@@ -243,7 +278,7 @@
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,
@@ -274,7 +309,7 @@
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,
{
@@ -301,9 +336,8 @@
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"
@@ -312,9 +346,8 @@
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"
@@ -323,14 +356,13 @@
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"
@@ -346,9 +378,8 @@
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"
@@ -357,9 +388,10 @@
assert url["HOST"] == ""
assert url["PORT"] == ""
- def test_redshift_parsing(self):
- url =
"redshift://uf07k1i6d8ia0v:[email protected]:5439/d8r82722r2kuvn?currentSchema=otherschema"
- url = dj_database_url.parse(url)
+ def test_redshift_parsing(self) -> None:
+ url = dj_database_url.parse(
+
"redshift://uf07k1i6d8ia0v:[email protected]:5439/d8r82722r2kuvn?currentSchema=otherschema"
+ )
assert url["ENGINE"] == "django_redshift_backend"
assert url["NAME"] == "d8r82722r2kuvn"
@@ -370,9 +402,10 @@
assert url["OPTIONS"]["options"] == "-c search_path=otherschema"
assert "currentSchema" not in url["OPTIONS"]
- def test_mssql_parsing(self):
- url =
"mssql://uf07k1i6d8ia0v:[email protected]/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:[email protected]/d8r82722r2kuvn?driver=ODBC
Driver 13 for SQL Server"
+ )
assert url["ENGINE"] == "sql_server.pyodbc"
assert url["NAME"] == "d8r82722r2kuvn"
@@ -383,9 +416,10 @@
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:[email protected]\\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:[email protected]\\insnsnss:12345/d8r82722r2kuvn?driver=ODBC
Driver 13 for SQL Server"
+ )
assert url["ENGINE"] == "sql_server.pyodbc"
assert url["NAME"] == "d8r82722r2kuvn"
@@ -396,9 +430,10 @@
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'
@@ -410,9 +445,10 @@
assert url['OPTIONS']['sslcert'] == '/certs/client.myprojectuser.crt'
assert url['OPTIONS']['sslkey'] == '/certs/client.myprojectuser.key'
- def test_mssqlms_parsing(self):
- url =
"mssqlms://uf07k1i6d8ia0v:[email protected]/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:[email protected]/d8r82722r2kuvn?driver=ODBC
Driver 13 for SQL Server"
+ )
assert url["ENGINE"] == "mssql"
assert url["NAME"] == "d8r82722r2kuvn"
@@ -423,9 +459,10 @@
assert url["OPTIONS"]["driver"] == "ODBC Driver 13 for SQL Server"
assert "currentSchema" not in url["OPTIONS"]
- def test_timescale_parsing(self):
- url =
"timescale://uf07k1i6d8ia0v:[email protected]:5431/d8r82722r2kuvn"
- url = dj_database_url.parse(url)
+ def test_timescale_parsing(self) -> None:
+ url = dj_database_url.parse(
+
"timescale://uf07k1i6d8ia0v:[email protected]:5431/d8r82722r2kuvn"
+ )
assert url["ENGINE"] == "timescale.db.backends.postgresql"
assert url["NAME"] == "d8r82722r2kuvn"
@@ -434,9 +471,10 @@
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"
@@ -445,8 +483,9 @@
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"
@@ -454,9 +493,10 @@
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"
@@ -465,9 +505,10 @@
assert url["PASSWORD"] == "wegauwhgeuioweg"
assert url["PORT"] == 5431
- def test_timescale_search_path_parsing(self):
- url =
"timescale://uf07k1i6d8ia0v:[email protected]: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:[email protected]: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"
@@ -477,9 +518,10 @@
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:%[email protected]:5431/%23database"
- url = dj_database_url.parse(url)
+ def test_timescale_parsing_with_special_characters(self) -> None:
+ url = dj_database_url.parse(
+
"timescale://%23user:%[email protected]:5431/%23database"
+ )
assert url["ENGINE"] == "timescale.db.backends.postgresql"
assert url["NAME"] == "#database"
@@ -488,9 +530,10 @@
assert url["PASSWORD"] == "#password"
assert url["PORT"] == 5431
- def test_timescalegis_parsing(self):
- url =
"timescalegis://uf07k1i6d8ia0v:[email protected]:5431/d8r82722r2kuvn"
- url = dj_database_url.parse(url)
+ def test_timescalegis_parsing(self) -> None:
+ url = dj_database_url.parse(
+
"timescalegis://uf07k1i6d8ia0v:[email protected]:5431/d8r82722r2kuvn"
+ )
assert url["ENGINE"] == "timescale.db.backends.postgis"
assert url["NAME"] == "d8r82722r2kuvn"
@@ -499,9 +542,10 @@
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"
@@ -510,8 +554,9 @@
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"
@@ -519,9 +564,10 @@
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"
@@ -530,9 +576,10 @@
assert url["PASSWORD"] == "wegauwhgeuioweg"
assert url["PORT"] == 5431
- def test_timescalegis_search_path_parsing(self):
- url =
"timescalegis://uf07k1i6d8ia0v:[email protected]: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:[email protected]: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"
@@ -542,9 +589,10 @@
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:%[email protected]:5431/%23database"
- url = dj_database_url.parse(url)
+ def test_timescalegis_parsing_with_special_characters(self) -> None:
+ url = dj_database_url.parse(
+
"timescalegis://%23user:%[email protected]:5431/%23database"
+ )
assert url["ENGINE"] == "timescale.db.backends.postgis"
assert url["NAME"] == "#database"
@@ -553,7 +601,7 @@
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
)
@@ -561,7 +609,7 @@
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 aren’t required
url = dj_database_url.parse(
@@ -575,13 +623,13 @@
os.environ,
{"DATABASE_URL":
"postgres://user:[email protected]: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()
@@ -590,19 +638,46 @@
'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:[email protected]: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):
+ def test_options_int_values(self) -> None:
"""Ensure that options with integer values are parsed correctly."""
url = dj_database_url.parse(
"mysql://user:[email protected]:15036/db?connect_timout=3"
@@ -613,7 +688,7 @@
os.environ,
{"DATABASE_URL":
"postgres://user:[email protected]:5431/d8r8?"},
)
- def test_server_side_cursors__config(self):
+ 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