Skip to content

Commit

Permalink
Move Redis DSN validation into a type
Browse files Browse the repository at this point in the history
Add a type that validates a Redis DSN and substitutes in information
from environment variables set by tox-docker if they are present.
  • Loading branch information
rra committed Jul 16, 2024
1 parent 11a35bc commit a18f0e0
Showing 1 changed file with 34 additions and 27 deletions.
61 changes: 34 additions & 27 deletions src/vocutouts/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@
RedisDsn,
SecretStr,
UrlConstraints,
field_validator,
)
from pydantic_core import MultiHostUrl
from pydantic_core import MultiHostUrl, Url
from pydantic_settings import BaseSettings, SettingsConfigDict
from safir.arq import ArqMode
from safir.datetime import parse_timedelta
Expand Down Expand Up @@ -76,6 +75,38 @@ def _validate_env_async_postgres_dsn(v: MultiHostUrl) -> MultiHostUrl:
"""Async PostgreSQL data source URL honoring Docker environment variables."""


def _validate_env_redis_dsn(v: RedisDsn) -> RedisDsn:
"""Possibly adjust a Redis DSN based on environment variables.
When run via tox and tox-docker, the Redis hostname and port will be
randomly selected and exposed only in environment variables. We have to
patch that into the Redis URL at runtime since `tox doesn't have a way of
substituting it into the environment
<https://github.com/tox-dev/tox-docker/issues/55>`__.
"""
if port := os.getenv("REDIS_6379_TCP_PORT"):
return RedisDsn.build(
scheme=v.scheme,
username=v.username,
password=v.password,
host=os.getenv("REDIS_HOST", v.unicode_host() or "localhost"),
port=int(port),
path=v.path.lstrip("/") if v.path else v.path,
query=v.query,
fragment=v.fragment,
)
else:
return v


EnvRedisDsn: TypeAlias = Annotated[
Url,
UrlConstraints(host_required=True, allowed_schemes=["redis"]),
AfterValidator(_validate_env_redis_dsn),
]
"""Redis data source URL honoring Docker environment variables."""


def _parse_timedelta(v: str | float | timedelta) -> float | timedelta:
if not isinstance(v, str):
return v
Expand Down Expand Up @@ -129,7 +160,7 @@ class Config(BaseSettings):
description="This will always be production outside the test suite",
)

arq_queue_url: RedisDsn = Field(
arq_queue_url: EnvRedisDsn = Field(
...,
title="arq Redis DSN",
description="DSN of Redis server to use for the arq queue",
Expand Down Expand Up @@ -221,30 +252,6 @@ class Config(BaseSettings):
env_prefix="CUTOUT_", case_sensitive=False
)

@field_validator("arq_queue_url")
@classmethod
def _validate_arq_queue_url(cls, v: RedisDsn) -> RedisDsn:
if v.scheme != "redis":
raise ValueError("Only redis DSNs are supported")

# When run via tox and tox-docker, the Redis port will be randomly
# selected and exposed only in the REDIS_6379_TCP environment
# variable. We have to patch that into the Redis URL at runtime since
# tox doesn't have a way of substituting it into the environment (see
# https://github.com/tox-dev/tox-docker/issues/55).
if port := os.getenv("REDIS_6379_TCP_PORT"):
return RedisDsn.build(
scheme=v.scheme,
username=v.username,
password=v.password,
host=os.getenv("REDIS_HOST", v.unicode_host() or "localhost"),
port=int(port),
path=v.path,
query=v.query,
fragment=v.fragment,
)
return v

@property
def arq_redis_settings(self) -> RedisSettings:
"""Redis settings for arq."""
Expand Down

0 comments on commit a18f0e0

Please sign in to comment.