02 FastAPI入门手册

官方文档:学习 - FastAPI

fastapi,一个用于构建 API 的现代、快速(高性能)的web框架。

fastapi是建立在Starlette和Pydantic基础上的,Pydantic是一个基于Python类型提示来定义数据验证、序列化和文档的库。Starlette是一种轻量级的ASGI框架/工具包,是构建高性能Asyncio服务的理性选择。

一、http请求协议格式

URL(Uniform Resource Locator,统一资源定位符) 是互联网上用于唯一标识和定位资源的地址。简单来说,URL 就是我们常说的“网址”,用于访问网页、文件、图片、视频等网络资源。

一个完整的URL包括:协议、ip、端口、路径、参数

例如: 百度安全验证https://www.baidu.com/s?wd=yuan 其中https是协议,www.baidu.com 是IP,端口默认80,/s是路径,参数是wd=yuan

请求方式: get与post请求

  • GET提交的数据会放在URL之后,以?分割URL和传输数据,参数之间以&相连,如EditBook?name=test1&id=123456;提交的数据大小有限制(因为浏览器对URL的长度有限制)
  • POST方法是把提交的数据放在HTTP包的请求体中;提交的数据没有限制

响应状态码:是当客户端向服务器端发送请求时, 返回的请求结果。状态码如200 OK,以3位数字和原因组成。

二、fastapi_quickstart

pip install fastapi uvicorn

uvicorn v0.21.0(高版本开发环境时重启有bug)

  • FastAPI-Web 框架:用于编写 API 的代码逻辑,处理路由、参数校验、序列化、身份认证等。
  • Uvicorn-ASGI 服务器:负责运行 FastAPI 应用,处理 HTTP 请求与响应,支持异步和高并发。
from fastapi import FastAPI
import uvicorn

app = FastAPI()


@app.get("/")  # 路径操作装饰器
async def home():  # 路径操作函数
    return {"user_id": 123}

# 路径操作装饰器参数可选,主要用于注解
@app.post("/shop",
          tags=["shop接口"],
          summary="this is summary",
          description="this is description",
          response_description="this is response_description",
          deprecated=False,  # 是否废弃
          )
def shop():
    return {"shop_id": "123"}


if __name__ == '__main__':
    uvicorn.run("quickstart:app", host="127.0.0.1", port=8080, reload=True)
    # 终端运行
    # uvicorn quickstart:app --host 127.0.0.1 --port 8080 --reload

http://127.0.0.1:8080/docs 可查看 fastapi 自动生成的交互式 API 文档

三、路径操作

3.1 路径操作装饰器

fastapi支持各种请求方式:

3.2 路由分发 include_router

app01.utils.shop

app02.utils.user

main.py

http://127.0.0.1:8080/docs 查看fastapi接口文档

四、请求与相应

路径参数、查询参数、请求体参数

4.1 路径参数Path(⭐)
(1)基本用法

以使用与 Python 格式化字符串相同的语法来声明路径"参数"或"变量":

@app.get("/user/{user_id}")
def get_user(user_id):
    print(user_id, type(user_id))
    return {"user_id": user_id}

路径参数 user_id 的值将作为参数 user_id 传递给你的函数

(2)有类型的路径参数

你可以使用标准的 Python 类型标注为函数中的路径参数声明类型。

@app.get("/user/{user_id}")
def get_user(user_id: int):
    print(user_id, type(user_id))
    return {"user_id": user_id}

在这个例子中,user_id 被声明为 int 类型。(这将为你的函数提供编辑器支持,包括错误检查、代码补全等等。)

(3)注意顺序

在创建路径操作时,你会发现有些情况下路径是固定的。

比如 /users/me,我们假设它用来获取关于当前用户的数据.

然后,你还可以使用路径 /user/{username} 来通过用户名 获取关于特定用户的数据。

由于路径操作是按顺序依次运行的,你需要确保路径 /user/me 声明在路径 user/{username}之前:

