From bfc669cd116dc38cdea37edb597c96a864b57db1 Mon Sep 17 00:00:00 2001 From: Adrien Date: Sat, 9 Sep 2023 23:34:04 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Use=20pydantic-settings=20?= =?UTF-8?q?to=20handle=20config=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/backend/settings.py | 75 ++++++++++++++++++++++--------------- backend/pyproject.toml | 8 +++- 2 files changed, 51 insertions(+), 32 deletions(-) diff --git a/backend/backend/settings.py b/backend/backend/settings.py index ff6bf84..ab2b75b 100644 --- a/backend/backend/settings.py +++ b/backend/backend/settings.py @@ -1,6 +1,14 @@ -from typing import Any +from __future__ import annotations -from pydantic import BaseModel, BaseSettings, Field, root_validator, SecretStr +from typing import Annotated + +from pydantic import BaseModel, SecretStr +from pydantic.functional_validators import model_validator +from pydantic_settings import ( + BaseSettings, + PydanticBaseSettingsSource, + SettingsConfigDict, +) class HttpSettings(BaseModel): @@ -9,28 +17,13 @@ class HttpSettings(BaseModel): cert: str | None = None -def check_user_password(cls, values: dict[str, Any]) -> dict[str, Any]: - user = values.get("user") - password = values.get("password") - - if user is not None and password is None: - raise ValueError("user is set, password shall be set too.") - - if password is not None and user is None: - raise ValueError("password is set, user shall be set too.") - - return values - - class DatabaseSettings(BaseModel): - name: str = "carrramba-encore-rate" - host: str = "127.0.0.1" - port: int = 5432 + name: str + host: str + port: int driver: str = "postgresql+psycopg" - user: str | None = None - password: SecretStr | None = None - - _user_password_validation = root_validator(allow_reuse=True)(check_user_password) + user: str + password: Annotated[SecretStr, check_user_password] class CacheSettings(BaseModel): @@ -38,9 +31,18 @@ class CacheSettings(BaseModel): host: str = "127.0.0.1" port: int = 6379 user: str | None = None - password: SecretStr | None = None + password: Annotated[SecretStr | None, check_user_password] = None - _user_password_validation = root_validator(allow_reuse=True)(check_user_password) + +@model_validator(mode="after") +def check_user_password(self) -> DatabaseSettings | CacheSettings: + if self.user is not None and self.password is None: + raise ValueError("user is set, password shall be set too.") + + if self.password is not None and self.user is None: + raise ValueError("password is set, user shall be set too.") + + return self class TracingSettings(BaseModel): @@ -50,10 +52,23 @@ class TracingSettings(BaseModel): class Settings(BaseSettings): app_name: str - idfm_api_key: SecretStr = Field(..., env="IDFM_API_KEY") - clear_static_data: bool = Field(False, env="CLEAR_STATIC_DATA") + idfm_api_key: SecretStr + clear_static_data: bool - http: HttpSettings = HttpSettings() - db: DatabaseSettings = DatabaseSettings() - cache: CacheSettings = CacheSettings() - tracing: TracingSettings = TracingSettings() + http: HttpSettings + db: DatabaseSettings + cache: CacheSettings + tracing: TracingSettings + + model_config = SettingsConfigDict(env_prefix="CER__", env_nested_delimiter="__") + + @classmethod + def settings_customise_sources( + cls, + settings_cls: type[BaseSettings], + init_settings: PydanticBaseSettingsSource, + env_settings: PydanticBaseSettingsSource, + dotenv_settings: PydanticBaseSettingsSource, + file_secret_settings: PydanticBaseSettingsSource, + ) -> tuple[PydanticBaseSettingsSource, ...]: + return env_settings, init_settings, file_secret_settings diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 8af85e7..0156387 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -9,7 +9,7 @@ readme = "README.md" python = "^3.11" aiohttp = "^3.8.3" aiofiles = "^22.1.0" -fastapi = "^0.95.0" +fastapi = "^0.103.0" uvicorn = "^0.20.0" msgspec = "^0.12.0" opentelemetry-instrumentation-fastapi = "^0.38b0" @@ -23,11 +23,12 @@ sqlalchemy = "^2.0.12" psycopg = "^3.1.9" pyyaml = "^6.0" fastapi-cache2 = {extras = ["redis"], version = "^0.2.1"} +pydantic-settings = "^2.0.3" [tool.poetry.group.db_updater.dependencies] aiofiles = "^22.1.0" aiohttp = "^3.8.3" -fastapi = "^0.95.0" +fastapi = "^0.103.0" msgspec = "^0.12.0" opentelemetry-instrumentation-fastapi = "^0.38b0" opentelemetry-instrumentation-sqlalchemy = "^0.38b0" @@ -41,11 +42,14 @@ pyyaml = "^6.0" sqlalchemy = "^2.0.12" sqlalchemy-utils = "^0.41.1" tqdm = "^4.65.0" +pydantic-settings = "^2.0.3" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" + + [tool.poetry.group.dev.dependencies] pylsp-mypy = "^0.6.2" mccabe = "^0.7.0"