• FastAPI(64)- Settings and Environment Variables 配置项和环境变量


    背景

    • 在许多情况下,应用程序可能需要一些外部设置或配置,例如密钥、数据库凭据、电子邮件服务凭据等。
    • 大多数这些设置都是可变的(可以更改),例如数据库 URL,很多可能是敏感数据,比如密码
    • 出于这个原因,通常在应用程序读取的环境变量中提供它们

    Pydantic Settings

    • Pydantic 提供了一个很好的实用程序来处理环境变量的设置
    • 从 Pydantic 导入 BaseSettings 并创建一个子类,非常类似于 Pydantic 的 BaseModel
    •  Pydantic Model 一样,可以使用类型注释和默认值声明类属性
    • 可以使用和 Pydantic Model 的所有相同验证功能和工具,例如不同的数据类型和使用 Field()
    #!usr/bin/env python
    # -*- coding:utf-8 _*-
    """
    # author: 小菠萝测试笔记
    # blog:  https://www.cnblogs.com/poloyy/
    # time: 2021/10/9 7:25 下午
    # file: 52_settings_env.py
    """
    import os
    
    import uvicorn
    from fastapi import FastAPI
    from pydantic import BaseSettings
    
    
    class Settings(BaseSettings):
        app_name: str = "Awesome API"
        admin_email: str
        items_per_user: int = 50
    
    
    settings = Settings()
    app = FastAPI()
    
    
    @app.get("/info")
    async def info():
        return {
            "app_name": settings.app_name,
            "admin_email": settings.admin_email,
            "items_per_user": settings.items_per_user,
        }
    • 然后,当创建 Settings 该类的实例时Pydantic 将以不区分大小写的方式读取环境变量
    • 因此,仍会为属性 app_name 读取为大写变量 APP_NAME
    • 接下来它将转换和验证数据
    • 因此,当使用该 settings 对象时,将拥有声明的类型的数据(例如 items_per_user 是 int)

     

    运行 uvicorn 服务器

    要为单个命令设置多个环境变量,只需用空格分隔它们,并将它们全部放在命令之前

    ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" uvicorn main:app

    访问 /info 接口

    Settings 跨模块调用

    config.py 

    from pydantic import BaseSettings
    
    class Settings(BaseSettings):
        app_name: str = "Awesome API"
        admin_email: str
        items_per_user: int = 50
    
    settings = Settings()

     

    main.py

    from fastapi import FastAPI
    from .config import settings
    
    
    app = FastAPI()
    
    
    @app.get("/info")
    async def info():
        return {
            "app_name": settings.app_name,
            "admin_email": settings.admin_email,
            "items_per_user": settings.items_per_user,
        }

    Settings 在依赖项中

    前言

    • 在某些情况下,提供依赖项的 Settings 会有用,而不是让全局对象拥有可随处使用的 Settings
    • 在测试期间会有用,因为使用自定义 Settings 覆盖依赖项非常容易

    config.py 

    from pydantic import BaseSettings
    
    class Settings(BaseSettings):
        app_name: str = "Awesome API"
        admin_email: str
        items_per_user: int = 50

    这里不创建默认实例 settings = Settings()

    main.py

    from fastapi import FastAPI, Depends
    from functools import lru_cache
    from .config import Settings
    
    app = FastAPI()
    
    @lru_cache
    def get_settings():
        return Settings
    
    
    @app.get("/info")
    async def info(settings: Settings = Depends(get_settings)):
        return {
            "app_name": settings.app_name,
            "admin_email": settings.admin_email,
            "items_per_user": settings.items_per_user,
        }

    测试上述接口

    from fastapi.testclient import TestClient
    from .config import Settings
    from .main import app, get_settings
    
    client = TestClient(app)
    
    # 依赖覆盖,为 Settings 对象设置一个新的 admin_email 值
    def get_settings_override():
        return Settings(admin_email="testing_admin@example.com")
    
    app.dependency_overrides[get_settings] = get_settings_override
    
    
    def test_app():
        response = client.get("/info")
        data = response.json()
        assert data == {
            "app_name": "Awesome API",
            "admin_email": "testing_admin@example.com",
            "items_per_user": 50,
        }

    命令行执行

    > pytest 53_settings_test.py                                                      
    ============================================================================================================ test session starts ============================================================================================================
    platform darwin -- Python 3.9.5, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
    rootdir: /Users/polo/Downloads/FastAPI_project
    plugins: anyio-3.3.2
    collected 1 item                                                                                                                                                                                                                            
    
    53_settings_test.py .                                                                                                                                                                                                                 [100%]
    
    ============================================================================================================= 1 passed in 0.30s =============================================================================================================

    使用 .env 文件

    背景

    如果有会经常变化的设置项,也许在不同的环境中,将它们放在一个文件中,然后从文件中读取它们,就好像它们是环境变量一样

    这些环境变量通常放在一个文件 .env 中,该文件称为“dotenv”

    tips

    • 以点 (.) 开头的文件是类 Unix 系统(如 Linux 和 macOS)中的隐藏文件
    • 但是 dotenv 文件实际上不必具有那个确切的文件名
    • Pydantic 支持使用外部库读取这类型的文件

    安装第三方库

    pip install python-doten

    .env 文件

    ADMIN_EMAIL="xiaopolo@example.com"
    APP_NAME="小菠萝"

      

    config.py 文件

    from pydantic import BaseSettings
    
    
    class Settings(BaseSettings):
        app_name: str = "Awesome API"
        admin_email: str
        items_per_user: int = 50
    
        class Config:
            # 设置需要识别的 .env 文件
            env_file = ".env"
            # 设置字符编码
            env_file_encoding = 'utf-8'

    第二种调用 .env 的方法

    # 创建 Settings 对象的时候指定
    settings = Settings(_env_file='.env', _env_file_encoding='utf-8')

    lru_cache

    背景

    继上面的栗子,读取 .env 文件可能是一件代价高昂(缓慢)的操作

    从性能角度出发,肯定希望只读取一次,后续每个请求可以重复使用同一个 Settings 对象,这样就只会读取一次 .env 文件

    def get_settings():
        return Settings()

    上述代码,如果作为请求的依赖项,那么每次请求进来,都会创建一个 Settings 对象,然后读取一次 .env 文件,这不是我们希望的

    @lru_cache

    如果加上了 @lru_cache 那么 get_settings 只会在第一次调用的时候执行一次,然后 Settings 对象也只会创建一次,.env 文件也只会读取一次

    from functools import lru_cache
    from fastapi import Depends, FastAPI
    from . import config
    
    app = FastAPI()
    
    
    @lru_cache()
    def get_settings():
        return config.Settings()
    
    
    @app.get("/info")
    async def info(settings: config.Settings = Depends(get_settings)):
        return {
            "app_name": settings.app_name,
            "admin_email": settings.admin_email,
            "items_per_user": settings.items_per_user,
        }

    对于后续请求的依赖项中的 get_settings() 的任何后续调用,它不会执行 get_settings() 的内部代码并创建新的 Settings 对象,而是返回与第一次调用时返回的相同对象

    lru_cache 技术细节

    • @lru_cache() 修改它修饰的函数返回与第一次返回相同的值,而不是再次执行函数内部代码
    • 因此,它下面的函数将针对每个参数组合执行一次
    • 然后,每当使用完全相同的参数组合调用函数时,每个参数组合返回相同的值将一次又一次地使用
    • 在请求依赖项 get_settings() 的情况下,该函数没有参数,所以它总是返回相同的值
    • 这样,它的行为就好像它只是一个全局变量
    • 但是因为它使用了一个依赖函数,所以可以很容易地覆盖它进行测试
    • @lru_cache() 是 functools 的一部分,它是 Python 标准库的一部分
    • 使用 @lru_cache() 可以避免为每个请求一次又一次地读取 .env 文件,同时可以在测试期间覆盖它的值

    有参数的函数的栗子

    @lru_cache()
    def say_hi(name: str, salutation: str = "Ms."):
        print(123)
        return f"Hello {salutation} {name}"
    
    
    print(say_hi(name="Camila"))
    print(say_hi(name="Camila"))
    
    print(say_hi(name="Rick", salutation="Mr."))
    print(say_hi(name="Rick", salutation="Mr."))
    
    print(say_hi(name="Camila"))
    print(say_hi(name="Rick", salutation="Mr."))

    运行结果

    123
    Hello Ms. Camila
    Hello Ms. Camila
    
    123
    Hello Mr. Rick
    Hello Mr. Rick
    
    Hello Ms. Camila
    Hello Mr. Rick

    使用完全相同的参数调用函数时,直接返回结果而不会执行厘米的代码

    原理图

      

  • 相关阅读:
    第一天,用诗遇见
    13计本班人工智能第二次作业
    第一次人工智能作业
    陈林 130702010048
    人工智能第一次作业
    第二次作业
    人工智能第一次作业
    软件工程(2019)结对编程第二次作业
    软件工程(2019)结对编程第一次作业
    软件工程(2019)第二次作业
  • 原文地址:https://www.cnblogs.com/poloyy/p/15388090.html
Copyright © 2020-2023  润新知