@app.get("/user/me")
async def read_user_me():
    return {"username": "the current user"}

@app.get("/user/{username}")
async def read_user(username: str):
    return {"username": username}

否则,/user/{username} 的路径还将与 /user/me 相匹配,"认为"自己正在接收一个值为 "me" 的 username 参数。

(4)枚举型路径参数
from enum import Enum
from fastapi import FastAPI

class cust_level(str, Enum):    
    level1 = '钻石'    
    level2 = '私人银行'    
    level3 = '金葵花'    
    level4 = '金卡'    
    level5 = '普卡'     
app = FastAPI()    
   
@app.get('/customer_level/{level}')  
def read_enum(level: cust_level):  
    return {'level_res': level}  

4.2 查询参数Query(⭐)
(1)简介

路径函数中声明不属于路径参数的其他函数参数时,它们将被自动解释为"查询字符串"参数,就是 url? 问号之后用&分割的 key-value 键值对。

示例:

http://127.0.0.1:8000/items/?page=1&limit=10

该请求的查询参数有两个,page和limit,值分别为1,10。

@app.get("/jobs/{kd}")
async def search_jobs(kd: str, city: Union[str, None] = None, xl: Optional[str] = None):  # 有默认值即可选,否则必选
    if city or xl:
        return {"kd": kd, "city": city, "xl": xl}
    return {"kd": kd}

在这个例子中,函数参数 cityxl 是可选的,并且默认值为 None

  • Union 是当有多种可能的数据类型时使用,比如函数有可能根据不同情况有时返回str或返回list,那么就可以写成Union[list, str]
  • Optional 是Union的一个简化, 当 数据类型中有可能是None时,比如有可能是str也有可能是None,则Optional[str], 相当于Union[str, None]
(2)设置参数默认值
@app.get("/list/query1")  
def list_query1(page: int = 1, limit: int = 10):  
    return {"page": page, "limit": limit}
ip:port/list/query?page=1&limit=3  
ip:port/list/query                # 使用默认值 page=1,limit=10  
ip:port/list/query?page=&limit=   # 报错
(3)设置为可选参数

声明可选的查询参数,只需要将他们的默认值设置为None即可。

@app.get("/list/query2")  
def list_query2(page:int = None):  
    return {"page": page}
ip:port/list/test
ip:port/list/test?page=3 
(4)设置为枚举类型参数
from fastapi import APIRouter,Query  
from enum import Enum  
  
//自定义枚举类  
class Cust_level(str, Enum):  
    Level1 = '钻石'  
    Level2 = '私人银行'  
    Level3 = '金葵花'  
    Level4 = '金卡'  
    Level5 = '普卡'  
  
app03 = APIRouter()  
 
@app03.get("/list/query4")  
def list_query4(name:str = Query(...,alias='姓名'),  
                level :Cust_level = Cust_level.Level1 ):  
    return{"name":name,"level":level}

(5)Query简单介绍

Query库,可以提供对查询参数进行额外校验的功能。

def Query(  # noqa: N802  
    default: Any = Undefined,  //参数类型,传...表示为必传,传None表示为可选
    *,  
    alias: Optional[str] = None,  //别名
    title: Optional[str] = None,  
    description: Optional[str] = None,  //描述
    gt: Optional[float] = None,  
    ge: Optional[float] = None,  
    lt: Optional[float] = None,  
    le: Optional[float] = None,  
    min_length: Optional[int] = None,  //最小长度
    max_length: Optional[int] = None,  //最大长度
    regex: Optional[str] = None,  //正则表达式校验
    example: Any = Undefined,  
    examples: Optional[Dict[str, Any]] = None,  
    deprecated: Optional[bool] = None,  
    include_in_schema: bool = True,  
    **extra: Any,  
) -> Any: 
q: str = Query(None,  max_length=50)  # 可选
q: str = Query(... , max_length=50)  # 必选
from fastapi import FastAPI, Query  
  
