FastAPI学习
一、简介
fastapi是高性能的web框架。他的主要特点是:
- 快速编码
- 减少人为bug
- 直观
- 简易
- 具有交互式文档
- 高性能
- 基于API的开放标准
支持 python 3.6+ 版本。
官方文档链接:FastAPI
二、安装和运行
#安装 pip install fastapi #安装部署包 pip install uvicorn
简单示例:
from fastapi import FastAPI app = FastAPI() # 通过FastAPI类生成一个app实例 # 装饰器表明下面的视图函数通过app相应的路径通过访问,默认路径为:http://127.0.0.1:8000/ @app.get("/") def read_root(): return {"Hello": "World"} # 下面视图函数的路由:http://127.0.0.1:8000/items/xxx (xxx为数字类型即可) @app.get("/items/{item_id:int}") # 最后的int指定item_id为int类型,路径参数item_id会自动传给视图函数的形参item_id def read_item(item_id: int, q: str = None): return {"item_id": item_id, "q": q}
运行上面的脚本:
# 再terminal终端输入如下: uvicorn main:app --reload # 还可以通过指定主机端口的方式启动: uvicorn main:app --host 10.xxx.163.108 --port 8080 --reload # 这样别人可以通过http://10.xxx.163.108:8080/docs 就能访问你本地机子上的接口 # main: 表示app所在文件名 # app:FastAPI实例 # reload:debug模式,可以自动重启,调试时使用,生产环境不适用
更改运行端口,允许非本机ip访问
通常情况下我们以上面的方式运行程序,那我们机子上运行的接口只能由本机访问,其他人无法访问。但是,在FastAPI中,和Django/Flask一样,可以指定运行的 ip + port,让别人能够访问我们机子上运行的api接口,如下:
# 还可以通过指定主机端口的方式启动: uvicorn main:app --host 10.xxx.163.108 --port 8080 --reload # 这样别人可以通过http://10.xxx.163.108:8080/docs 就能访问你本地机子上的接口
配置运行脚本
在项目中我们不希望每次都通过命令行来运行我们的程序,这样也不利于我们用debug调试,所以我们需要配置一下运行脚本,这样就能通过pycharm直接通过点击按钮就能运行程序,具体配置信息如下:
三、基本用法
路径参数的嵌套
假设你有一个路径操作,它的路径为 /files/{file_path}
。
但是你需要 file_path
自身也包含路径,比如 home/johndoe/myfile.txt
。
因此,该文件的URL将类似于这样:/files/home/johndoe/myfile.txt
。
这时,我们可以使用路径转换器,使用直接来自 Starlette 的选项来声明一个包含路径的路径参数:
/files/{file_path:path}
在这种情况下,参数的名称为 file_path
,结尾部分的 :path
说明该参数应匹配任意的路径。
因此,你可以这样使用它:
from fastapi import FastAPI app = FastAPI() @app.get('/files/{file_path:path}') # 在路径参数中声明file_path后面跟的是一个路径 async def get_file_path(file_path: str): return {'file_path': file_path, 'message': f'{file_path} is a file path too'}
注意:
你可能会需要参数包含 /home/johndoe/myfile.txt
,以斜杠(/
)开头。
在这种情况下,URL 将会是 /files//home/johndoe/myfile.txt
,在files
和 home
之间有一个双斜杠(//
)。
如下图所示:
Enum用法(路径参数值可选)
from fastapi import FastAPI from enum import Enum app = FastAPI() # 定义一个类,继承Enum类 class ModelName(str,Enum): name = "heiheihei" hobby = "DBJ" # 在get请求的路径参数中的model_name会传给下面的视图函数作为参数,参数类型只能是ModelName类中列出的值,输入其他的会报错 @app.get("/models/{model_name}") async def get_model(model_name:ModelName): # 用法一 if model_name is ModelName.name: return {'model_name': model_name, 'message': 'I am HEIHEIHEI'} # 用法二:跟用法一差不多 if model_name.value == "DBJ": return {"model_name": model_name, "message": "I like dbj"} return {"model_name": model_name, 'message': 'lalala'} # GET请求: # 访问路径1:http://127.0.0.1:8000/models/heiheihei # 访问路径2:http://127.0.0.1:8000/models/DBJ
BaseModel用法(用于请求体携带参数,可有多个)
BaseModel子类声明的是一个JSON类型的请求体格式,请求体将当做JSON读取
from fastapi import FastAPI from enum import Enum from pydantic import BaseModel from typing import Union app = FastAPI() class ModelName(str,Enum): name = "heiheihei" hobby = "DBJ" # 定义一个Item类并继承BaseModel类,FastAPI可以默认识别并将Item类内的值作为请求体参数传递 class Item(BaseModel): name: str price: float description: Union[str, None] = None # Union表明description数据类型为可选str类型或None,默认值为None # 定义一个User类并继承BaseModel类,FastAPI可以默认是别并将User类内的值作为请求体参数传递 class User(BaseModel): username: str full_name: Union[str, None] = None # 路径参数中的item_id会传给get_models视图函数中的形参item_id @app.post("/items/{item_id}") async def get_models(item_id: int, model_name: ModelName, item: Item, user: User): # 请求体中携带的参数放在继承BaseModel的类中 result = {} if item.description: result.update(item.dict()) # 可以通过item.dict()把Item类中的值转化为字典类型数据 if user.username: result.update(user.dict()) return result # POST请求: # 访问路径1:http://127.0.0.1:8000/items/123 # 访问路径2:http://127.0.0.1:8000/items/123 # 请求体传参: { "item": { "name": "xm", "price": 0, "description": "no descri" }, "user": { "username": "bj", "full_name": "beijing" } } # 返回结果: { "name": "xm", "price": 0, "description": "no descri", "username": "bj", "full_name": "beijing" }
BaseModel高级用法:嵌套子模型
Basemodel不仅可以嵌套简单的str,int,list等数据类型,还可以嵌套继承 BaseModel 类的模型,如下:
from typing import Set, Union from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class Image(BaseModel): url: str name: str class Item(BaseModel): name: str description: Union[str, None] = None price: float tax: Union[float, None] = None tags: Set[str] = set() image: Union[Image, None] = None # 在这里把Image嵌套到image里面作为子模型 @app.put("/items/{item_id}") async def update_item(item_id: int, item: Item): results = {"item_id": item_id, "item": item} return results # 访问:http://127.0.0.1:8000/items/123 # 请求体传参: { "name": "Foo", "description": "The pretender", "price": 42.0, "tax": 3.2, "tags": ["rock", "metal", "bar"], "image": { "url": "http://example.com/baz.jpg", "name": "The Foo live" } } # 返回结果: { "item_id": 123, "item": { "name": "Foo", "description": "The pretender", "price": 42, "tax": 3.2, "tags": [ "rock", "bar", "metal" ], "image": { "url": "http://example.com/baz.jpg", "name": "The Foo live" } } }
Body用法(将单一值的参数声明为请求体参数)
如果我们按照正常变量的方式添加一个函数形参email_name,那么FastAPI会默认他是一个查询参数,会添加在路由末端的 ? 号后面作为查询参数,所以我们需要通过 Body 来向FastAPI声明 email_name 是一个请求体参数,如下:
from typing import Union from fastapi import Body, FastAPI from pydantic import BaseModel app = FastAPI() class Item(BaseModel): name: str description: Union[str, None] = None price: float tax: Union[float, None] = None class User(BaseModel): username: str full_name: Union[str, None] = None @app.put("/items/{item_id}") async def update_item(item_id: int, item: Item, user: User, email_name: str = Body(default=None)): # 这里通过Body声明email_name为请求体参数 results = {"item_id": item_id, "item": item, "user": user, "email_name": email_name} return results # 访问路径:http://127.0.0.1:8000/items/1212 # 请求体参数: { "item": { "name": "nc", "description": "nc do not need description", "price": 0, "tax": 0 }, "user": { "username": "BJ", "full_name": "Beijing" }, "email_name": "yessir@nb.com" } # 响应结果: { "item_id": 1212, "item": { "name": "nc", "description": "nc do not need description", "price": 0, "tax": 0 }, "user": { "username": "BJ", "full_name": "Beijing" }, "email_name": "yessir@nb.com" }
Body中的embed参数
当我们只有一个来自 Pydantic 模型 Item
的请求体参数 item,但是我们希望请求体的数据格式为字典嵌套字典的格式:{{},{},{}...},而不是单纯的字典格式:{},这时我们就需要在Body中用到embed参数,如下:
from typing import Union from fastapi import Body, FastAPI from pydantic import BaseModel app = FastAPI() class Item(BaseModel): name: str description: Union[str, None] = None price: float tax: Union[float, None] = None @app.put("/items/{item_id}") async def update_item(item_id: int, item: Item = Body(default=None, embed=True)): # embed参数实现字典嵌套的请求体数据格式 results = {"item_id": item_id, "item": item} return results
这时请求体的数据格式如下:
{ "item": { "name": "Foo", "description": "The pretender", "price": 42.0, "tax": 3.2 } }
而不是之前的:
{ "name": "Foo", "description": "The pretender", "price": 42.0, "tax": 3.2 }
Dict用法(声明参数为请求体参数)
你也可以将请求体声明为使用某类型的键和其他类型值的 dict
。无需事先知道有效的字段/属性(在使用 Pydantic 模型的场景)名称是什么。如果你想接收一些尚且未知的键,这将很有用,如下:
from typing import Dict from fastapi import FastAPI app = FastAPI() @app.post("/index-weights/") async def create_index_weights(weights: Dict[int, float]): # 指定weights参数的值为字典格式,并且key为int类型,value为float类型 return weights # 访问:http://127.0.0.1:8000/index-weights/ # 请求体参数: { "1": 1.1, # 如果key不是数字类型会报错 "2": 2.2, "3": 3.3 } # 返回结果: { "1": 1.1, "2": 2.2, "3": 3.3 }
Query用法(用于查询参数校验)
多用于查询参数的校验,内部可以传入很多
Query内还可以加上参数校验值的最小长度、最大长度、正则匹配、标题、描述、别名等等
详见:官方文档链接
from typing import Union from fastapi import FastAPI, Query app = FastAPI() @app.get("/items/") async def read_items(q: Union[str, None] = Query(default=None, min_length=3, max_length=50, regex="^fixedquery$")): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) return results # 上面的视图函数中 q: Union[str, None] = Query(default=None)等同于 q: str = None,都是指定q的数据类型为str,默认值为None # 并且Query内还可以加上参数校验值的最小长度、最大长度、正则匹配、标题、描述、别名等等
Path用法(路径参数值校验)
对于路径参数的校验,可以有很多,包括数值校验,跟Query类似,如下:
from fastapi import FastAPI, Path app = FastAPI() @app.get("/items/{item_id}") async def read_items( *, # 这里*号表示后面必须用关键字传参的形式 item_id: int = Path(title="The ID of the item to get", gt=0, le=1000), # 这里用Path对路径参数item_id的大小范围进行限制 q: str,): results = {"item_id": item_id} if q: results.update({"q": q}) return results
List用法(查询参数列表)
from typing import List, Union from fastapi import FastAPI, Query app = FastAPI() @app.get("/items/") async def read_items(q: Union[List[str], None] = Query(default=None)): query_items = {"q": q} return query_items # 访问:http://localhost:8000/items/?q=foo&q=bar # 得到结果: { "q": [ "foo", "bar" ] }
简单用法:
直接使用 list
代替 List[str],但是后者会检查列表中的数据类型是否为str类型,前者则不会。
from fastapi import FastAPI, Query app = FastAPI() @app.get("/items/") async def read_items(q: list = Query(default=[])): query_items = {"q": q} return query_items
参数顺序说明:
如果你在函数中将带有「默认值」的参数放在没有「默认值」的参数之前,Python 将会报错
from fastapi import FastAPI, Path app = FastAPI() # 测试一:正常 @app.get("/items/{item_id}") async def read_items(q: str, item_id: int = Path(title="The ID of the item to get")): # 正常 results = {"item_id": item_id} if q: results.update({"q": q}) return results # 测试二:报错 @app.get("/items/{item_id}") async def read_items(q: str = None, item_id: int): # 报错,q带有默认值None,有默认值的参数不能放在没有默认值的参数前面 results = {"item_id": item_id} if q: results.update({"q": q}) return results
四、引入请求体参数的三种方式
FastAPI中引入请求体的方式有三种:
- 通过继承Pydantic中BaseModel类方式,详见:BaseModel用法
- 通过fastapi中Body函数方式,详见:Body用法
- 通过Dict方式,详见:Dict用法
FastAPI通过BaseModel模型接收到的请求体数据为json类型数据,但是我们可以转化为其他其他类型(如有需要)
同时,FastAPI还提供了模型嵌套,模型嵌套模型、模型嵌套字典,模型嵌套模型:详见 BaseModel高级用法:嵌套子模型,
下面介绍模型嵌套字典的用法:
from typing import Union from pydantic import BaseModel from fastapi import FastAPI app = FastAPI() class ItemData(BaseModel): item_name: str create_flag: Union[bool, None] = None extra_data: dict @app.post("/items/{item_id}/create_item") async def create_item(item_id: int, data: ItemData): result = {"item_id": item_id, "item_name": data.item_name, "create_flag": data.create_flag} if data.create_flag: requester = data.extra_data.get('requester') # 因为我们定义extra_data为dict类型,所以FastAPI能自动识别出extra_data为字典 if requester: result.update(data.extra_data) return result if __name__ == '__main__': import uvicorn uvicorn.run(app, host="localhost", port=8888)
用postman请求如下:
FastAPI运行的两种方式
方式一
这时正常生产环境中我们运行程序的方式:
# 在terminal终端中输入如下: uvicorn main:app --reload # main指的是main.py文件 # app指的是调用FastAPI类生成的实例:app=FastAPI(),--reload仅在开发中使用,生产环境下不适用
方式二
主要是在我们在进行程序测试的时候使用,便于打断点调试
import uvicorn from fastapi import FastAPI app = FastAPI() @app.get('/items/{item_id}') async def get_item(item_id: int): return {"item_id": item_id} if __name__ == '__main__': uvicorn.run(app, host="192.168.1.xx", port=8000) # 可以指定自己的ip,让别人也可以访问我们运行的接口