所有代码资料:<https://github.com/L4plAcEee/learning/tree/main/FastApi

此时,幻想乡…
每天都有不计其数(确信)的妖怪前来捐赠
灵梦很苦恼,记账实在太麻烦啦~
此时baka提出了一个想法:“要不整个app用来管理自己的赛钱箱?”
说干就干!

0. 初始化

笨重什么的实在是太不优雅了~
所以说FastAPI是一个好东西

  • FastAPI*
    FastAPI 是一个用于构建 API 的现代、快速(高性能)的 web 框架,专为在 Python 中构建 RESTful API 而设计。
    FastAPI 建立在 Starlette 和 Pydantic 之上,利用类型提示进行数据处理,并自动生成API文档。

我们用pip进行下载:
pip install fastapi # FastAPI 依赖 Python 3.8 及更高版本。

另外我们还需要一个 ASGI 服务器,生产环境可以使用 Uvicorn 或者 Hypercorn:

pip install "uvicorn[standard]"

好简单baka很快就配完了环境,现在是时候开始第一个程序啦

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

app = FastAPI()

@app.get("/")
def say_hello():
    '''
    from fastapi import FastAPI: 这行代码从 fastapi 模块中导入了 FastAPI 类。FastAPI 类是 FastAPI 框架的核心,用于创建 FastAPI 应用程序实例。
    app = FastAPI():这行代码创建了一个 FastAPI 应用实例。与 Flask 不同,FastAPI 不需要传递 __name__ 参数,因为它默认使用当前模块。
    @app.get("/"): 这是一个装饰器,用于告诉 FastAPI 哪个 URL 应该触发下面的函数,并且指定了 HTTP 方法为 GET。在这个例子中,它指定了根 URL(即网站的主页)。
    def say_hello():: 这是定义了一个名为 say_hello 的函数,它将被调用当用户使用 GET 方法访问根 URL 时。
    return {"Hello": "reimu"}: 这行代码是 say_hello 函数的返回值。当用户使用 GET 方法访问根 URL 时,这个 JSON 对象将被发送回用户的浏览器或 API 客户端。
    '''
    return {"hello": "reimu"} # 会返回一个JSON

在命令行中运行以下命令以启动应用:
uvicorn main:app --reload

1
{"hello": "reimu"}

在根路由下,应该能看到这个

1. FastAPI 交互式 API 文档

FastAPI 提供了内置的交互式 API 文档,使开发者能够轻松了解和测试 API 的各个端点。
这个文档是自动生成的,基于 OpenAPI 规范,支持 Swagger UI 和 ReDoc 两种交互式界面。
通过 FastAPI 的交互式 API 文档,开发者能够更轻松地理解和使用 API,提高开发效率
在运行 FastAPI 应用时,Uvicorn 同时启动了交互式 API 文档服务。
默认情况下,你可以通过访问 http://127.0.0.1:8000/docs 来打开 Swagger UI 风格的文档。

2. 定义实体

baka已经定义了一个基本的根路由验证了FastAPI的正常运行~
但是不清楚模型怎么行!
现在是时候搭建一个最基本的模型啦!

  • FastAPI Pydantic 模型
    Pydantic 是一个用于数据验证和序列化的 Python 模型库。
    它在 FastAPI 中广泛使用,用于定义请求体、响应体和其他数据模型,提供了强大的类型检查和自动文档生成功能。

FastAPI内置且推荐使用Pydantic构建模型:
使用 Pydantic 定义一个模型非常简单,只需创建一个继承自 pydantic.BaseModel 的类,并在其中定义字段。字段的类型可以是任何有效的 Python 类型,也可以是 Pydantic 内置的类型。

1
2
3
4
5
6
7
class Donation(BaseModel):
    id: int
    signature: str
    donation_amount: Decimal
    # 在 Python 类型提示(Type Hints)中,Optional 是一个特殊类型注解,
    # 用于表示某个值可以是指定类型,也可以是 None。它来自 typing 模块,是 Python 3.5+ 引入的类型系统的重要组成。
    description: Optional[str] = None

