Validate database and Redis URLs with pydantic
A malformed DATABASE_URL does not fail at startup — it fails on the first query, with a stack trace that points at the driver, not the config. A pydantic validator turns it into a clear boot-time error. This page validates connection URLs, extending Custom Validators & Constraints.
Problem 1: any string accepted
# ANTI-PATTERN: a typo'd scheme passes, fails at first connect
class Settings(BaseSettings):
database_url: str # "postgres//db" (missing colon) is a valid str
redis_url: str
The error appears later as an opaque driver exception, far from the bad value.
Problem 2: plaintext where TLS is required
# ANTI-PATTERN: non-TLS Redis accepted in production
redis_url = "redis://cache:6379" # should be rediss:// in prod
A redis:// URL silently disables TLS where rediss:// was required.
Secure implementation
# config/urls.py
from urllib.parse import urlparse
from pydantic import field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(extra="forbid")
database_url: str
redis_url: str
require_tls: bool = True
@field_validator("database_url")
@classmethod
def valid_db(cls, v: str) -> str:
parsed = urlparse(v)
if parsed.scheme not in {"postgresql", "postgresql+psycopg", "mysql+pymysql"}:
raise ValueError(f"unsupported database scheme: {parsed.scheme!r}")
if not parsed.hostname:
raise ValueError("database_url has no host")
return v
@field_validator("redis_url")
@classmethod
def valid_redis(cls, v: str, info) -> str:
parsed = urlparse(v)
if parsed.scheme not in {"redis", "rediss"}:
raise ValueError("redis_url must use redis:// or rediss://")
if info.data.get("require_tls", True) and parsed.scheme != "rediss":
raise ValueError("require_tls is set but redis_url is not rediss://")
return v
urlparse plus a scheme allow-list catches malformed and plaintext URLs at startup; the Redis validator cross-checks require_tls. You can also use pydantic’s PostgresDsn/RedisDsn types for parsing, but a custom validator gives a clearer message and enforces TLS.
Gotchas & version-specific behaviour
urlparsedoes not validate credentials — it only splits the URL; the scheme/host checks do the work.- pydantic’s
PostgresDsnandRedisDsnparse and normalize, but raise generic errors; custom validators read better in logs. - Access sibling fields in a v2 field validator via
info.data(only fields validated before it are present — order matters). - Never log the full URL on error if it embeds a password; report the scheme/host only.
Production parity checklist
- DB and Redis URLs validated against a scheme allow-list at startup.
- TLS enforced (
rediss://,sslmode=require) whenrequire_tlsis set. extra="forbid"rejects stray connection variables.- Error messages avoid printing embedded credentials.
- A CI fixture exercises valid and invalid URLs.
Conclusion
A scheme allow-list and a TLS cross-check turn a bad connection URL into a precise startup error instead of a runtime mystery. For ARN and webhook validation, see Validators for AWS ARNs and URLs.