Skip to content
Merged
Show file tree
Hide file tree
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
38 changes: 36 additions & 2 deletions config.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from functools import lru_cache
from typing import Optional

from pydantic import field_validator
from pydantic import field_validator, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
environment: str = "dev"
auth0_domain: str
auth0_custom_domain: Optional[str] = None
auth0_management_id: str
Expand All @@ -24,7 +25,7 @@ class Settings(BaseSettings):
# to be available before the app starts
cors_allowed_origins: str
# AAI Portal URL for admin links in emails
aai_portal_url: str = "https://aaiportal.test.biocommons.org.au"
aai_portal_url: Optional[str] = None
# Default sender for outbound emails
default_email_sender: str = "[email protected]"
# Allowed email domains for SBP registration
Expand All @@ -47,12 +48,45 @@ class Settings(BaseSettings):

model_config = SettingsConfigDict(env_file=".env", extra="ignore")

@field_validator("environment", mode="before")
def normalize_environment(cls, value: str | None) -> str:
if value is None:
return "dev"
normalized = str(value).strip().lower()
if normalized in {"dev", "development"}:
return "dev"
if normalized in {"staging", "stage"}:
return "staging"
return normalized

@field_validator('auth0_custom_domain', mode="after")
def strip_trailing_slash(cls, value: str | None) -> str | None:
if value is None:
return None
return value.rstrip("/")

@field_validator("aai_portal_url", mode="after")
def strip_aai_portal_trailing_slash(cls, value: str | None) -> str | None:
if value is None:
return None
return value.rstrip("/")

@model_validator(mode="after")
def set_default_aai_portal_url(self) -> "Settings":
if self.aai_portal_url:
return self
env_to_url = {
"dev": "https://dev.portal.aai.test.biocommons.org.au",
"staging": "https://staging.portal.aai.test.biocommons.org.au",
}
default_url = env_to_url.get(self.environment)
if not default_url:
raise ValueError(
"Unknown ENVIRONMENT value and AAI_PORTAL_URL is not set."
)
self.aai_portal_url = default_url
return self


@lru_cache()
def get_settings():
Expand Down
43 changes: 43 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import pytest

from config import Settings


def _base_settings_kwargs():
return {
"auth0_domain": "mock-domain",
"auth0_management_id": "mock-id",
"auth0_management_secret": "mock-secret",
"auth0_audience": "mock-audience",
"recaptcha_secret": "mock-recaptcha",
"jwt_secret_key": "mock-secret-key",
"cors_allowed_origins": "https://test",
}


@pytest.mark.parametrize(
("environment", "expected_url"),
[
("dev", "https://dev.portal.aai.test.biocommons.org.au"),
("development", "https://dev.portal.aai.test.biocommons.org.au"),
("staging", "https://staging.portal.aai.test.biocommons.org.au"),
("stage", "https://staging.portal.aai.test.biocommons.org.au"),
],
)
def test_aai_portal_url_defaults_by_environment(environment, expected_url):
settings = Settings(_env_file=None, environment=environment, **_base_settings_kwargs())
assert settings.aai_portal_url == expected_url


def test_aai_portal_url_override_strips_trailing_slash():
settings = Settings(
_env_file=None,
aai_portal_url="https://example.test/",
**_base_settings_kwargs(),
)
assert settings.aai_portal_url == "https://example.test"


def test_unknown_environment_requires_explicit_portal_url():
with pytest.raises(ValueError, match="Unknown ENVIRONMENT value"):
Settings(_env_file=None, environment="qa", **_base_settings_kwargs())