在以上例子中,我们会在每次回复中携带一个json格式的数据,但是每次也自己手搓也太麻烦了吧!
而且,为了统一回复风格和类型安全,我们更要约定回复风格~
所以baka选择定义了一个APIResponse模型用来约束每次回复的内容:

1
2
3
4
5
class APIResponse(BaseModel):
    code: int
    msg: Optional[str] = None
    # typing.Any允许任何类型,类似动态类型,比Optional[Object]方案更适合
    data: Optional[Any] = None

那么我们每次回复应该就会是这样的结构:

1
2
3
4
5
6
7
{
"code": 200,
"msg": "success",
"data": {
//...
}
}

现在我们来更改一下say_hello()让它符合baka的规范吧!

1
2
3
4
@app.get("/")

def say_hello():
    return APIResponse(code=200, msg='success', data={"hello": "reimu"})

然后我们收到这样了的回复:

1
{"code":200,"msg":"success","data":{"hello":"reimu"}}

成功啦!

3. 定义方法

FastAPI提供了装饰器让我们直接使用http方法构建RestfulAPI端点,非常直观

HTTP 方法(HTTP Methods),也称为 HTTP 动词(HTTP Verbs),用于指定客户端与服务器之间的交互方式。

🟢 常见的 HTTP 方法

方法 作用 是否有请求体 是否有响应体 幂等性 安全性
GET 获取资源 ❌ 无 ✅ 有 ✅ 是 ✅ 是
POST 创建资源 ✅ 有 ✅ 有 ❌ 否 ❌ 否
PUT 更新/替换资源 ✅ 有 ✅ 有 ✅ 是 ❌ 否
PATCH 部分更新资源 ✅ 有 ✅ 有 ❌ 否 ❌ 否
DELETE 删除资源 ❌ 通常无 ✅ 可有 ✅ 是 ❌ 否

🟡 较少使用的 HTTP 方法

方法 作用 是否有请求体 是否有响应体 幂等性 安全性
HEAD 获取资源的头部信息(不返回正文) ❌ 无 ❌ 无 ✅ 是 ✅ 是
OPTIONS 获取服务器支持的 HTTP 方法 ❌ 无 ✅ 有 ✅ 是 ✅ 是
TRACE 追踪请求(通常用于调试) ❌ 无 ✅ 有 ✅ 是 ❌ 否
CONNECT 建立 TCP/IP 隧道(通常用于 HTTPS 代理) ✅ 有 ✅ 有 ❌ 否 ❌ 否

定义路由

现在我们就根据http方法定义一些路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@app.get("/donation/{id}")
def reimu_get(id: int):
   
    return
@app.post()
def reimu_post():
   
    return
@app.put()
def reimu_put():
   
    return
@app.patch()
def reimu_patch():
   
    return
@app.delete()
def reimu_delete(item_id: int):
   
    return
@app.options()
def func_option():
   
    return

baka累了,想休息,先写个TODO挂着吧)

4. 定义数据源

因为之前baka已经定义了对应的模型,所以我们直接存到列表里模拟数据库,
然后使用random库随机生成测试数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
data: list[Donation] = []
def init_data(num_records: int = 10):
    """ 初始化随机 Donation 记录 """
    global data
    data.clear()
    for i in range(1, num_records + 1):
        donation = Donation(
            id=i,
            signature=f"Donor_{random.randint(1000, 9999)}",
            donation_amount=Decimal(random.uniform(10, 1000)).quantize(Decimal("0.01")),
            description=random.choice(["Support education", "Medical aid", "Environmental protection", ""])
        )
        data.append(donation)

5.完善代码

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
from fastapi import FastAPI

from pydantic import BaseModel

from typing import Optional

from typing import Any

from decimal import Decimal

import random




class Donation(BaseModel):

    id: int

    signature: str

    donation_amount: Decimal

    # 在 Python 类型提示(Type Hints)中,Optional 是一个特殊类型注解,

    # 用于表示某个值可以是指定类型,也可以是 None。它来自 typing 模块,是 Python 3.5+ 引入的类型系统的重要组成。

    description: Optional[str] = None



