FastAPI基础入门(三)

FastAPI 基础入门

fastapi第三章


文章目录

  • [FastAPI 基础入门](#FastAPI 基础入门)
  • [3.1 app应用配置参数详解](#3.1 app应用配置参数详解)
    • [3.1.1 开启debug模式](#3.1.1 开启debug模式)
    • [3.1.2 关于API交互式文档参数](#3.1.2 关于API交互式文档参数)
    • [3.1.3 关闭交互式文档访问](#3.1.3 关闭交互式文档访问)
    • [3.1.4 全局routes参数说明](#3.1.4 全局routes参数说明)
    • [3.1.5 全局异常/错误捕获](#3.1.5 全局异常/错误捕获)
  • [3.2 API端口路由注册和匹配](#3.2 API端口路由注册和匹配)
    • [3.2.1 路由节点元数据](#3.2.1 路由节点元数据)
    • [3.2.2 路由URL匹配](#3.2.2 路由URL匹配)
    • [3.2.3 基于APIRouter实例的路由注册](#3.2.3 基于APIRouter实例的路由注册)
    • 在这里插入图片描述
  • [3.3 异步和同步API端点路由](#3.3 异步和同步API端点路由)
    • [3.3.1 同步API端点路由](#3.3.1 同步API端点路由)
    • [3.3.2 异步API端点路由](#3.3.2 异步API端点路由)
  • [3.4 多应用挂载](#3.4 多应用挂载)
    • [3.4.1 主从应用挂载](#3.4.1 主从应用挂载)
    • [3.4.2 挂载其他wsgi应用](#3.4.2 挂载其他wsgi应用)
  • [3.5 自定义配置swagger_ui](#3.5 自定义配置swagger_ui)
  • [3.6 应用配置信息读取](#3.6 应用配置信息读取)
    • [3.6.1 基于文件读取配置参数](#3.6.1 基于文件读取配置参数)
    • [3.6.2 基于pydantic和.env环境变量读取配置参数](#3.6.2 基于pydantic和.env环境变量读取配置参数)
    • [3.6.3 给配置读取加上缓存](#3.6.3 给配置读取加上缓存)
  • [3.7 API端点路由函数参数](#3.7 API端点路由函数参数)
    • [3.7.1 路径操作及路径函数](#3.7.1 路径操作及路径函数)
    • [3.7.2 path 参数](#3.7.2 path 参数)
    • [3.7.3 Query参数](#3.7.3 Query参数)
    • [3.7.4 Body参数](#3.7.4 Body参数)
    • [3.7.5 Form数据和文件处理](#3.7.5 Form数据和文件处理)

本章源码

3.1 app应用配置参数详解

3.1.1 开启debug模式

复制代码
from fastapi import FastAPI
from fastapi.response import PlainTextResponse

app = FastAPI(debug=True)
@app.get('/')
def index():
	1988/0
	return PlainTextResponse('欢迎学习FastAPI框架!')

在上述代码中,实例化app对象时设置了debug参数值为True;并且创建一个API,在接口函数设置了一个"1988/0"的错误"ZeroDivision-Error"。启动服务,此时页面http://127.0.0.1:8000/会显示出详细的错误堆栈异常信息。

通过堆栈异常信息,可以快速定位到错误问题点的具体位置。

3.1.2 关于API交互式文档参数

调试接口的过程中,常用的是基于Swagger UI模式生成文档。

复制代码
#!/usr/bin/evn python
# -*- coding: utf-8 -*-

from fastapi import FastAPI

app = FastAPI(
    title="文档的标题",
    description='关于该API文档一些描述信息补充说明',
    version='v1.0.0',
    openapi_prefix='',
    swagger_ui_oauth2_redirect_url="/docs/oauth2-redirect",
    swagger_ui_init_oauth=None,
    docs_url='/docs',
    redoc_url='/redoc',
    openapi_url="/openapi/openapi_json.json",
    terms_of_service="https://terms/团队的官网网站/",
    deprecated=True,
    contact={
        "name": "邮件接收者信息",
        "url": "https://xxx.cc",
        "email": "[email protected]",
    },
    license_info={
        "name": "版权信息说明 License v3.0",
        "url": "https://xxxxxxx.com",
    },
    openapi_tags=[
        {
            "name": "接口分组",
            "description": "接口分组信息说明",
        },
    ],
    # 配置服务请求地址相关的参数信息
    servers=[
        {"url": "/", "description": "本地调试环境"},
        {"url": "https://xx.xx.com", "description": "线上测试环境"},
        {"url": "https://xx2.xx2.com", "description": "线上生产环境"},
    ]
)

@app.get(path="/index")
async def index():
    return {"index": "index"}

if __name__ == "__main__":
    import uvicorn
    import os

    app_modeel_name = os.path.basename(__file__).replace(".py", "")
    print(app_modeel_name)
    uvicorn.run(f"{app_modeel_name}:app", host='127.0.0.1', reload=True)

参数项说明:

字段 说明
title 表示整个API文档的标题和文档网站标题,默认fastapi
description 表示该API文档描述的补充说明,支持使用markdown格式来编写
version 表示API文档版本号信息
openapi_prefix 配置访问openapi_json.json文件路径的前缀,默认空字符串。
swagger_ui_init_oauth2_redirect_url 配置在swagger_ui,使用oauth2进行身份认证时的授权URL重定向地址。
swagger_ui_init_oauth 自定义oauth认证信息配置,默认是none
docs_url 自定义swagger_ui交互式文档访问请求地址,默认为docs。
redoc_url 自定义redoc_ui交互式文档访问请求地址,默认为redoc。
openapi_url 配置访问openapi_json.json文件路径
terms_of_service 团队服务条款的URL;如果提供,则该值必须是一个URL;该参数为非必要参数
deprecated 是否全局标注所有的API为废弃标识,默认为none
openapi_tags 默认的接口分组列表信息,可以定义相关api的分组tag名称
servers 默认的接口分组列表信息,可以定义相关API的分组tag名称

3.1.3 关闭交互式文档访问

复制代码
#!/usr/bin/evn python
# -*- coding: utf-8 -*-

from fastapi import FastAPI
app = FastAPI(
    docs_url=None,
    redoc_url=None,
    # 或者直接设置openapi_url=None
    openapi_url=None,
)
if __name__ == "__main__":
    import uvicorn
    import os

    app_modeel_name = os.path.basename(__file__).replace(".py", "")
    print(app_modeel_name)
    uvicorn.run(f"{app_modeel_name}:app", host='127.0.0.1', reload=True)

设置了docs_url=None和redoc_url=None。访问http:/127.0.0.1:8000/docs的请求都会返回"not found"的提示,表示找不到该路由的访问地址。

3.1.4 全局routes参数说明

复制代码
from fastapi import FastAPI,Request
from fastapi.responses import JSONResponse
from fastapi.routing import APIRoute

async def fastapi_index():
    return JSONResponse({'index':'fastapi_index'})


async def fastapi_about():
    return JSONResponse({'about':'fastapi_about'})

# 定义一个routes列表,保存着已经实例化的APIRoute对象实例。
routes = [
    APIRoute(path='/fastapi/index',endpoint=fastapi_index,methods=['GET',"POST"]),
    APIRoute(path='/fastapi/about',endpoint=fastapi_about,methods=["POST"]),
]

app = FastAPI(routes=routes)

if __name__ == "__main__":
    import uvicorn
    import os

    app_modeel_name = os.path.basename(__file__).replace(".py", "")
    print(app_modeel_name)
    uvicorn.run(f"{app_modeel_name}:app", host='127.0.0.1', reload=True)

3.1.5 全局异常/错误捕获

exception_handlers参数主要用于捕获在执行业务逻辑处理过程中抛出的各种异常。

复制代码
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.exceptions import HTTPException

# 自定义异常处理程序
async def http_exception_handler(request: Request, exc: HTTPException):
    return JSONResponse(
        status_code=exc.status_code,
        content={"code": exc.status_code, "error": "没有定义这个请求地址"},
        media_type="application/json; charset=utf-8"
    )

app = FastAPI(exception_handlers={404: http_exception_handler})

@app.get("/")
async def read_root():
    return {"message": "Hello World"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="127.0.0.1", port=8000, reload=True)

3.2 API端口路由注册和匹配

在FastAPI框架中,所有注册路由都会统一保存到app.routes中。

3.2.1 路由节点元数据

元数据 :一组用于描述数据的数据,主要用于组织、查找和理解数据(通常指描述数据属性的信息)。

与api可视化文档显示有关的字段信息说明如下:

参数 说明
tags 设置API文档中接口所属组别的标签名,可以理解为分组名称,支持设定多个所属分组
summary 设置API文档中该路由接口名称,默认值为当前被装饰的函数(视图函数)名称
description 设置API文档中对该路由功能的详细描述
response_description 设置API文档中对该路由响应保温信息结果的描述
deprecated 设置API文档中是否该路由标记为废弃接口
operation_id 自定义设置路径操作中使用的openapi的 operation_id名称
name 设置API文档中该路由姐哭的名称。其功能和summary类似,但是name主要是供用户反向查询使用。两者同时存在会优先显示summary值
openapi_extra 用于自定义或扩展API文档中对应的openapi_extra字段的功能
include_in_schema 表示该路由接口相关信息是否在API文档中显示

与响应报文处理有关的字段信息说明如下:

参数 说明
path 定义路由访问的URL地址
response_model 定义函数处理结果中返回的json的模型类,这里会把输出数据转换为对应的response_model中声明的数据模型
status_code 定义响应报文状态码
response_class 设置响应保温使用的Response类,默认返回JSONReponse
responses 设定不同响应报文状态码下不同的响应模型
response_model_include 设置响应模型的json信息中包含哪些字段,参数格式为集合{字段名,字段名,...}
response_model_exclude 设定响应模型的json信息中需要过滤哪些字段
response_model_include_unset 设定不返回响应模型的json信息中没有值的字段
response_model_include_defaults 设定不返回响应模型的json信息中有默认值的字段
response_model_exclude_none 设定不返回响应模型的json信息中值为none的字段

3.2.2 路由URL匹配

URL地址是绑定路径装饰器和对应视图函数(同步函数或协程函数)的纽带。

1️⃣多重URL地址绑定函数

RESTful API常用的http方法主要有如下几个:

❑ GET:读取数据库信息

❑ POST:创建新数据

❑ PUT:更新已有数据

❑ PATCH:更新数据,通常仅更新部分数据

❑ DELETE:删除数据信息

在项目开发过程中,一个试图函数需要同时支持多个请求地址的访问,则需要使用多个装饰器(多个URL地址)来装饰绑定视图函数。

复制代码
# 使用app实例对象来装饰实现路由注册

@app.get('/',response_class = JSONResponse)
@app.get('/index',response_class = JSONResponse)
@app.get('/index',response_class = JSONResponse)
@app.get('/app/hello',tags = ['app实例对象注册接口-示例'])
def index():
		return {"Hello","app api"}

2️⃣同一个URL下的动态和静态路由优先级

从访问URL的可变性角度,可以把路由划分为静态路由(固定请求URL)和动态路由(可变请求URL)。如果动态和静态路由URL同时存在,那么请求访问的优先级是怎么样的?

复制代码
from fastapi imort FastAPI

app = FastAPI()

# 动态路由
@app.get('/user/{userid}')
async def login(userid:str):
		return {"Hello":"dynamic"}
		
# 静态路由
@app.get('/user/{userid}')
async def login():
		return {"Hello":"static"}

上述代码,定义了2个路由,他们URL地址几乎一样,区别在于URL地址中的useid参数,一个是动态路由中的动态参数,另一个是静态路由中的路径参数的一部分。启动服务后,同时访问http://127.0.0.1:8000/user/userid,则此时的路由访问原则是:谁先注册就优先访问谁。最终结果: {"Hello":"dynamic"}。调换注册顺序的话,此时会显示:{"Hello":"static"}。

3️⃣一个URL配置多个http请求地址

复制代码
#!/usr/bin/evn python
# -*- coding: utf-8 -*-

from fastapi import FastAPI
from fastapi import APIRouter
from starlette.responses import JSONResponse

app = FastAPI(routes=None)


# ============一个URL配置多个HTTP请求方法==========
# =========================================
@app.api_route(path="/index", methods=["GET", "POST"])
async def index():
    return {"index": "index"}


async def index2():
    return JSONResponse({"index": "index"})


app.add_api_route(path="/index2", endpoint=index2, methods=["GET", "POST"])

router_uesr = APIRouter(prefix="/user", tags=["用户模块"])


@router_uesr.get("/user/login")
def user_login():
    return {"ok": "登入成功!"}


@router_uesr.api_route("/user/api/login", methods=['GET', 'POST'])
def user_api_route_login():
    return {"ok": "登入成功!"}


def add_user_api_route_login():
    return {"ok": "登入成功!"}


router_uesr.add_api_route("/user/add/api/login", methods=['GET', 'POST'], endpoint=add_user_api_route_login)
app.include_router(router_uesr)

if __name__ == "__main__":
    import uvicorn
    import os

    app_modeel_name = os.path.basename(__file__).replace(".py", "")
    print(app_modeel_name)
    uvicorn.run(f"{app_modeel_name}:app", host='127.0.0.1', reload=True)

3.2.3 基于APIRouter实例的路由注册

API端点路由注册方式三种:

❑ 基于app实例对象提供的装饰器或函数进行注册

❑ 基于FastAPI提供的APIRouter类的实例对象提供的装饰器或函数进行注册

❑ 通过直接实例化APIRoute对象且添加的方式进行注册

⚠️

这里的APIRouter和APIRoute是不一样。APIRouter主要用于定义路由组,可以理解为一个路由组的根路由,而APIRoute则表示具体路由节点对象。APIRouter可以实现的功能类似flask框架中提供的一个蓝图模式的加载。

1️⃣路由注册方式

基于APIRouter的实例对象实现路由注册,本质上是向路由中添加子路由(也就是所谓的蓝图模式)

复制代码
from fastapi import FastAPI, APIRouter

app = FastAPI()

# 创建路由分组
router_user = APIRouter(prefix='/user', tags=['用户模块'])
router_pay = APIRouter(prefix='/pay', tags=['支付模块'])

# 定义路由
@router_user.get("/login")
def user_login():
    return {"ok": "登陆成功!"}

@router_pay.get("/order")
def pay_order():
    return {"ok": "订单支付成功!"}

# 添加路由分组
app.include_router(router_user)
app.include_router(router_pay)

if __name__ == "__main__":
    import uvicorn
    import os

    app_model_name = os.path.basename(__file__).replace(".py", "")
    print(app_model_name)
    uvicorn.run(f"{app_model_name}:app", host='127.0.0.1', reload=True)

参数说明:

❑ prefix:当前整个全局路由对象请求URL地址前缀

❑ tags:API分组归属标签

2️⃣给路由配置多个http请求方法

复制代码
from fastapi import FastAPI, APIRouter

app = FastAPI()

# 创建路由分组
router_user = APIRouter(prefix='/user', tags=['用户模块'])
router_pay = APIRouter(prefix='/pay', tags=['支付模块'])

# 定义路由
@router_user.get("/login")
def user_login():
    return {"ok": "登陆成功!"}

@router_user.api_route("/user/api/login",methods=['GET','POST'])
def user_api_route_login():
    return {"ok": "登陆成功!"}

def add_user_api_router_login():
    return {"ok": "登陆成功!"}

router_user.add_api_route("/user/add/api/login",methods=['GET','POST'],endpoint=add_user_api_router_login)


@router_pay.get("/order")
def pay_order():
    return {"ok": "订单支付成功!"}

# 添加路由分组
app.include_router(router_user)
app.include_router(router_pay)

if __name__ == "__main__":
    import uvicorn
    import os

    app_model_name = os.path.basename(__file__).replace(".py", "")
    print(app_model_name)
    uvicorn.run(f"{app_model_name}:app", host='127.0.0.1', reload=True)

3.3 异步和同步API端点路由

3.3.1 同步API端点路由

同步路由:当使用URL地址绑定的关联视图函数是一个同步函数(使用def定义的函数)时,就把这个绑定过程理解为一个同步路由注册的过程。

复制代码
#!/usr/bin/evn python
# -*- coding: utf-8 -*-

from fastapi import FastAPI
import threading
import time
import asyncio
app = FastAPI(routes=None)

@app.get(path="/sync")
def syncdef():
    time.sleep(10)
    print("当前普通函数运行的线程ID:",threading.current_thread().ident)
    return {"index": "sync"}




if __name__ == "__main__":
    import uvicorn
    import os

    app_modeel_name = os.path.basename(__file__).replace(".py", "")
    print(app_modeel_name)
    uvicorn.run(f"{app_modeel_name}:app", host='127.0.0.1', reload=True)

从上面的输出结果看到,当发起多个请求时,每个请求的执行所执行所产生的线程ID都是不一样的。同步路由的并发处理机制是基于多线程方式来实现的。

3.3.2 异步API端点路由

异步路由:当使用URL地址绑定的视图函数是一个协程函数(使用async def定义的函数),就把这个绑定过程理解为一个异步路由注册的过程

复制代码
#!/usr/bin/evn python
# -*- coding: utf-8 -*-

from fastapi import FastAPI
import threading
import time
import asyncio
app = FastAPI(routes=None)

@app.get(path="/async")
async def asyncdef():
    await asyncio.sleep(10)
    print("当前协程运行的线程ID:", threading.current_thread().ident)
    return {"index": "async"}


if __name__ == "__main__":
    import uvicorn
    import os

    app_modeel_name = os.path.basename(__file__).replace(".py", "")
    print(app_modeel_name)
    uvicorn.run(f"{app_modeel_name}:app", host='127.0.0.1', reload=True)

从输出结果可以看出:当发起多个访问请求时,每个请求的执行都运行在同一个线程内。所有可以理解每个请求本质上都是运行在同一个线程的循环事件中,所有他们的线程id是一样的,也说明了异步路由的并发处理机制是基于单线程方式运行的。

⚠️

不建议在异步操作使用同步函数,因为在一个线程中执行同步函数时肯定会引发阻塞。比如一般不会在异步协程函数中使用time.sleep(),而是使用await asyncio.sleep()。


3.4 多应用挂载

3.4.1 主从应用挂载

主从应用挂载子应用:app独立管理;个字所属API交互式文档是独立的,可以分开访问。具体挂载步骤:

复制代码
#!/usr/bin/evn python
# -*- coding: utf-8 -*-

from fastapi import FastAPI
from fastapi.responses import JSONResponse

# 1️⃣创建主app应用对象实例,注册所属的路由信息
app = FastAPI(title='主应用',description="我是主应用文档的描述",version="v1.0.0")
@app.get('/index',summary='首页')
async def index():
    return JSONResponse({"index": "我是属于主应用的接口!"})

# 2️⃣创建子app应用对象实例,注册所属的路由信息
subapp = FastAPI(title='子应用',description="我是子应用文档的描述",version="v1.0.0")
@subapp.get('/index',summary='首页')
async def index():
    return JSONResponse({"index": "我是属于子应用的接口!"})

# 3️⃣通过调用app.mout(subapp)进行主应用挂载子应用关联,设置子应用请求URL地址为/subapp
app.mount(path='/subapp',app=subapp,name='subapp')


if __name__ == "__main__":
    import uvicorn
    import os

    app_modeel_name = os.path.basename(__file__).replace(".py", "")
    print(app_modeel_name)
    uvicorn.run(f"{app_modeel_name}:app", host='127.0.0.1', reload=True)

4️⃣启动服务,分别查看主应用和子应用的独立文档

❑ 主应用交互式文档:http://127.0.0.1:8000/docs

❑ 子应用交互式文档:http://127.0.0.1:8000/subapp/docs


3.4.2 挂载其他wsgi应用

如果已经开发了wsgi应用程序(如flask或者Django),也可以通过FastAPI无缝进行挂载关联,这样就可以通过FastAPI部署来启动WSGI应用程序。FastAPI提供一个名叫WSGI Middlerware中间件,通过他可以挂载WSGI应用程序。

1️⃣ > pip install flask

复制代码
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from fastapi import FastAPI
from fastapi.responses import JSONResponse
from starlette.middleware.wsgi import WSGIMiddleware


# 2️⃣创建FastAPI主应用,注册所属的路由信息
app = FastAPI(title='主应用', description="我是主应用文档的描述", version="v1.0.0")

@app.get('/index', summary='首页')
async def index():
    return JSONResponse({"index": "我是属于FastAPI应用的接口!"}, media_type="application/json; charset=utf-8")

# 3️⃣创建Flask子应用,注册所属的路由信息
from flask import Flask, Response  # 使用 Response 来返回原始 JSON 字符串
flask_app = Flask(__name__)

@flask_app.route('/index')
def flask_main():
    # 返回原始 JSON 字符串,并设置 Content-Type 头
    return Response('{"index": "我是属于flask_app应用的接口!"}', mimetype='application/json; charset=utf-8')

# 4️⃣进行挂载关联,设置子应用请求URL地址为/flaskapp
app.mount(path='/flaskapp', app=WSGIMiddleware(flask_app), name='flask_app')

if __name__ == "__main__":
    import uvicorn
    import os

    app_model_name = os.path.basename(__file__).replace(".py", "")
    print(app_model_name)
    uvicorn.run(f"{app_model_name}:app", host='127.0.0.1', reload=True)

5️⃣启动服务,分别访问不同的路由地址,最终不同的请求会分发到不同路由上,且会由不同的框架进行处理并返回最终数据。

访问:http://127.0.0.1:8000/index

访问:http://127.0.0.1:8000/flaskapp/index


3.5 自定义配置swagger_ui

swagger-ui.css和swagger-ui-bundle.js资源是从第三方的cdn服务商上加载的,第三方服务cdn服务出现问题会导致无法加载可视化API文档界面。为避免这种情况,可以自定义(或改造)并渲染html模版中的一些变量,让swagger-ui.css和swagger-ui-bundle.js等静态资源从本地进行加载。

复制代码
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pathlib
from fastapi import FastAPI
from fastapi.openapi.docs import (
    get_redoc_html,
    get_swagger_ui_html,
    get_swagger_ui_oauth2_redirect_html,
)
from fastapi.staticfiles import StaticFiles

app = FastAPI(docs_url=None)

# 确保静态文件目录存在
static_dir = pathlib.Path(__file__).parent / "static"
if not static_dir.exists():
    static_dir.mkdir()

app.mount("/localswagger/static", StaticFiles(directory=str(static_dir)), name="static")

@app.get('/docs', include_in_schema=False)
async def custom_swagger_ui_html():
    return get_swagger_ui_html(
        openapi_url=app.openapi_url,
        title=app.title + " - Swagger UI",
        oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
        swagger_js_url="/localswagger/static/swagger-ui-bundle.js",
        swagger_css_url="/localswagger/static/swagger-ui.css",
        swagger_favicon_url="https://fastapi.tiangolo.com/img/favicon.png"
    )

if __name__ == "__main__":
    import uvicorn
    import os

    app_model_name = os.path.basename(__file__).replace(".py", "")
    print(app_model_name)
    uvicorn.run(f"{app_model_name}:app", host='127.0.0.1', reload=True)



3.6 应用配置信息读取

3.6.1 基于文件读取配置参数

1️⃣定义配置文件con.ini的内容

复制代码
[fastapi_config]
debug = True
title = "FastAPI"
description = "FastAPI文档明细描述"
version = v1.0.0

[redis]
ip = 127.0.0.1
port = 6379
password = 123456

2️⃣导入configparser模块解析配置文件,读取制定配置参数信息

复制代码
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
from fastapi import FastAPI
import configparser

# 获取当前脚本的目录
current_dir = os.path.dirname(os.path.abspath(__file__))
config_path = os.path.join(current_dir, 'conf.ini')

# 检查配置文件是否存在
if not os.path.exists(config_path):
    raise FileNotFoundError(f"配置文件 {config_path} 不存在")

# 加载配置文件
config = configparser.ConfigParser()
config.read(config_path, encoding='utf-8')

# 检查配置文件中是否存在 [fastapi_config] 部分
if not config.has_section('fastapi_config'):
    raise configparser.NoSectionError("配置文件中不存在 [fastapi_config] 部分")

app = FastAPI(
    debug=config.getboolean('fastapi_config', 'debug'),
    title=config.get('fastapi_config', 'title'),
    description=config.get('fastapi_config', 'description'),
    version=config.get('fastapi_config', 'version'),
)

if __name__ == "__main__":
    import uvicorn
    import os

    app_model_name = os.path.basename(__file__).replace(".py", "")
    print(app_model_name)
    uvicorn.run(f"{app_model_name}:app", host='127.0.0.1', reload=True)

3.6.2 基于pydantic和.env环境变量读取配置参数

1️⃣安装读取环境变量的依赖包

复制代码
pip install python-dotenv

2️⃣定义配置文件.env内容

复制代码
DEBUG=true
TITLE="FastAPI"
DESCRIPTION="FastAPI文档明细描述"
vERSION="v1.0.0"

3️⃣定义继承于BaseSettings模型的Settings子类

复制代码
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from typing import Optional
from fastapi import FastAPI
from pydantic_settings import BaseSettings
from pydantic import field_validator
from functools import lru_cache

class Settings(BaseSettings):
    debug: bool = False
    title: str
    description: str
    version: str

    class Config:
        env_file = ".env"
        env_file_encoding = 'utf-8'

    @field_validator("version", mode='before')
    def version_len_check(cls, v: str) -> Optional[str]:
        if v and len(v) == 0:
            return None
        return v

@lru_cache()
def get_settings():
    return Settings()

# 4️⃣创建Settings实例对象,完整.env文件解析
settings = get_settings()
print(settings.debug)
print(settings.title)
print(settings.description)
print(settings.version)

app = FastAPI(
    debug=settings.debug,
    title=settings.title,
    description=settings.description,
    version=settings.version,
)

if __name__ == "__main__":
    import uvicorn
    import os

    app_model_name = os.path.basename(__file__).replace(".py", "")
    print(app_model_name)
    uvicorn.run(f"{app_model_name}:app", host='127.0.0.1', reload=True)

3.6.3 给配置读取加上缓存

系统中用到的配置参数只会读取一次,如果每次调用参数都要进行一次Settings对象实例化,则可能引发性能问题(当然也可以进行单例的实现)。对于非单例模式的实例对象,可以通过添加缓存的方式来避免多次实例化,进而提高整体性能

复制代码
from functools import lru_cache
@lru_cache()
def get_settings():
    return Settings()

app = FastAPI(
    debug=settings.debug,
    title=settings.title,
    description=settings.description,
    version=settings.version,
)

通过导入functools模块的lru_cache装饰器,完成了Settings对象缓存处理。


3.7 API端点路由函数参数

项目开发过程中,用户一般需要通过提交参数来访问API,但是对于API来说,用户输入的信息永远是不可靠的,所以需要对用户输入的参数进行校验,比如字段参数类型是否一致、字段的长度不能超过多少等。参数的校验库比较多,如以下常用的:

参数 说明
WTForms 支持多个Web框架的Form组件,主要用于对用户请求数据进行验证
valideer 轻量级、可扩展的数据验证和适配库
validators 验证库
cerberus 用于python的轻量级和可扩展的数据验证库
colander 用于对xml、json、html以及其他同样简单的序列化数据进行校验和反序列化的库
jsonschema 用来标记和校验json数据,可在自动化测试中验证json的整体结构和字段类型
schematics 一个python库,用于将类型组合到结构中并验证他们,然后根据简单的描述转换数据的形状
voluptuous 主要用于验证以json、yaml等形式传入python的数据

3.7.1 路径操作及路径函数

复制代码
from typing import List
from fastapi import FastAPI, Query, Path
from starlette import status
from enum import Enum

app = FastAPI()


@app.post("/parameter/", summary='我是路径参数', status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)
async def parameter(q: List[str] = Query(["test1", "test2"])):
    return {
        'message': q,
    }

@app.post() 表示API端点的路径操作,也就是API装饰器 ,他里面的传输参数则可以理解为路径操作函数 ;async def parameter()表示视图函数,该视图函数可以是同步函数,也可以是协程函数,其中需要传入参数表示路径函数参数,这里叫做视图函数参数

3.7.2 path 参数

1️⃣路径参数变量的声明和获取

路径参数通常是指URL地址中可变的参数

复制代码
@app.get("/user/{user_id}/article/{article_id}")
async def callback(user_id: int, article_id: str):
    return {
        'user_id': user_id,
        'article_id': article_id
    }

user_id和article_id两个路径参数 变量,FastAPI会自动把这两个参数传递到视图函数上。在视图函数中,对这两个路径参数的要求如下:

❑ user_id:int 类型,是必填项。如果传递过程中没有这个参数,则会抛出请求参数校验异常。

❑ article_id:字符串类型,是必填项。如果传递过程中没有这个参数,则会抛出请求参数校验异常。

2️⃣带"/"的关键子路径参数

传入路径参数是一种文件类型的路径,且要求把该变量值传入URL路径中,那么URL会识别出多重路径。

复制代码
@app.get("/uls/{file_path}")
async def callback_file_path(file_path: str):
    return {
        'file_path': file_path
    }

http://127.0.0.1:8000/user/s1234/article/xiaoxiao

如果此时提交file_path变量值为file/1.txt/,如访问http://127.0.0.1:8000/uls//file/1.txt,则会出现404错误,无法匹配到正确的路由地址。此时只有把路径变量定义为Path类型,才能进行正确处理。

复制代码
@app.get("/uls/{file_path:path}")
async def callback_file_path_2(file_path: str):
    return {
        'file_path': file_path
    }

上面的代码,路径参数为file_path:path。其中path表示该参数应匹配的任意路径,也可以理解为一种对路径的转换机制。此时访问http://127.0.0.1:8000/uls//file/1.txt,就能正常返回最终的处理结果

3️⃣枚举预设路径参数值

复制代码
from fastapi import FastAPI, Query, Path
from enum import Enum
class ModelName(str, Enum):
    name1 = "name1"
    name2 = "name2"
    name3 = "name3"


@app.get("/model/{model_name}")
async def get_model(model_name: ModelName):
    if model_name == ModelName.name1:
        return {"model_name": model_name, "message": "ok!"}
    if model_name.value == "name2":
        return {"model_name": model_name, "message": "name2 ok!"}
    return {"model_name": model_name, "message": "fail!"}

4️⃣路径参数的更多条件校验

需要对单独的一个参数进行多维度校验,则单纯采用上文的声明无法完成。

复制代码
@app.get("/pay/{user_id}/article/{article_id}")
async def callback(user_id: int = Path(..., title="用户ID", description='用户ID信息', ge=10000),
                         article_id: str = Path(..., title="文章ID", description='用户所属文章ID信息', min_length=1,
                                                max_length=50)):
    return {
        'user_id': user_id,
        'article_id': article_id
    }

在上面的代码中,在视图函数中声明了两个path参数。

❑ user_id:int类型参数

❑ ...:表示user_id是一个必填项

❑ title:表示参数显示在API交互式文档中的标题名称

❑ description:表示参数显示在API交互式文档中的详细描述

❑ ge=10000:表示参数传值需要大于或等于10000。如不满足,则会抛出请求参数校验异常

❑ article_id:字符串类型参数

❑ min_length=1:表示参数传值需要字符串长度大于1。如不满足,则会抛出请求参数校验异常

❑ max_length=50:表示参数传值需要字符串长度小于50。如不满足,则会抛出请求参数校验异常

5️⃣路径参数必填值说明

⚠️路径参数的定义------任何路径参数值都应该声明为必填项;若将路径参数声明为非必填项,如user_id:int=Path(default=None),则这个声明不会起作用,因为它还是一个路径参数。

6️⃣参数声明顺序问题

在视图函数中,声明的参数氛围默认值和无默认值两种。如果有默认值参数放在无默认值参数的前面,那么ide会提示告警,并且无法启动服务。坚持吧有默认参数放在最前面,则需要在第一个参数前面加一个*。

复制代码
@app.get("/items/{item_id}")
async def callback(*,item_id: int = Path(...,), q: str):
    return 'OK'

此时,FastAPI框架会将*之后的参数自动识别为关键字参数(键值对),也就是**kwargs,并且他不会在意之后对参数是否有默认值。

3.7.3 Query参数

1️⃣查询参数必选和可选

复制代码
@app.get('/query')
async def read_item(user_id:int,user_name:Optional[str]=None,user_token:str = 'token'):
    return{
        'user_id':user_id,
        'user_name':user_name,
        "user_token":user_token
    }

在视图函数中声明查询的参数说明如下:

❑ user_id:int类型参数,是必填项,如果没有这个参数的传递,则会抛出请求参数校验异常。

❑ user_name:字符串类型参数,是可选项,如果没有这个参数,则不会出发任何校验异常。Optional的主要作用是参数值类型提示,方便IDE识别提示。Optional[str]=None表示该参数要么是字符串类型,要么是None类型

❑ user_token:字符串类型参数,它的默认值为"token"

2️⃣bool类型参数转换

❑ isbool=true/false

❑ isbool=1/0(1表示true,0表示false)

❑ isbool=on/off(on表示true,off表示false)

当请求地址为以下几种时得到的结果是一样的:

http://127.0.0.1:8000/query/bool/?isbool=true

http://127.0.0.1:8000/query/bool/?isbool=1

http://127.0.0.1:8000/query/bool/?isbool=on

3.7.4 Body参数

Body(请求体)参数表示在HTTP中提交请求体的数据,它既可以是某种文档类型的数据,也可以是文件类型或是表单类的数据。常见的请求体参数传递都是JSON格式的,如果是JSON格式的数据,则通常要求提交请求头字段Content-Type的格式为如下形式。

FastAPI框架提供了如下3种方式来接收Body参数,并自动把JSON格式的字符串转换为dict格式。

❑ 引入Pydantic模型来声明请求体并进行绑定。

❑ 直接通过Request对象获取Body的函数。

❑ 使用Body类来定义

1️⃣pydantic模型声明请求体

复制代码
# 步骤1:创建对应的body数据模型
from pydantic import BaseModel
from typing import Optional

class Item(BaseModel):
    user_id: str
    token: str
    timestamp: str
    article_id: Optional[str] = None

# 步骤2:把模型绑定到视图函数中

@app.post("/action/")
def callback(item: Item):
    return {
        'user_id': item.user_id,
        'article_id': item.article_id,
        'token': item.token,
        'timestamp': item.timestamp
    }

完成以上两个步骤后,完成上面两个步骤之后,FastAPI框架会自动处理以下几种情况。

❑ 将请求体参数识别为JSON格式字符串,并自动将字段转换为相应的数据类型。

❑ 自动进行参数规则的校验。如果校验失败,则响应报文内容会自动返回一个错误,并准确指出错误数据的位置和信息。

❑ 为模型生成JSON Schema定义,并显示在API交互式文档中,Schema会成为OpenAPI Schema的一部分。

❑ 在函数内部,可以直接访问模型对象的所有属性。

2️⃣单值Request Body字段参数定义

复制代码
@app.post("/action/body")
def callbackbody(
        token: str = Body(...),
        user_id: int = Body(..., gt=10),
        timestamp: str = Body(...),
        article_id: str = Body(default=None),
):
    return {
        'user_id': user_id,
        'article_id': article_id,
        'token': token,
        'timestamp': timestamp
    }

❑ token、user_id、timestamp都是必填的字段。

❑ user_id参数值需要大于或等于10。

❑ article_id是可选的字段。

可以将所有body参数都设置为可选

复制代码
@app.post("/action/body2")
def callbackbody(
        token: str = Body(default=None),
        user_id: int = Body(default=None, gt=10),
        timestamp: str = Body(default=None),
        article_id: str = Body(default=None),
):
    return {
        'user_id': user_id,
        'article_id': article_id,
        'token': token,
        'timestamp': timestamp
    }

此时body参数为非必填项。

3️⃣Request Body中的embed参数

复制代码
class Itement(BaseModel):
    user_id: int = Body(..., gt=10, embed=True)
    token: str
    timestamp: str
    article_id: Optional[str] = None


@app.post("/action/body3")
def callbackbody(item: Itement = Body(default=None, embed=False)):
    return {
        'body': item
    }

❑ default=None表示这个模型作为请求体提交时是一个可选的字段,此时不会校验这个模型是否存在,尽管模"型类中的user_id、token、timestamp等字段都是必填项,但还是会忽略对模型类中必填参数的校验。

embed=False 表示item参数字段名不会成为请求体的一部分,如下图

embed=True 表示item参数字段名会成为请求体的一部分,如下图

4️⃣多个Request Body参数

复杂请求体数据,有可能有嵌套要求,请求体格式要求如下:

复制代码
{
    "item":{
        "name":"苹果",
        "description":"苹果手机"
        "price":4212.0,
        "tax":3.2
    },
    "user":{
        "username":"xiaoxiao",
        "full_name":"xiaozhong tongxue"
    }
}

class ItemUser(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None


class User(BaseModel):
    username: str
    full_name: str = None


@app.put("/items/")
async def update_item1111(item: ItemUser, user: User):
    results = {"item": item, "user": user}
    return results

上面多个模型声明在API交互式文档中显示结果:

5️⃣多个模型类和单个Request Body

请求体不仅存在多个Request Body,而且存在单一Request Body的字段信息,

复制代码
{
    "item":{
        "name":"Foo",
        "description":"The pretender"
        "price":42.0,
        "tax":3.2
    },
    "user":{
        "username":"dave",
        "full_name":"Dave "
    },
    "importance":5
}

@app.put("/items/")
async def update_item1111(item: ItemUser, user: User):
    results = {"item": item, "user": user}
    return results

3.7.5 Form数据和文件处理

表单数据默认使用POST的方式提交到API上,由于表单数据的传输过程使用一种"特殊"编码,所以与JSON不同,设置Content-Type格式的对应要求也不同。对于表单数据的传输,通常要求提交请求头字段Content-Type的方式是application/x-www-form-urlencoded,这也是默认的方式。下面介绍如何在FastAPI框架中对Form参数进行解析读取。

FastAPI中提供的Form类是基于Body模块扩展而来的,所以它的用法和Body是一样的。

复制代码
rom fastapi import FastAPI, Query, Path, Body, Form, File, UploadFile
from pydantic.main import BaseModel
from starlette import status
from enum import Enum
from fastapi import Depends

app = FastAPI()

@app.post("/demo/login/")
async def login(username: str = Form(...,title="用户名",description="用户名字段描述", max_length=5),
                 password: str = Form(...,title="用户密码",description="用户密码字段描述", max_length=20)):
    return {"username": username, "password": password}

File bytes 方式接受文件上传

复制代码
@app.post("/sync_file",summary='File形式的-单文件上传')
def sync_file(file: bytes = File(...)):
    '''
    基于使用File类 文件内容会以bytes的形式读入内存通常主要用于上传小的文件
    :param file:
    :return:
    '''
    with open('./data.bat', 'wb') as f:
        f.write(file)
    return {'file_size': len(file)}


@app.post("/async_file",summary='File形式的-单文件上传')
async def async_file(file: bytes = File(...)):
    '''
    基于使用File类 使用异步的方式进行文件接收处理
    :param file:
    :return:
    '''
    # 异步方式执行with操作,修改为 async with
    async with aiofiles.open("./data.bat", "wb") as fp:
        await fp.write(file)
    return {'file_size': len(file)}

可以看到定义的两个视图函数,一个是同步类型的sync_file()视图函数,另一个是异步类型的async_file()视图函数,与它们对应的视图函数都用file: bytes=File参数声明具体要接收的文件。需要注意,File参数接收到的内容是文件字节,它会将整个内容读取并存储到内存中,所以它主要用在上传小文件的场景中。

复制代码
@app.post("/uploadfiles",summary='UploadFile形式的-单文件上传')
async def uploadfiles(file: UploadFile = File(...)):
    result = {
        "filename": file.filename,
        "content-type": file.content_type,
    }
    content = await file.read()
    with open(f"./{file.filename}", 'wb') as f:
        f.write(content)
    return result

与bytes对比,UploadFile具有以下几点优势。

❑ 使用UploadFile进行文件读取时,所获得的数据存储在内存中,当占用的内存达到阈值后将被保存在磁盘中。这种读取方式更适用于大图片、视频等大文件的上传处理。

❑ UploadFile对象包含很多文件元数据,如文件名、文件类型等。

❑ 有文件对象的异步接口,可以对文件对象进行write()、read()、seek()和close()等操作。

相关推荐
浠寒AI13 小时前
FastAPI核心解密:深入“路径操作”与HTTP方法,构建API的坚实骨架
网络协议·http·fastapi
Python私教3 天前
FastAPI 与 JWT 身份验证:保护你的 API
网络·fastapi
白嫖不白嫖4 天前
Django、Flask、FastAPI与Jupyter对比
django·flask·fastapi
一刀到底2114 天前
Python 高级应用10:在python 大型项目中 FastAPI 和 Django 的相互配合
python·django·fastapi
fengbingchun4 天前
线性规划饮食问题求解:FastAPI作为服务端+libhv作为客户端实现
fastapi·libhv·pyomo
zhangsan09334 天前
web框架(Django 与 FastAPI)
django·fastapi
jingyucsdn5 天前
网页端 VUE+C#/FastAPI获取客户端IP和hostname
网络协议·tcp/ip·fastapi
掘金-我是哪吒7 天前
分布式微服务系统架构第144集:FastAPI全栈开发教育系统
分布式·微服务·架构·系统架构·fastapi
jingyucsdn13 天前
AsyncIOScheduler与BackgroundScheduler的线程模型对比
fastapi