app = FastAPI()  
 
@app.get('/items/')  
def read_items(  
 q: str = Query(None, min_length=1, max_length=20, regex='^str$') ):  
    results = {'items': [{'item_id': 'Foo'}, {'item_id': 'Bar'}]}  
    if q:  
        results.update({'q': q})  
    return results

这个特定的正则表达式检查接收到的参数值:

  • ^: 表示字符串str前面没有字符
  • str: 匹配 str字符串
  • $: 表示字符串str后面不匹配任何字符

数值校验:

from fastapi import FastAPI, Path  
  
app = FastAPI()  
 
@app.get('/items/{item_id}')  
def read_items(  
 *, item_id: int = Path(..., ge=1), q: str ):  
    results = {'item_id': item_id}  
    if q:  
        results.update({'q': q})  
    return results
from fastapi import FastAPI, Path  
  
app = FastAPI()  
 
@app.get('/items/{item_id}')  
async def read_items(*,
    *,item_id: int = Path(..., gt=0, le=1000),q: str ):
    results = {'item_id': item_id}  
    if q:  
        results.update({'q': q})  
    return results
from fastapi import FastAPI, Path, Query  
  
app = FastAPI()  
 
@app.get('/items/{item_id}')  
def read_items(  
    *, item_id: int = Path(..., ge=0, le=1000),  
    item_id: int = Path(..., ge=0, le=1000),  
    q: str,  
 size: float = Query(..., gt=0, lt=10.5) ):  
    results = {'item_id': item_id}  
    if q:  
        results.update({'q': q})  
    return results
4.3 请求体数据Body(⭐)

FastAPI 基于 Pydantic ,Pydantic 主要用来做类型强制检查(校验数据)。不符合类型要求就会抛出异常。

定义请求体,一般需要使用Pydantic模型,发送请求体数据,常用以下几种方法:POST(最常见)、PUT、DELETE、PATCH。

对于 API 服务,支持类型检查非常有用,会让服务更加健壮,也会加快开发速度,因为开发者再也不用自己写一行一行的做类型检查。

安装上手pip install pydantic

(1)使用Pydantic模型
from typing import Union, List, Optional

from fastapi import FastAPI
from pydantic import BaseModel, Field, ValidationError, validator
import uvicorn
from datetime import date


class Addr(BaseModel):
    province: str
    city: str


class User(BaseModel):
    name = 'root'
    age: int = Field(default=0, lt=100, gt=0)
    birth: Optional[date] = None
    friends: List[int] = []
    description: Union[str, None] = None

    # addr: Union[Addr, None] = None  # 类型嵌套

    @validator('name')  # 定义自定义数据验证逻辑的装饰器
    def name_must_alpha(cls, v):
        assert v.isalpha(), 'name must be alpha'
        return v


class Data(BaseModel):  # 类型嵌套
    users: List[User]


app = FastAPI()


@app.post("/data/")
async def create_data(data: Data):
    # 添加数据库
    return data

和声明查询参数时一样,当一个模型属性具有默认值时,它不是必需的。否则它是一个必需属性。将默认值设为 None 可使其成为可选属性。

FastAPI 会自动将定义的模型类转化为JSON Schema,Schema 成为 OpenAPI 生成模式的一部分,并显示在 API 交互文档中,查看 API 交互文档如下,该接口将接收application/json类型的参数。

FastAPI 支持同时定义 Path 参数、Query 参数和请求体参数,FastAPI 将会正确识别并获取数据。

  • 参数在 url 中也声明了,它将被解释为 path 参数
  • 参数是单一类型(例如int、float、str、bool等),它将被解释为 query 参数
  • 参数类型为继承 Pydantic 模块的BaseModel类的数据模型类,则它将被解释为请求体参数
(2)Body简单介绍
  • 可以将单类型的参数成为Request Body的一部分,即查询参数变成请求体参数。
  • 和 Query提供的额外校验基本一致。