class APIResponse(BaseModel):

    code: int

    msg: Optional[str] = None

    # typing.Any允许任何类型,类似动态类型,比Optional[Object]方案更适合

    data: Optional[Any] = None



data: list[Donation] = []

def init_data(num_records: int = 10):

    """ 初始化随机 Donation 记录 """

    global data

    data.clear()

    for i in range(1, num_records + 1):

        donation = Donation(

            id=i,

            signature=f"Donor_{random.randint(1000, 9999)}",

            donation_amount=Decimal(random.uniform(10, 1000)).quantize(Decimal("0.01")),

            description=random.choice(["芙兰朵露", "十六夜咲夜", "红美玲", ""])

        )

        data.append(donation)



app = FastAPI()

init_data()



@app.get("/")

def say_hello():

    """

    from fastapi import FastAPI: 这行代码从 fastapi 模块中导入了 FastAPI 类。FastAPI 类是 FastAPI 框架的核心,用于创建 FastAPI 应用程序实例。



    app = FastAPI():这行代码创建了一个 FastAPI 应用实例。与 Flask 不同,FastAPI 不需要传递 __name__ 参数,因为它默认使用当前模块。



    @app.get("/"): 这是一个装饰器,用于告诉 FastAPI 哪个 URL 应该触发下面的函数,并且指定了 HTTP 方法为 GET。在这个例子中,它指定了根 URL(即网站的主页)。



    def say_hello():: 这是定义了一个名为 say_hello 的函数,它将被调用当用户使用 GET 方法访问根 URL 时。



    return {"Hello": "reimu"}: 这行代码是 say_hello 函数的返回值。当用户使用 GET 方法访问根 URL 时,这个 JSON 对象将被发送回用户的浏览器或 API 客户端。

    """

    return APIResponse(code=10001, msg="OK", data={"hello": "reimu"})



@app.get("/donation/{id}")

def reimu_get(id: int, q: str):

    """

    FastAPI会自动捕获"/{id}?q='xxx'"中的查询参数并解析成python可直接处理的数据格式

    """

    for it in data:

        if it.id == id or it.signature == q:

            return APIResponse(code=10001, msg="OK", data=it)

    return APIResponse(code=10404, msg="donation record not found")

@app.post("/donation/")

def reimu_post(donation: Donation):

    """

    使用 Pydantic 模型 定义了一个请求体,包含多个字段,其中一些有默认值

    FastAPI会自动处理参数并包装成对应Pydantic模型



    针对Post的特性,应当创建新数据时使用post

    """

    for it in data:

        if it.id == donation.id:

            return APIResponse(code=205, msg="Reset Content")

    data.append(donation)

    return APIResponse(code=10001, msg="OK")

@app.put("/donation/")

def reimu_put(donation: Donation):

    """

    相同的,FastAPI会自动处理参数并包装成对应Pydantic模型

    不过针对put方法的特性,应当完全更新数据时使用put

    """

    for index, it in enumerate(data): # enumerate()返回一个由索引和对象构成的二元组

        if it.id == donation.id:

            data[index] = donation

            return APIResponse(code=10001, msg="OK")

    data.append(donation)

    return APIResponse(code=10001, msg="OK")



@app.patch("/donation/")

def reimu_patch(donation: Donation):

    """

    相同的,FastAPI会自动处理参数并包装成对应Pydantic模型

    不过针对patch方法的特性,应当更新部分字段,而不是整个资源时使用patch

    """

    for index, it in enumerate(data):

        if it.id == donation.id:

            data[index] = donation

            return APIResponse(code=10001, msg="OK")

    return APIResponse(code=10404, msg="not found")

@app.delete("/donation/{id}")

def reimu_delete(id: int):

    """

    相同的,FastAPI会自动处理参数并包装成对应Pydantic模型

    不过针对delete方法的特性,删除资源时使用delete

    """

    for it in data:

        if it.id == id:

            data.remove(it)

            return APIResponse(code=10001, msg="OK")

    return APIResponse(code=10404, msg="not found")

@app.options("/donation/")

def func_option():

    return APIResponse(code=10001, msg='OK', data={"qwq": "qaq"})