【python】FastAPI7之APIRouter与请求响应的进阶用法(七)

10-1 APIRouter基本使用【重点】

场景:

如果我们写一个网站,或者写一个APP,那整个项目是比较复杂的,此时不应该把所有代码放在一个文件中。

前面我们通过把代码拆分到不同文件的方式,可以解决一些代码混乱的问题,但是却不能更好的解决。

比如一个项目中可能含有不同的模块,那不同的模块应该分开管理,这样项目才便于维护和管理。。

FastAPI的解决方式

APIRouter就是FastAPI为了此需求场景提供了一种解决方式,它类似 Flask中的蓝图Django中的app

示例1:APIRouter的基本使用

  • main.py 非常简洁,主要负责注册管理
  • 使用 app.include_router(blog.router, prefix="/blog", tags=["Blog"]) 注册模块,同时可以指定及api的前缀(prefix)和标签(tags)
1
2
3
4
5
6
from fastapi import FastAPI
from routers import blog, user

app = FastAPI()
app.include_router(blog.router, prefix="/blog", tags=["Blog"])
app.include_router(user.router)
  • routers/blog.py 主要负责和blog模块有关的业务
  • router = APIRouter()得到的 router对象和main.py中的app对象使用方式一样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from fastapi import APIRouter
router = APIRouter()

@router.get("/blogs")
def blogs():
return [{"id": i, "title": f"blog{i}"} for i in range(10)]

@router.get("/{blog_id}")
def get_blog(blog_id: int):
return {"id": blog_id, "title": f"blog{blog_id}"}

@router.delete("/{blog_id}")
def delete(blog_id: int):
return {"code": 1, "msg": f"Done blog_id: {blog_id}"}
  • routers/user.py,负责和用户相关的模块
1
2
3
4
5
6
7
8
9
10
11
from fastapi import APIRouter, Form
router = APIRouter(tags=["User"], prefix="/user")

@router.post("/login")
def login(name: str = Form(), pwd: str = Form()):
return {"name": name}


@router.get("/me")
def info():
return {"info": "this is a cute boy"}
  • 目的:

让所有的API文件指向一个同一个实例化的FastAPI()对象 也就是同一个app对象

11-1 直接使用Request对象

  • 关于请求的操作,比如从URL中提取路径参数,获取查询参数,获取请求头,获取Cookie,获取请求体中的数据;这些参数和值的获取非常方便,这是因为FastAPI帮我们创造便利。

  • FastAPI底层依赖Starlette,本质上是FastAPI帮我们做了一些操作,从Starlette的Request对象解析出上述各个参数。

  • 所以,对于上面这些常用的请求参数,我们可以直接使用FastAPI给我们提供的工具,并且有了数据校验、类型转化、OPenAPI文档等功能。

  • 当然了,你不使用FastAPI提供的便捷工具,直接从Request对象中解析数据也是可以的,但就没由数据校验、类型转化、OPenAPI文档等功能。

  • 不过,有些场景,比如说获取请求的IP,请求的client host等等,那我们就必须直接使用Request对象。

示例1:使用使用Request获取一些常见参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from fastapi import FastAPI, Request
from starlette.requests import Request
app = FastAPI()
@app.get("/{item_id}")
def hello(req: Request):
item_id = req.path_params.get("item_id") # 从请求的路径参数获取item_id
page = req.query_params.get("page", 0) # 从请求的查询参数获取分页信息
size = req.query_params.get("size", 10)
x_token = req.headers.get("x-token") # 从 请求头从获取x-token
x_token_cookie = req.cookies.get("x_token") # 从 请求的cookies从获取x-token

return {
"item_id": item_id,
"page": page,
"size": size,
"x-token": x_token,
"cookie": x_token_cookie,

}

示例2:使用Request获取客户端host

  • 从fastapi导入 Request对象,或者直接从 starlette.requests导入 Request, 两者是一样的效果。
  • 本例中,路径参数 item_id是用的FastAPI提供的便捷方式,所以具有类型转换、类型校验、openapi文档等附加功能。
  • 第二个形参 req 的类型是 Request,那FastAPI就知道要直接从Request中获取参数,就没有上述附加功能。
