FastAPI:在相同设置中加载多个环境 class

FastAPI: Loading multiple environments within the same settings class

我一直在努力实现这一目标,但似乎找不到解决方法。我的 FastAPI 项目有以下 main 入口点:

from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware
from starlette.responses import RedirectResponse

from app.core.config import get_api_settings
from app.api.api import api_router


def get_app() -> FastAPI:
    api_settings = get_api_settings()

    server = FastAPI(**api_settings.fastapi_kwargs)
    server.add_middleware(
        CORSMiddleware,
        allow_origins=["*"],
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )
    server.include_router(api_router, prefix="/api")

    @server.get("/", include_in_schema=False)
    def redirect_to_docs() -> RedirectResponse:
        return RedirectResponse(api_settings.docs_url)

    return server


app = get_app()

到目前为止没有什么特别的。如您所见,我正在导入 get_api_settings,它包含我的整个服务配置,它看起来像这样:

from functools import lru_cache
from typing import Any, Dict

from pydantic import BaseSettings


class APISettings(BaseSettings):
    """This class enables the configuration of your FastAPI instance
    through the use of environment variables.

    Any of the instance attributes can be overridden upon instantiation by
    either passing the desired value to the initializer, or by setting the
    corresponding environment variable.

    Attribute `xxx_yyy` corresponds to environment variable `API_XXX_YYY`.
    So, for example, to override `api_prefix`, you would set the environment
    variable `API_PREFIX`.

    Note that assignments to variables are also validated, ensuring that
    even if you make runtime-modifications to the config, they should have
    the correct types.
    """

    # fastapi.applications.FastAPI initializer kwargs
    debug: bool = False
    docs_url: str = "/docs"
    openapi_prefix: str = ""
    openapi_url: str = "/openapi.json"
    redoc_url: str = "/redoc"
    title: str = "Api Backend"
    version: str = "0.1.0"

    # Custom settings
    disable_docs: bool = False
    environment: str

    @property
    def fastapi_kwargs(self) -> Dict[str, Any]:
        """This returns a dictionary of the most commonly used keyword
        arguments when initializing a FastAPI instance.

        If `self.disable_docs` is True, the various docs-related arguments
        are disabled, preventing spec from being published.
        """
        fastapi_kwargs: Dict[str, Any] = {
            "debug": self.debug,
            "docs_url": self.docs_url,
            "openapi_prefix": self.openapi_prefix,
            "openapi_url": self.openapi_url,
            "redoc_url": self.redoc_url,
            "title": self.title,
            "version": self.version

        }
        if self.disable_docs:
            fastapi_kwargs.update({
                "docs_url": None,
                "openapi_url": None,
                "redoc_url": None
            })
        return fastapi_kwargs

    class Config:
        case_sensitive = True
        # env_file should be dynamic depending on the 
        # `environment` env variable
        env_file = ""
        env_prefix = ""
        validate_assignment = True


@lru_cache()
def get_api_settings() -> APISettings:
    """This function returns a cached instance of the APISettings object.

    Caching is used to prevent re-reading the environment every time the API
    settings are used in an endpoint.
    If you want to change an environment variable and reset the cache
    (e.g., during testing), this can be done using the `lru_cache` instance
    method `get_api_settings.cache_clear()`.
    """
    return APISettings()

我正在尝试为多种环境准备此服务:

对于以上每一个,我有如下三个不同的 .env 文件:

例如,.env 文件如下所示:

environment=dev
frontend_service_url=http://localhost:3000

我无法理解的是如何根据 [=] 中的 environment 属性动态设置 Config class 中的 env_file = "" 25=] 基本设置 class.

通读 Pydantic's docs 我想我可以使用 customise_sources class 方法来做这样的事情:

def load_envpath_settings(settings: BaseSettings):
    environment =  # not sure how to access it here
    for env in ("dev", "stage", "prod"):
        if environment == env:
            return f"app/configs/{environment}.env"


class APISettings(BaseSettings):
    # ...

    class Config:
        case_sensitive = True
        # env_file = "app/configs/dev.env"
        env_prefix = ""
        validate_assignment = True

        @classmethod
        def customise_sources(cls, init_settings, env_settings, file_secret_settings):
            return (
                init_settings,
                load_envpath_settings,
                env_settings,
                file_secret_settings,
            )

但是我找不到访问 load_envpath_settings 中的 environment 的方法。知道如何解决这个问题吗?或者如果有另一种方法可以做到这一点?我也尝试在我的 APISettings class 中创建另一个 @property ,它基本上与 load_envpath_settings 相同,但我无法在 Config class.

首先;通常你会把你想要激活的文件复制到 .env 文件中,然后加载它。但是,如果您想让该 .env 文件控制哪些配置处于活动状态:

您可以有两组配置 - 一组从 .env 加载初始配置(即哪个环境是活动的),另一组从 core/configs/<environment>.env 加载实际的应用程序设置文件。

class AppSettings(BaseSettings):
    environment:str = 'development'

这会受到 .env 中给出的配置(默认文件名)的影响。然后,您会将此值用于 load the API configuration by using the _env_file parameter,所有 BaseSettings 实例都支持该值。

def get_app_settings() -> AppSettings:
    return AppSettings()

def get_api_settings() -> APISettings:
    app_settings = get_app_settings()
    return APISettings(_env_file=f'core/configs/{app_settings.environment}.env')  # or os.path.join() and friends