def Body(  # noqa: N802  
    default: Any = Undefined,  
    *,  
    embed: bool = False,  
    media_type: str = "application/json",  
    alias: Optional[str] = None,  
    title: Optional[str] = None,  
    description: Optional[str] = None,  
    gt: Optional[float] = None,  
    ge: Optional[float] = None,  
    lt: Optional[float] = None,  
    le: Optional[float] = None,  
    min_length: Optional[int] = None,  
    max_length: Optional[int] = None,  
    regex: Optional[str] = None,  
    example: Any = Undefined,  
    examples: Optional[Dict[str, Any]] = None,  
    **extra: Any,  
) -> Any:
from fastapi import FastAPI,Body  
app = FastAPI()    
class Item2(BaseModel):  
    name:str = Body(default="张三",alias="姓名")  
    description:str=Body(default="",alias="描述")  
    price:float = Body(default='',alias="分数",gt=5.0,lt=10.5)  
    tax:float=None  
@app04.post('
def create_item3(item:Item2 ):  
    return item

4.4 form表单数据

在 OAuth2 规范的一种使用方式(密码流)中,需要将用户名、密码作为表单字段发送,而不是 JSON。

FastAPI 可以使用Form组件来接收表单数据,需要先使用pip install python-multipart命令进行安装。

from fastapi import FastAPI, Form

app = FastAPI()

@app.post("/regin")
def regin(username: str = Form(..., max_length=16, min_length=8, regex='[a-zA-Z]'),
          password: str = Form(..., max_length=16, min_length=8, regex='[0-9]')):
    print(f"username:{username},password:{password}")
    return {"username": username}

4.5 文件上传
from fastapi import FastAPI, File, UploadFile
from typing import List

app = FastAPI()

# file: bytes = File():适合小文件上传
# 使用 File() 接收文件内容,直接以 bytes 类型存储在内存中。
@app.post("/files/")
async def create_file(file: bytes = File()):
    print("file:", file)
    return {"file_size": len(file)}

# 通过 List[bytes] 接收多个文件,每个文件内容以字节形式存储
@app.post("/multiFiles/")
async def create_files(files: List[bytes] = File()):
    return {"file_sizes": [len(file) for file in files]}

# file: UploadFile:适合大文件上传
# 使用 UploadFile 类型,支持流式读写,避免一次性加载大文件到内存。
# 分块读取文件(每次 1024 字节),写入本地磁盘
@app.post("/uploadFile/")
async def create_upload_file(file: UploadFile):
    with open(f"{file.filename}", 'wb') as f:
        for chunk in iter(lambda: file.file.read(1024), b''):
            f.write(chunk)

    return {"filename": file.filename}


@app.post("/multiUploadFiles/")
async def create_upload_files(files: List[UploadFile]):
    return {"filenames": [file.filename for file in files]}
4.6 Request对象

有些情况下我们希望能直接访问Request对象。例如我们在路径操作函数中想获取客户端的IP地址,需要在函数中声明Request类型的参数,FastAPI 就会自动传递 Request 对象给这个参数,我们就可以获取到 Request 对象及其属性信息,例如 header、url、cookie、session 等。

from fastapi import Request


@app.get("/items")
async def items(request: Request):
    return {
        "请求URL:": request.url,
        "请求ip:": request.client.host,
        "请求宿主:": request.headers.get("user-agent"),
        "cookies": request.cookies,
    }
4.7 请求静态文件

在 Web 开发中,需要请求很多静态资源文件(不是由服务器生成的文件),如 css/js 和图片文件等。

from fastapi.staticfiles import StaticFiles

app = FastAPI()
app.mount("/static",StaticFiles(directory="static"))
4.8 响应模型相关参数(⭐)
(1)response_model

前面写的这么多路径函数最终 return 的都是自定义结构的字典,FastAPI 提供了 response_model 参数,声明 return 响应体的模型

# 路径操作
@app.post("/items/", response_model=Item)
# 路径函数
async def create_item(item):
    ...

response_model 是路径操作的参数,并不是路径函数的参数

FastAPI将使用response_model进行以下操作:

  • 将输出数据转换为response_model中声明的数据类型。
  • 验证数据结构和类型
  • 将输出数据限制为该model定义的
  • 添加到OpenAPI中
  • 在自动文档系统中使用。
  • 你可以在任意的路径操作中使用 response_model 参数来声明用于响应的模型

案例:

  • 注册功能
  • 输入账号、密码、昵称、邮箱,注册成功后返回个人信息
from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: Union[str, None] = None


class UserOut(BaseModel):
    username: str
    email: EmailStr
    full_name: Union[str, None] = None


@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn):
    return user

