Skip to content

Configuration

Application configuration management.

This module defines the application settings using Pydantic Settings, reading configuration from environment variables and providing computed properties for database connection strings.

The configuration is organized into sections: - JWT authentication settings - Auth database connection - FHIR database connection - EHRbase connection - VAPID keys for push notifications

Settings

Bases: BaseSettings

Application settings loaded from environment variables.

All database passwords and secrets are stored as SecretStr to prevent accidental logging of sensitive values. Connection URLs are computed from individual components to support Docker Compose networking.

Attributes:

Name Type Description
API_PREFIX str

Base path for all API routes (default: /api)

JWT_SECRET SecretStr

Secret key for JWT token signing (min 32 chars)

JWT_ALG str

JWT signing algorithm (default: HS256)

ACCESS_TTL_MIN int

Access token lifetime in minutes (default: 15)

REFRESH_TTL_DAYS int

Refresh token lifetime in days (default: 7)

COOKIE_DOMAIN str | None

Domain for auth cookies (None = current domain)

SECURE_COOKIES bool

Whether to use Secure flag on cookies (default: False)

AUTH_DB_* bool

Authentication database connection parameters

FHIR_DB_* bool

FHIR database connection parameters

EHRBASE_* bool

EHRbase API connection parameters

VAPID_PRIVATE bool

VAPID private key for push notifications

