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中引入请求体的方式有三种:

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,让别人也可以访问我们运行的接口

 

posted @ 2023-01-10 21:09  _yessir  阅读(411)  评论(0编辑  收藏  举报