(2)response_model_exclude_unset

通过上面的例子,我们学到了如何用response_model控制响应体结构,但是如果它们实际上没有存储,则可能要从结果中忽略它们。例如,如果model在NoSQL数据库中具有很多可选属性,但是不想发送很长的JSON响应,其中包含默认值。

from typing import List, Union

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: float = 10.5
    tags: List[str] = []


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}


@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
    return items[item_id]

请求:http://127.0.0.1:8080/items/foo

不设置unset参数:

{
  "name": "Foo",
  "description": null,
  "price": 50.2,
  "tax": 10.5,
  "tags": []
}

设置unset参数:

{
  "name": "Foo",
  "price": 50.2
}

使用路径操作装饰器的 response_model 参数来定义响应模型,特别是确保私有数据被过滤掉。使用 response_model_exclude_unset 来仅返回显式设定的值。

除了response_model_exclude_unset以外,还有response_model_exclude_defaults和response_model_exclude_none,我们可以很直观的了解到他们的意思,不返回是默认值的字段和不返回是None的字段。

(3)INCLUDE和EXCLUDE

response_model_exclude 和 response_model_include 是用于控制响应数据中字段的包含或排除的参数。它们与 response_model 配合使用,允许你精细调整返回给客户端的数据结构。

response_model_excluderesponse_model_include 不能同时使用,否则 FastAPI 会抛出错误。

# response_model_exclude
@app.get("/items/{item_id}", response_model=Item, response_model_exclude={"description"}, )
async def read_item(item_id: str):
    return items[item_id]

# response_model_include  
@app.get("/items/{item_id}", response_model=Item, response_model_include={"name", "price"}, )
async def read_item(item_id: str):
    return items[item_id]

五、中间件与CORS

5.1 中间件

你可以向 FastAPI 应用添加中间件.

"中间件"是一个函数,它在每个请求被特定的路径操作处理之前,以及在每个响应之后工作.

如果你使用了 yield 关键字依赖, 依赖中的退出代码将在执行中间件执行.

如果有任何后台任务(稍后记录), 它们将在执行中间件运行.

要创建中间件你可以在函数的顶部使用装饰器 @app.middleware("http").

中间件参数接收如下参数:

  • request.
  • 一个函数call_next,它将接收request,作为参数.
    • 这个函数将 request 传递给相应的 路径操作.
    • 然后它将返回由相应的路径操作生成的 response.
  • 然后你可以在返回 response 前进一步修改它.
import uvicorn
from fastapi import FastAPI

from fastapi import Request
from fastapi.responses import Response
import time

app = FastAPI()


@app.middleware("http")
async def m2(request: Request, call_next):
    # 请求代码块
    print("m2 request")
    response = await call_next(request)
    # 响应代码块
    response.headers["author"] = "yuan"
    print("m2 response")
    return response


@app.middleware("http")
async def m1(request: Request, call_next):
    # 请求代码块
    print("m1 request")
    # if request.client.host in ["127.0.0.1", ]:  # 黑名单
    #     return Response(content="visit forbidden")

    # if request.url.path in ["/user"]:
    #     return Response(content="visit forbidden")

    start = time.time()

    response = await call_next(request)
    # 响应代码块
    print("m1 response")
    end = time.time()
    response.headers["ProcessTimer"] = str(end - start)
    return response