Source code in app/config.py
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
class Settings(BaseSettings):
    """Application settings loaded from environment variables.

    All database passwords and secrets are stored as SecretStr to prevent
    accidental logging of sensitive values. Connection URLs are computed
    from individual components to support Docker Compose networking.

    Attributes:
        API_PREFIX: Base path for all API routes (default: /api)
        JWT_SECRET: Secret key for JWT token signing (min 32 chars)
        JWT_ALG: JWT signing algorithm (default: HS256)
        ACCESS_TTL_MIN: Access token lifetime in minutes (default: 15)
        REFRESH_TTL_DAYS: Refresh token lifetime in days (default: 7)
        COOKIE_DOMAIN: Domain for auth cookies (None = current domain)
        SECURE_COOKIES: Whether to use Secure flag on cookies (default: False)

        AUTH_DB_*: Authentication database connection parameters
        FHIR_DB_*: FHIR database connection parameters
        EHRBASE_*: EHRbase API connection parameters
        VAPID_PRIVATE: VAPID private key for push notifications
    """

    API_PREFIX: str = "/api"
    BACKEND_ENV: str = "development"

    # CORS — restrict in production, allow all in development
    CORS_ORIGINS: list[str] = Field(
        default=["*"],
        description="Allowed CORS origins (set explicitly in production)",
    )

    # No env_file: read env provided by Docker/Compose (and Docker secrets)
    model_config = SettingsConfigDict(
        extra="ignore",
        case_sensitive=False,
        # If you use Docker secrets, uncomment:
        # secrets_dir="/run/secrets",
    )

    # --- Auth ---
    JWT_SECRET: SecretStr = Field(
        ..., min_length=32, description="JWT signing key"
    )
    JWT_ALG: str = "HS256"
    ACCESS_TTL_MIN: int = 15
    REFRESH_TTL_DAYS: int = 7
    PASSWORD_RESET_TTL_MIN: int = 30
    COOKIE_DOMAIN: str | None = None
    SECURE_COOKIES: bool = False

    # --- Frontend URL (for email links) ---
    FRONTEND_URL: str = Field(
        "http://localhost",
        description="Base URL of the frontend for email links",
    )

    # --- Clinical services flag ---
    CLINICAL_SERVICES_ENABLED: bool = Field(
        True,
        description=(
            "Whether FHIR and EHRbase are required. "
            "Teaching deployments set this to false."
        ),
    )

    # --- Auth Database ---
    AUTH_DB_NAME: str = Field("quill_auth", description="Auth database name")
    AUTH_DB_USER: str = Field("auth_user", description="Auth database user")
    AUTH_DB_PASSWORD: SecretStr = Field(
        ..., description="Auth database password"
    )
    AUTH_DB_HOST: str = Field(
        "postgres-auth", description="Auth database host"
    )
    AUTH_DB_PORT: int = Field(5432, description="Auth database port")

    # --- FHIR Database ---
    FHIR_DB_NAME: str = Field("hapi", description="FHIR database name")
    FHIR_DB_USER: str = Field("hapi_user", description="FHIR database user")
    FHIR_DB_PASSWORD: SecretStr = Field(
        SecretStr("fhir_password"),
        description="FHIR database password",
    )
    FHIR_DB_HOST: str = Field(
        "postgres-fhir", description="FHIR database host"
    )
    FHIR_DB_PORT: int = Field(5432, description="FHIR database port")

    # --- EHRbase Database ---
    EHRBASE_DB_NAME: str = Field(
        "ehrbase", description="EHRbase database name"
    )
    EHRBASE_DB_USER: str = Field(
        "ehrbase_user", description="EHRbase database user"
    )
    EHRBASE_DB_PASSWORD: SecretStr = Field(
        SecretStr("ehrbase_password"),
        description="EHRbase database password",
    )
    EHRBASE_DB_HOST: str = Field(
        "postgres-ehrbase", description="EHRbase database host"
    )
    EHRBASE_DB_PORT: int = Field(5432, description="EHRbase database port")

    # --- FHIR Server API ---
    FHIR_SERVER_URL: str = "http://fhir:8080/fhir"

    # --- EHRbase Server API ---
    EHRBASE_URL: str = "http://ehrbase:8080/ehrbase"
    EHRBASE_API_USER: str = Field(
        "ehrbase_user", description="EHRbase API user"
    )
    EHRBASE_API_PASSWORD: SecretStr = Field(
        SecretStr("ehrbase_password"),
        description="EHRbase API password",
    )
    EHRBASE_API_ADMIN_USER: str = Field(
        "ehrbase_admin", description="EHRbase API admin user"
    )
    EHRBASE_API_ADMIN_PASSWORD: SecretStr = Field(
        SecretStr("ehrbase_admin_password"),
        description="EHRbase API admin password",
    )

    # --- Teaching / GCS ---
    TEACHING_GCS_BUCKET: str | None = Field(
        None,
        description="GCS bucket for teaching question bank images",
    )
    TEACHING_IMAGES_BASE_URL: str | None = Field(
        None,
        description="Override for dev: local file serving base URL",
    )
    TEACHING_QUESTION_BANK_PATH: str | None = Field(
        None,
        description="Local filesystem path to question bank content",
    )

    # --- Email ---
    RESEND_API_KEY: SecretStr | None = Field(
        None,
        description="Resend API key for sending emails",
    )
    EMAIL_FROM: str = Field(
        "noreply@quillmedical.com",
        description="Sender address for outgoing emails",
    )
    EMAIL_DRY_RUN: bool = Field(
        True,
        description=(
            "When True, log emails instead of sending. "
            "Defaults to True for development safety."
        ),
    )

    # --- Startup validation ---
    @model_validator(mode="after")
    def _validate_clinical_services(self) -> "Settings":
        """Verify FHIR/EHRbase config is present when clinical services are enabled."""
        if not self.CLINICAL_SERVICES_ENABLED:
            return self
        if not self.FHIR_SERVER_URL:
            raise ValueError(
                "FHIR_SERVER_URL is required when "
                "CLINICAL_SERVICES_ENABLED is true"
            )
        if not self.EHRBASE_URL:
            raise ValueError(
                "EHRBASE_URL is required when "
                "CLINICAL_SERVICES_ENABLED is true"
            )
        return self

    # --- Computed Database URLs ---
    @computed_field  # type: ignore[prop-decorator]
    @property
    def AUTH_DATABASE_URL(self) -> str:
        """Auth Database Connection URL.

        Constructs a PostgreSQL connection URL for the authentication database
        using psycopg (pure Python driver). The password is URL-encoded to handle
        special characters safely. This URL is used by SQLAlchemy to establish
        connections to the auth database container.

        Returns:
            str: SQLAlchemy-compatible database URL in format:
                postgresql+psycopg://user:password@host:port/database
        """
        """Construct auth database URL from components."""
        return (
            f"postgresql+psycopg://{self.AUTH_DB_USER}:"
            f"{quote_plus(self.AUTH_DB_PASSWORD.get_secret_value())}@"
            f"{self.AUTH_DB_HOST}:{self.AUTH_DB_PORT}/{self.AUTH_DB_NAME}"
        )

    @computed_field  # type: ignore[prop-decorator]
    @property
    def FHIR_DATABASE_URL(self) -> str | None:
        """FHIR Database Connection URL.

        Constructs a PostgreSQL connection URL for the HAPI FHIR database.
        This database stores patient demographics and FHIR resources managed
        by the HAPI FHIR server. The password is URL-encoded for safe inclusion
        in the connection string.

        Returns None when clinical services are disabled (e.g. teaching).
        """
        if not self.CLINICAL_SERVICES_ENABLED:
            return None
        return (
            f"postgresql+psycopg://{self.FHIR_DB_USER}:"
            f"{quote_plus(self.FHIR_DB_PASSWORD.get_secret_value())}@"
            f"{self.FHIR_DB_HOST}:{self.FHIR_DB_PORT}/{self.FHIR_DB_NAME}"
        )

    @computed_field  # type: ignore[prop-decorator]
    @property
    def EHRBASE_DATABASE_URL(self) -> str | None:
        """EHRbase Database Connection URL.

        Constructs a PostgreSQL connection URL for the EHRbase database.
        This database stores OpenEHR compositions (clinical documents and letters)
        managed by the EHRbase server. The password is URL-encoded and includes
        special PostgreSQL parameters required by EHRbase.

        Returns None when clinical services are disabled (e.g. teaching).
        """
        if not self.CLINICAL_SERVICES_ENABLED:
            return None
        return (
            f"postgresql+psycopg://{self.EHRBASE_DB_USER}:"
            f"{quote_plus(self.EHRBASE_DB_PASSWORD.get_secret_value())}@"
            f"{self.EHRBASE_DB_HOST}:{self.EHRBASE_DB_PORT}/{self.EHRBASE_DB_NAME}"
        )

AUTH_DATABASE_URL property

AUTH_DATABASE_URL

Auth Database Connection URL.

Constructs a PostgreSQL connection URL for the authentication database using psycopg (pure Python driver). The password is URL-encoded to handle special characters safely. This URL is used by SQLAlchemy to establish connections to the auth database container.

Returns:

Name Type Description
str str

SQLAlchemy-compatible database URL in format: postgresql+psycopg://user:password@host:port/database

FHIR_DATABASE_URL property

FHIR_DATABASE_URL

FHIR Database Connection URL.

Constructs a PostgreSQL connection URL for the HAPI FHIR database. This database stores patient demographics and FHIR resources managed by the HAPI FHIR server. The password is URL-encoded for safe inclusion in the connection string.

Returns None when clinical services are disabled (e.g. teaching).

EHRBASE_DATABASE_URL property

EHRBASE_DATABASE_URL

EHRbase Database Connection URL.

Constructs a PostgreSQL connection URL for the EHRbase database. This database stores OpenEHR compositions (clinical documents and letters) managed by the EHRbase server. The password is URL-encoded and includes special PostgreSQL parameters required by EHRbase.

Returns None when clinical services are disabled (e.g. teaching).