【Python】FastAPI学习记录(二)

发布时间:2023年12月25日

使用记录

模型

响应模型

有的时候一个post接口,请求模型和响应模型我们需要的字段是不一样的,比如用户登录的接口:

from typing import Any

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: str | None = None

class UserOut(BaseModel):
    username: str
    email: EmailStr
    full_name: str | None = None


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

上述代码中添加了两个模型,UserIn用来做请求体的映射,而UserOut则是返回值的映射,通过response_model即可指定返回值模型;

除此之外,还可以选择在返回的时候忽略空的值,否则可能出现一个接口的返回值很冗长,携带大量空的json键值对,这个操作借由response_model_exclude_unset=True来实现。

减少代码量

对于用户模型来说,可以声明一个 UserBase 模型作为其他模型的基类。然后可以创建继承该模型属性(类型声明,校验等)的子类。这样,可以仅声明模型之间的差异部分(具有明文的 password、具有 hashed_password 以及不包括密码)。

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserBase(BaseModel):
    username: str
    email: EmailStr
    full_name: str | None = None

class UserIn(UserBase):
    password: str

class UserOut(UserBase):
    pass

class UserInDB(UserBase):
    hashed_password: str

def fake_password_hasher(raw_password: str):
    return "supersecret" + raw_password

def fake_save_user(user_in: UserIn):
    hashed_password = fake_password_hasher(user_in.password)
    user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password)
    print("User saved! ..not really")
    return user_in_db

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

任意 dict 构成的响应

可以使用一个任意的普通 dict 声明响应,仅声明键和值的类型,而不使用 Pydantic 模型。如果事先不知道有效的字段/属性名称(对于 Pydantic 模型是必需的),这将很有用。在这种情况下,可以使用 typing.Dict

from fastapi import FastAPI

app = FastAPI()


@app.get("/keyword-weights/", response_model=dict[str, float])
async def read_keyword_weights():
    return {"foo": 2.3, "bar": 3.4}

请求附加信息

定义Header参数和Cookies参数的方式与定义Query等参数的方式是一样的。

这是因为他们都是Path, Query等类的兄弟类型。它也继承自通用的Param 类.

Header信息

需要注意的其实只有一个点,那就是Header提供了自动转换的能力。

首先要知道大多数标准的headers用-分割,然而这种名字的变量在Python中无效,比如user-agent,因此,默认情况下,Header 将把参数名称的字符从_ 转换为-)来提取并记录 headers.

from typing import Annotated

from fastapi import FastAPI, Header

app = FastAPI()


@app.get("/items/")
async def read_items(user_agent: Annotated[str | None, Header()] = None):
    return {"User-Agent": user_agent}

其他的请求信息

上面说的大部分都是json数据格式的交互,有的时候会提交表单数据,或者用户上传文件,这里有别的处理方式。

表单数据

使用表单首先要安装额外的包

pip install python-multipart

fastapi 导入 Form

from fastapi import FastAPI, Form

app = FastAPI()


@app.post("/login/")
async def login(username: str = Form(), password: str = Form()):
    return {"username": username}

Tips:例如,OAuth2 规范的 “密码流” 模式规定要通过表单字段发送 usernamepassword

该规范要求字段必须命名为 usernamepassword,并通过表单字段发送,不能用 JSON。

使用 Form 可以声明与 Body (及 QueryPathCookie)相同的元数据和验证。

声明表单体要显式使用Form,否则,FastAPI 会把该参数当作查询参数或请求体(JSON)参数

文件数据

基本使用

文件数据使用File,从 fastapi 导入 File 并使用:

from fastapi import FastAPI, File, UploadFile

app = FastAPI()


@app.post("/files/")
async def create_file(file: bytes = File()):
    return {"file_size": len(file)}

上面的例子中如果把路径操作函数参数的类型声明为 bytesFastAPI 将以 bytes 形式读取和接收文件内容。这种方式把文件的所有内容都存储在内存里,适用于小型文件,实际上在多数情况下UploadFile 更好用。

from fastapi import FastAPI, UploadFile

app = FastAPI()


@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
    return {"filename": file.filename}

UploadFile的优势:

  • 使用spooled 文件,存储在内存的文件超出最大上限时,FastAPI 会把文件存入磁盘;
  • 这种方式更适于处理图像、视频、二进制文件等大型文件,好处是不会占用所有内存;
  • 可获取上传文件的元数据;
  • 自带 file-like async 接口;
  • 暴露的 Python SpooledTemporaryFile 对象,可直接传递给其他预期「file-like」对象的库。

UploadFile 的属性如下:

  • filename:上传文件名字符串(str),例如, myimage.jpg
  • content_type:内容类型(MIME 类型 / 媒体类型)字符串(str),例如,image/jpeg
  • fileSpooledTemporaryFilefile-like 对象)。其实就是 Python文件,可直接传递给其他预期 file-like 对象的函数或支持库。

UploadFile 支持以下 async 方法,(使用内部 SpooledTemporaryFile)可调用相应的文件方法。

  • write(data):把 datastrbytes)写入文件;
  • read(size):按指定数量的字节或字符(size (int))读取文件内容;
  • seek(offset):移动至文件offset(int)字节处的位置;
    • 例如,await myfile.seek(0) 移动到文件开头;
    • 执行 await myfile.read() 后,需再次读取已读取内容时,这种方法特别好用;
  • close():关闭文件。

与 JSON 不同,HTML 表单(<form></form>)向服务器发送数据通常使用「特殊」的编码,在不包含文件时,表单数据一般采用 application/x-www-form-urlencoded「媒体类型」编码,包含文件时则使用multipart/form-data编码;

如果想要文件上传选项是可选的,只需要以None作为注解即可:

from fastapi import FastAPI, File, UploadFile

app = FastAPI()


@app.post("/files/")
async def create_file(file: bytes | None = File(default=None)): # 这里多了None = File(default=None)
    if not file:
        return {"message": "No file sent"}
    else:
        return {"file_size": len(file)}


@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile | None = None):
    if not file:
        return {"message": "No upload file sent"}
    else:
        return {"filename": file.filename}
多文件

同一个表单字段可以包含多个文件,可以想到这种情况下使用含 bytesUploadFileList

from fastapi import FastAPI, File, UploadFile
from fastapi.responses import HTMLResponse

app = FastAPI()


@app.post("/files/")
async def create_files(files: list[bytes] = File()):
    return {"file_sizes": [len(file) for file in files]}


@app.post("/uploadfiles/")
async def create_upload_files(files: list[UploadFile]):
    return {"filenames": [file.filename for file in files]}


@app.get("/")
async def main():
    content = """
<body>
<form action="/files/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
<form action="/uploadfiles/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
</body>
    """
    return HTMLResponse(content=content)

表单+文件

直接设置多个内容即可:

from fastapi import FastAPI, File, Form, UploadFile

app = FastAPI()


@app.post("/files/")
async def create_file(
    file: bytes = File(), fileb: UploadFile = File(), token: str = Form()
):
    return {
        "file_size": len(file),
        "token": token,
        "fileb_content_type": fileb.content_type,
    }
文章来源:https://blog.csdn.net/Meepoljd/article/details/135200822
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。