1
2
3
4
5
6
7
8
9
from fastapi import FastAPI, Request
from starlette.requests import Request
app = FastAPI()
@app.get("/item/{item_id}")
def hello(item_id: int, req: Request):
return {
"item_id": item_id,
"client_host": req.client.host
}

补充:直接从Request中获取请求体参数,需要使用async/await语法,在下一章我们再详细介绍。

11-2 直接使用Response对象

  • 关于响应的操作,在前面的章节有涉及到,比如设置响应状态码,设置响应头,在路径函数内返回数据就可以当做响应体。

  • 这些都是FastAPI给我们提供的便捷方式,使用了这些方式,几乎帮助我们处理了大部分常见的需求。

  • 不过,在一些特殊常场景下,我们需要更加灵活的处理响应。

  • 比如,我们已经使用过了,通过Response对象来设置Cookie, 设置响应头等等。

  • 本质上,FastAPI借用了Starlette框架的Response对象,即我们使用的Response对象都是Starlette的

  • Response对象还有很多其他高进的用法,比如处理重定向、响应其他格式的数据:纯文本/HTML/XML/下载文件等等

示例1:使用Response设置状态码/响应头/cookie

  • 从FastAPI导入的Response,等价于从starlette.responses导入的Response
  • 在路径函数中定义的形参response的类型是Response,FastAPI可以是被出来你要手动使用Response对象
  • 但是,这样的使用方式,本质上依然使用了FastAPI的便捷方式,因为你返回的是”123”,但其实响应格式是”application/json”
  • 这是因为,FastAPI的默认响应方式是JSONResponse,即所有的返回给客户端的数据都会被json序列化
1
2
3
4
5
6
7
8
9
10
11
from fastapi import FastAPI, Response # 等价
from starlette.responses import Response # 等价
app = FastAPI()

@app.get("/")
def hello(response: Response):
response.status_code = 201 # 设置响应码
response.headers["x-token"] = "12345" # 设置响应头
response.set_cookie("token", "111111", 100) # 设置响应cookies

return "123"

示例2:直接返回Response对象

  • Response类初始化对象一般需要四个参数
  • content是响应体的数据,必须是支持编码的字符串。如果要返回的数据是字段,则要使用json序列化
  • status_code 设置响应的状态码
  • headers 设置响应头
  • media_type 设置响应类型,比如说是json格式的数据则值为 application/json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import json
from fastapi import FastAPI, Response # 等价下面的
from starlette.responses import Response # 等价上面的
app = FastAPI()

@app.get("/", )
def hello():
response = Response(
content=json.dumps({"hello": "world"}), # 序列化响应体数据
status_code=201, # 设定响应状态码
headers={"x-token": "qqqqqq"}, # 设定响应头
media_type="application/json" # 设定响应类型
) # response=Response(四个参数) 实例化一个Response对象

return response

11-3 默认响应方式

基于上面我们知道,可以直接返回Response对象,使用起来也比较简单,只需要在实例化对象是按照要求传参即可

但是你会发现,不同类型的响应数据,都需要手动传参,这是比较麻烦的。

其实,FastAPI提供了多种内置的响应方式,比如对于JSON格式的响应数据,提供了JSONResponse这种响应类型。

本质上JSONResponse类是Response这个类的子类,并且JSONResponse是FastAPI中的默认响应方式

示例1:

  • 此时直接返回的是字典,但FastAPI内部帮我们处理,返回的数据格式是json的。
  • 想要修改内置响应方式,可修改FastAPI()中或的APIRouter()中的 default_response_class
  • 还可以对于指定接口,通过 response_class设置响应方式
  • 优先级:接口 > router > app
1
2
3
4
5
6
7
8
9
10
11
from fastapi import FastAPI, APIRouter
from fastapi.responses import JSONResponse


app = FastAPI(default_response_class=JSONResponse) # 设置全局的默认响应方式
router = APIRouter(default_response_class=JSONResponse) # 设置APIRouter所有接口的默认响应方式


@app.get("/", response_class=JSONResponse) # 设置这个接口的默认响应方式
def hello():
return {"id": 1, "name": "liixu"}

