Release Notes: django-service-urls 3.1 and 3.2
django-service-urls is a Django settings helper that lets you configure
databases, caches, email, mailers, storages, and task backends with single URL
strings (e.g., postgres://user:pass@host/db).
Links
- PyPI: Client Challenge
- GitHub: GitHub - rsalmaso/django-service-urls: setting helper for django to represent databases, caches and email settings via a single string · GitHub
- Documentation: django-service-urls/README.md at main · rsalmaso/django-service-urls · GitHub
Highlights
Security: connection password no longer leaked by the UrlInfo repr (3.1)
UrlInfo.__repr__() masked the password field as *** precisely to keep
credentials out of logs and tracebacks, but printed location verbatim, and
location holds the raw netloc, user:pass@host:port. The masked password was
therefore exposed in cleartext wherever a UrlInfo was logged.
The repr now strips the userinfo from location, in both the single-host and
multi-host forms. The attribute itself is unchanged, so code reading
UrlInfo.location still gets the full netloc:
info = parse_url("postgres://user:passwd@db:5432/app")
info.location # 'user:passwd@db:5432' -- unchanged
repr(info) # ... password=***, ... location='db:5432' ...
MAILERS support via the new mailer service (3.1)
Django 6.1 replaces the flat EMAIL_* settings with MAILERS, a dictionary
mapping aliases to {"BACKEND": ..., "OPTIONS": {...}} entries. The new mailer
service parses it the same way as DATABASES and CACHES:
MAILERS = {
"default": "smtps://user:pass@smtp.example.net?timeout=30",
}
# Produces:
# {
# "default": {
# "BACKEND": "django.core.mail.backends.smtp.EmailBackend",
# "OPTIONS": {
# "host": "smtp.example.net",
# "username": "user",
# "password": "pass",
# "use_tls": True,
# "timeout": 30,
# },
# },
# }
It accepts the same URL schemes as the email service and is exported as
django_service_urls.mailer for direct use.
The email service is unchanged and still handles EMAIL_BACKEND.
Python 3.15 support (3.1)
On Python 3.15 and later, the package activates through a PEP 829 start file
(django_service_urls.start) instead of the .pth file, which is still shipped
for older Python versions.
Activation happens exactly once either way, and existing installs need no
changes. An explicit import django_service_urls.loads still works too.
Errors instead of silently ignored settings (3.1)
A setting that the installed Django version cannot use now raises
ImproperlyConfigured at startup instead of being silently ignored:
MAILERSon Django < 6.1: “The MAILERS setting requires Django 6.1 or later;
use EMAIL_BACKEND instead.”- An
EMAIL_BACKENDservice URL on Django 2028+, where theEMAIL_*settings are
removed: "EMAIL_BACKEND service URLs rely on the EMAIL_ settings removed in
Django 2028; use the MAILERS setting instead."* In 3.1 the message said
Django 7.0.
A plain backend import path in EMAIL_BACKEND is still left untouched.
Better typing support (3.2)
Typing has been improved throughout the library, which now type-checks cleanly
under mypy, pyright, pyrefly and ty.
Bug Fixes
-
ValidationErrordiscarded the contents of a nested error (3.1). When a
dict value was aValidationErrorwith its ownerror_dict(and therefore a
Nonemessage), thatNonewas stored and the nested errors were lost. The
nested error is now rendered as a string, soerror_dictvalues are always
strings, as its type annotation now states:inner = ValidationError({"nested": "boom"}) ValidationError({"k": inner}).error_dict # before: {'k': None} # after: {'k': "{'nested': 'boom'}"} -
Iterating a
ValidationErrorwith neither a message nor anerror_dict
(3.1) yielded a singleNone, contradicting the annotated yield type. It now
yields nothing.
Compatibility
- Python: 3.10, 3.11, 3.12, 3.13, 3.14, 3.15
- Django: 5.2, 6.0, 6.1, main
Upgrading
Most users can upgrade without any changes. Check for the following:
-
Do not define both
MAILERSand theEMAIL_*settings (3.1). Django 6.1
itself rejects that combination with
ImproperlyConfigured: Deprecated email settings are not allowed when MAILERS is defined: .... UseMAILERSon Django 6.1+ orEMAIL_BACKENDon older
versions, not both.If
EMAIL_BACKENDis a service URL, the error lists everyEMAIL_*setting,
not justEMAIL_BACKEND, because the URL is expanded into all of them before
Django’s check runs. RemovingEMAIL_BACKENDclears the whole list. -
Migrating to
MAILERS(3.1): theEMAIL_*settings are deprecated in
Django 6.1 and removed in Django 2028. AMAILERSentry accepts the same URL as
EMAIL_BACKEND, so migrating usually just means moving the string:# before EMAIL_BACKEND = "smtps://user:pass@smtp.example.net?timeout=30" # after MAILERS = {"default": "smtps://user:pass@smtp.example.net?timeout=30"}If your URL uses
use_localtime, remove it and set the global
EMAIL_USE_LOCALTIMEsetting instead. -
Code that parses
repr(UrlInfo)(3.1): thelocationfield is now
redacted. Read thelocationattribute instead; it is unchanged. -
Code that matches the
ImproperlyConfiguredmessage text (3.2): the
message now says “Django 2028” where 3.1 said “Django 7.0”. -
Settings dicts holding values other than URLs, config dicts or
None
(3.2): such values are now passed through instead of becoming{}or raising
AttributeError. -
Type checking (3.2):
Service.parsenow declares the values a settings
dict may hold (ConfigInput), so code passing other values may see new type
errors. New code can import the type aliases fromdjango_service_urlsor
django_service_urls.types.