Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DM-45138: Move Redis DSN validation into a type #217

Merged
merged 1 commit into from
Jul 16, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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