示例2:源码阅读

  • JSONResponse是Response的子类,m默认写死 media_type = "application/json"
  • 并重写了render方法,该方法会在示实例化响应对象时触发,用来把我们传进去的 content序列化。
  • 因此使用手动JSONResponse时需要保证content是可序列化的,否则报错。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class JSONResponse(Response):			
media_type = "application/json"

def __init__(
self,
content: typing.Any,
status_code: int = 200,
headers: typing.Optional[dict] = None,
media_type: typing.Optional[str] = None,
background: typing.Optional[BackgroundTask] = None,
) -> None:
super().__init__(content, status_code, headers, media_type, background)
# 重写父类方法
def render(self, content: typing.Any) -> bytes:
return json.dumps(
content,
ensure_ascii=False,
allow_nan=False,
indent=None,
separators=(",", ":"),
).encode("utf-8")

11-4 文本相关的Response

和文本相关的有两种响应方式:

  • PlainTextResponse

  • HTMLResponse

示例1:PlainTextResponse用来响应纯文本的数据

  • PlainTextResponse是Response的子类,重写了media_type = “text/plain”,对应响应头中的 content-type: text/plain,此时网页上显示纯文本信息。
1
2
3
4
5
6
7
8
9
10
from fastapi import FastAPI
from fastapi.responses import PlainTextResponse
app = FastAPI()

@app.get("/", response_class=PlainTextResponse) # 定义response_class
def main():
return "Hello World"
# 源码
class PlainTextResponse(Response): # 继承
media_type = "text/plain" # 重写属性

示例2:HTMLResponse用来响应HTML页面

  • HTMLResponse是Response的子类,重写media_type = “text/html”
  • 就可以直接返回HTMLResponse对象,网页渲染HTML样式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()
@app.get("/")
def index():
html_content = """
<html>
<head>
<title>Some HTML in here</title>
</head>
<body>
<h1>Look ma! HTML!</h1>
</body>
</html>
"""
return HTMLResponse(content=html_content, status_code=200)
@app.get("/", response_class=HTMLResponse)
def index():
return """
<html>
<head>
<title>Some HTML in here</title>
</head>
<body>
<h1>Look ma! HTML!</h1>
</body>
</html>
"""
# 源码
class HTMLResponse(Response): # 继承
media_type = "text/html"# 重写属性

11-5 下载文件相关的Response

下载文件相关的响应类有两个:

  • StreamingResponse
  • FileResponse

示例1:StreamingResponse支持文件类型的操作,下载文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from fastapi import FastAPI
from fastapi.responses import StreamingResponse

app = FastAPI()


@app.get("/")
def index():
def iterfile(): #
with open("mybook.zip", mode="rb") as f: #
yield from f

return StreamingResponse(iterfile(), media_type="application/zip")
# StreamingResponse也是Response 的子类

示例2:使用FileResponse

  • 更加直接,文件下载
1
2
3
4
5
6
7
8
9
10
from fastapi import FastAPI
from fastapi.responses import FileResponse

app = FastAPI()


@app.get("/")
def index():
return FileResponse("mybook.zip", filename="book.zip") # 第一个参数文件路径,filename指定下载下来的文件名
# FileResponse也是Response 的子类

11-6 其他Response

示例1:重定向Response

1
2
3
4
5
6
7
8
9
from fastapi import FastAPI
from fastapi.responses import RedirectResponse

app = FastAPI()


@app.get("/")
def go_to_baidu():
return RedirectResponse("https://www.baidu.com")

示例2:ORJSONResponse

  • ORJSONResponse是一个基于orjson序列化的响应类,它在性能上要由于JSONResponse
  • 使用是需要安装 pip3 install orjson
1
2
3
4
5
6
7
8
9
from fastapi import FastAPI
from fastapi.responses import ORJSONResponse

app = FastAPI()


@app.get("/", response_class=ORJSONResponse)
def go_to_baidu():
return {"id": 1, "name": "liuxu"}

补充:继承Response, 模仿者其他响应类,定义自己的响应类(自己实现吧)


【python】FastAPI7之APIRouter与请求响应的进阶用法(七)
http://example.com/2024/03/04/687FastAPI7之APIRouter与请求响应的进阶用法/
作者
Wangxiaowang
发布于
2024年3月4日
许可协议