@app.get("/user")
def get_user():
    time.sleep(3)  # 模拟耗时操作
    print("get_user函数执行")
    return {
        "user": "current user"
    }


@app.get("/item/{item_id}")
def get_item(item_id: int):
    time.sleep(2)  # 模拟耗时操作
    print("get_item函数执行")
    return {
        "item_id": item_id
    }


if __name__ == '__main__':
    uvicorn.run('main:app', host='127.0.0.1', port=8030, reload=True,
                debug=True, workers=1)

FastAPI 的中间件按照注册顺序依次执行请求代码块,但响应代码块的执行顺序是相反的(后注册的中间件先处理响应)。

访问 /user 时:

m2 request
m1 request
get_user函数执行  # 休眠 3 秒
m1 response
m2 response

访问 /user 时,响应头如下:

HTTP/1.1 200 OK
content-type: application/json
author: yuan
ProcessTimer: 3.002345
5.2 CORS 跨源资源共享

CORS是跨源资源共享,用来允许不同源的前端应用访问后端API。

CORS(Cross-Origin Resource Sharing,跨源资源共享) 是一种浏览器安全机制,用于控制不同源(协议、域名、端口)之间的资源访问权限。它的核心目的是保护用户隐私和数据安全,防止恶意网站通过脚本窃取用户数据。

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()
origins = [
    "http://localhost:63342"
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,  # 允许的源列表。
    # allow_origins=["*"],   # 代表允许所有源。
    # 当 allow_credentials=True 时,allow_origins 不能为 *,否则浏览器会拒绝请求。
    allow_credentials=True, # 允许携带凭证(如 Cookies)
    allow_methods=["GET"],  # 允许的 HTTP 方法
    allow_headers=["*"],    # 允许的请求头
)

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


if __name__ == '__main__':
    import uvicorn

    uvicorn.run("main:app", host="127.0.0.1", port=8080, debug=True, reload=True)
### FastAPI 实战教程与学习资源 #### 完整后端实现示例 FastAPI-RealWorld示例应用展示了如何利用FastAPI框架构建一个功能全面的Web应用。此项目实现了Conduit API规范,提供了详细的开发实例来辅助理解FastAPI的各种高级特性和最佳实践[^1]。 #### 基础到进阶的学习路径 对于希望深入学习FastAPI的开发者而言,存在一个专门设计的学习示例项目。该项目涵盖了从基础概念如路由、中间件等到更复杂的主题比如依赖注入等内容,并且还涉及到了诸如CRUD操作、身份验证以及文件上传等实用的功能模块。这些例子有助于新手入门同时也能够满足有经验者的进一步探索需求[^2]。 #### 高效性能下的实战指南 针对想要了解FastAPI在真实世界中的表现情况的人群来说,《使用FastAPI for Python构建应用实战教程》是一份不可多得的好材料。这份文档不仅解释了为何FastAPI能在众多竞争对手之中脱颖而出成为年度最受欢迎开源工具之一的原因所在;更重要的是它指导读者一步步完成整个项目的搭建过程——从最开始的环境配置到最后阶段有关于高可用性的考量等方面都有所涉猎[^4]。 #### 逐步引导式的教学手册 另外还有专门为具有一定Python编程能力和Web开发背景的朋友编写的系列课程,在这里可以找到关于怎样运用FastAPI去创造属于自己的RESTful服务的具体步骤说明。该教材强调动手能力培养的同时也不忘理论知识传授的重要性,确保每位学员都能扎实掌握所需技能[^3]。 ```python from fastapi import FastAPI, Depends, UploadFile, File from pydantic import BaseModel import uvicorn app = FastAPI() class Item(BaseModel): name: str description: str | None = None price: float tax: float | None = None @app.post("/items/") async def create_item(item: Item): return {"item": item} if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值