django-service-urls 2.0

django-service-urls 2.0 Release Announcement

We’re excited to announce the release of **django-service-urls 2.0**! This major release brings significant improvements, new features, and enhanced type safety while maintaining the simplicity and elegance you’ve come to expect.

django-service-urls is a Django configuration helper that allows you to represent complex service settings (databases, caches, email, storages, and task backends) as simple, portable URL strings. Instead of managing multiple configuration variables across different environments, you can use a single URL like `postgres://user:pass@host:5432/db` or set it via environment variables. This approach simplifies deployment, makes configuration more maintainable, and follows the principles of 12-factor apps.

:tada: What’s New

Python and Django Support

  • Python: 3.10, 3.11, 3.12, 3.13, 3.14
  • Django: 3.2, 4.0, 4.1, 4.2, 5.0, 5.1, 5.2, 6.0

Advanced Configuration with Query Strings and Fragments

Since version 1.9, django-service-urls has supported advanced configuration through nested dictionaries, lists, and URL fragments. This means you can express complex settings in a single URL string:

"postgres://user:pass@host:5432/db?pool.min_size=2&pool.max_size=10#CONN_MAX_AGE=300"
# → {
#     "ENGINE": "django.db.backends.postgresql",
#     "NAME": "db",
#     "USER": "user",
#     "PASSWORD’: "pass",
#     "HOST": "host",
#     "PORT": 5432,
#     "OPTIONS": {
#         "pool": {"min_size": 2, "max_size": 10}
#     },
#     "CONN_MAX_AGE": 300
#   }

Query parameters with dot notation create nested configuration options, while URL fragments define top-level Django settings.

Support for Storage and Task Backends

Version 2.0 adds support for two new Django service types:

Storage Backends - Configure Django’s STORAGES setting via URLs:

STORAGES = {
    "default": "s3://?bucket_name=mybucket&region_name=us-east-1",
    "staticfiles": "whitenoise://",
}

Task Backends - Configure django-tasks via URLs:

TASKS = {
    "default’: ‘database+dt://",
    "background’: ‘rq+dt://localhost:6379/0",
}

Automatic URL Decoding

All URL components are now automatically URL-decoded while preserving case sensitivity:

# Credentials with special characters
"postgres://user%40company:P%40ssw0rd%21@host:5432/db"
# → USER: ‘user@company’, PASSWORD: ‘P@ssw0rd!’

# Database paths with spaces
"sqlite:///My%20Database/Test%20File.db"
# → NAME: ‘/My Database/Test File.db’

# Hostnames with special characters
"postgres://user:pass@My%2DServer.Example.Com:5432/db"
# → HOST: ‘My-Server.Example.Com’ (case preserved!)

Full Type Safety

The entire codebase now includes comprehensive mypy type annotations.

Enhanced URL Parsing

New UrlInfo dataclass - Replaces the previous dictionary-based approach:

from django_service_urls import UrlInfo
from django_service_urls.parse import parse_url

url_info: UrlInfo = parse_url(“postgres://user:pass@host:5432/db”)

print(url_info.hostname)  # Type-safe attribute access
print(url_info.port)      # Integer, not string!

Expanded Database Backend Support

A new sqlite+:// protocol provides production-optimized SQLite configuration with WAL mode, IMMEDIATE transactions, memory-mapped I/O, and other performance optimizations based on dj-lite recommendations.

Both sqlite:// and sqlite+:// protocols now support PRAGMA configuration via URL fragments, allowing you to customize SQLite behavior inline:

# Override production PRAGMA settings
DATABASES = {
    "default": "sqlite+:///db.sqlite3#PRAGMA.journal_mode=DELETE&PRAGMA.busy_timeout=5000"
}

# Add PRAGMA settings to standard SQLite
DATABASES = {
    "default": "sqlite:///db.sqlite3#PRAGMA.journal_mode=WAL&PRAGMA.synchronous=NORMAL"
}

Additionally, new database backends aligned with `dj-database-url`: MSSQL, Redshift, CockroachDB, Timescale (with GIS support), and GIS variants for MySQL and Oracle.

:warning: Breaking Changes

1. Removed service_urls Shim Module

The backward-compatibility shim has been removed. Update your imports:

Before:

from service_urls import db, cache, email

After:

from django_service_urls import db, cache, email

2. Removed Service.validate() Method

The validate() method has been removed in favor of a more Pythonic EAFP (Easier to Ask for Forgiveness than Permission) pattern:

Before:

if email.validate(settings.EMAIL_BACKEND):
    email_config = email.parse(settings.EMAIL_BACKEND)

After:

from django_service_urls import ValidationError

try:

    email_config = email.parse(settings.EMAIL_BACKEND)

except ValidationError:

    # It’s a backend path string, not a URL

    …

else:

    …

3. Changed Parse Result Structure

parse_url() now returns a UrlInfo dataclass instead of a dictionary:

Before:

parsed = parse_url(url)

hostname = parsed[“hostname”]  # Dictionary access

After:

parsed = parse_url(url)

hostname = parsed.hostname  # Attribute access

4. Automatic URL Decoding

URL components are now automatically decoded. If you were manually decoding URLs, remove that code:

Before:

from urllib.parse import unquote

username = unquote(parsed_url.username)

After:

username = parsed_url.username  # Already decoded!

## :link: Resources

- Documentation: README.md

- Changelog: CHANGELOG.md

- Issues: Issues Github

- PyPI: PyPI

:vertical_traffic_light: Getting Started

Install or upgrade:

uv add django-service-urls

Or with `pip`:

pip install --upgrade django-service-urls

:speech_balloon: Feedback

We’d love to hear your feedback! Please:

- Report issues on GitHub

- Share your use cases and suggestions

- Contribute improvements via pull requests

-–

Happy configuring! :confetti_ball:

The django-service-urls team