FastAPI教程(一)

FastAPI

简单应用

from fastapi import FastAPI
​
app = FastAPI()
# app是表示整个Web应用程序的顶级FastAPI对象
​
# 在此服务器上对URL“/hi”的请求应定向到以下函数。
# 这个装饰器只适用与get请求
@app.get("/hi")
def greet():
    return "Hello? World?"
​
if __name__ == "__main__":
    # 引入web服务器
    # 默认使用8000端口
    import uvicorn
    # reload参数用于在文件发生更改时重启服务器
    uvicorn.run("hello:app", reload=True)
​
    # 直接在浏览器访问
    # 访问http://localhost:8000/hi
​
    # 使用request访问
    import requests
    r = requests.get("http://localhost:8000/hi")
    print(r.json())
    # 直接在浏览器访问
    # 访问http://localhost:8000/hi
​
    # 使用request访问
    import requests
    r = requests.get("http://localhost:8000/hi")
    print(r.json())
    
    # 使用Httpie测试
    # 默认方法 会返回请求体
    $ http localhost:8000/hi
    # 跳过响应头 只输出响应体
    $ http -b localhost:8000/hi
    # 获得完整的响应头
    $ http -v localhost:8000/hi

HTTP Requests

URL Path

一个http请求格式

GET /hi HTTP/1.1 
Accept: /
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: localhost:8000
User-Agent: HTTPie/3.2.1
#FastAPI使用了依赖注入,需要的单数都可以在path函数中声明和提供
# 利用路径传入参数
from fastapi import FastAPI

app = FastAPI()

# 将who变量的值注入 然后输出
@app.get("/hi/{who}")
def greet(who):
    return f"Hello? {who}?"

if __name__ == "__main__":
    # 引入web服务器
    # 默认使用8000端口
    import uvicorn
    # reload参数用于在文件发生更改时重启服务器
    uvicorn.run("hello:app", reload=True)

# 在另一个窗口运行
import requests

r = requests.get("http://localhost:8000/hi/simon")
print(r.json())
# 最后的响应是一个JSON字符串
# 输出 Hello? simon?
Query Parameters
# 利用查询参数传递参数
from fastapi import FastAPI

app = FastAPI()

# 定义了一个叫做who的参数
@app.get("/hi")
def greet(who):
    return f"Hello? {who}?"

if __name__ == "__main__":
    # 引入web服务器
    # 默认使用8000端口
    import uvicorn
    # reload参数用于在文件发生更改时重启服务器
    uvicorn.run("hello:app", reload=True)
    
# 在路径中直接写入变量    
import requests
requests.get("http://localhost:8000/hi?who=simon")
r.json()

# 使用字典传入参数
params = {"who": "Mom"}
r = requests.get("http://localhost:8000/hi", params=params)

Body

# 我们可以向GET端点提供路径或查询参数,但不能提供来自请求体的值。在HTTP中,GET应该是幂等的--一个计算机术语,意思是问同样的问题,得到同样的答案。HTTP GET应该只返回内容。请求体用于在创建(POST)或更新(PUT或PATCH)时向服务器发送内容。

# 使用post 在请求体中传递参数
from fastapi import FastAPI, Body

app = FastAPI()

@app.post("/hi")
def greet(who:str = Body(embed=True)):
    return f"Hello? {who}?"


if __name__ == "__main__":
    import uvicorn
    uvicorn.run("hello:app", reload=True)

import requests
# 使用json参数来传递JSON编码数据。
r = requests.post("http://localhost:8000/hi", json={"who": "Mom"})

HTTP Header

from fastapi import FastAPI, Header

app = FastAPI()

# 在请求头中传递参数
@app.post("/hi")
def greet(who:str = Header()):
    return f"Hello? {who}?"
# $ http -v localhost:8000/hi who:Mom
# 返回请求头中内容
from fastapi import FastAPI, Header# 最后返回的是请求头中的

app = FastAPI()

@app.post("/agent")
def get_agent(user_agent:str = Header()):
    return user_agent
# 最后返回的是请求头中的user-agent内容
# FastAPI对这些内容进行了一些转化 从-到_

同一个路径函数中可以使用多个方法,可以同时从URL、查询参数、HTTP体、HTTP头、cookies等位置获取数据。

  • 在URL中传递参数时,遵循RESTful准则是标准做法。

  • 查询字符串通常用于提供可选参数,如分页。

  • body通常用于较大的输入,如整体或部分模型。

Http Response

默认情况下,FastAPI将从端点函数返回的任何内容转换为JSON。

Status Code

# 添加状态码
@app.get("/happy")
def happy(status_code=200):
    return ":)"

Headers

from fastapi import FastAPI, Body
from fastapi import Response
app = FastAPI()
# 注入响应头
@app.get("/header/{name}/{value}")
def header(name: str, value: str, response:Response):
    response.headers[name] = value
    return "normal body"

if __name__ == "__main__":
    import uvicorn
    uvicorn.run("hello:app", reload=True)
# localhost:8000/header/simon/skywalker

Response Types

常见的返回类型包括

  • JSONResponse (默认值)

  • HTMLResponse

  • PlainTextResponse

  • RedirectResponse

  • FileResponse

  • StreamingResponse

with语句

# with用于简化资源管理的上下文管理器语法结构,它能够确保代码块执行前后正确地获取和释放资源。
# with语句常见操作
# 文件操作
with open('file.txt', 'r') as f:
    content = f.read()
    # 文件会在with块结束后自动关闭

# 锁操作
with lock:
    # 临界区代码
    # 锁会在with块结束后自动释放

# 数据库连接
with connection:
    # 数据库操作
    # 连接会在with块结束后自动关闭或归还连接池

# 进行异常处理    
with pytest.raises(ValueError):
    # 期望这里的代码抛出ValueError异常
    int('not a number')

响应模型

from datetime import datetime
from pydantic import BaseClass

# 仅包含用户标签名
# 用于验证客户端请求数据
class TagIn(BaseClass):
    tag: str

# 内部模型
# 包含完整信息,包括敏感数据 (secret) 
class Tag(BaseClass):
    tag: str
    created: datetime
    secret: str

# 输出模型
# 返回必要信息 排除敏感信息
class TagOut(BaseClass):
    tag: str
    created: datetime
import datetime
from fastapi import FastAPI
from model.tag import TagIn, Tag, TagOut
import service.tag as service

app = FastAPI()

@app.post('/')
# 接受请求体中的TagIn对象
# 返回类型注解 -> TagIn
def create(tag_in: TagIn) -> TagIn:
    # tag: Tagt、ag_in: TagIn使用了参数类型注解语法
    tag: Tag = Tag(tag=tag_in.tag, created=datetime.utcnow(),
        secret="shhhh")
    service.create(tag)
    return tag_in

# 使用 response_model=TagOut 自动过滤返回数据
@app.get('/{tag_str}', response_model=TagOut)
def get_one(tag_str: str) -> TagOut:
    tag: Tag = service.get(tag_str)
    return tag

自动化文档

# 进入该页面 即可在这个页面进行自动化测试 
# 可以实现自定义参数发起请求等等操作
http://localhost:8000/docs

异步编程

异步的主要用途是避免长时间的I/O等待。

综合示例

结合上面的知识,让我们解析一个完整的示例:

import asyncio
# async 用于定义一个异步函数
# 异步函数在被调用时返回一个协程对象,这个对象需要被await或其他方式执行。

示例:
async def q():
    print("Why can't programmers tell jokes?")
    # asyncio.sleep() 是一个异步的睡眠函数,它返回一个协程,在指定的时间后完成。
    await asyncio.sleep(3)
# await用于挂起异步函数的执行,直到await后面的异步操作完成。只能在异步函数中使用。
# await`后面通常跟随一个耗时操作(例如 I/O 操作、网络请求、睡眠等),这些操作返回一个协程对象、任务或未来对象。

async def a():
    print("Timing!")

async def main():
    # asyncio用于编写异步 I/O 代码。它提供了事件循环、协程和任务等功能。
   
	# asyncio.gather() 用于并发运行多个协程,并在所有协程完成后返回它们的结果。
	# 它接受多个协程作为参数,并返回一个包含所有协程结果的列表。
    await asyncio.gather(q(), a())

if __name__ == "__main__":
    # asyncio.run()用于运行一个异步函数,启动事件循环并等待其完成。
    asyncio.run(main())
    
# 首先会运行q()
# 然后进程阻塞 切换到运行a()
# a()执行完切换回q()
# 简单的异步demo
from fastapi import FastAPI
import asyncio
import uvicorn

app = FastAPI()


@app.get("/hi")
async def greet():
    await asyncio.sleep(1)
    return {"message": "Hello? World?"}


if __name__ == "__main__":
    uvicorn.run("Test:app")

Starlette

# 直接使用Starlette编写程序
import uvicorn
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route

async def greeting(request):
    return JSONResponse('Hello? World?')

app = Starlette(debug=True, routes=[
    Route('/hi', greeting),
])

if __name__ == "__main__":
    uvicorn.run("Test:app")

Pydantic

# 基本模型定义
from pydantic import BaseModel
from typing import Optional, List
from datetime import datetime

class User(BaseModel):
    id: int
    username: str
    email: str
    is_active: bool = True
    created_at: datetime
    tags: List[str] = []
    bio: Optional[str] = None
    
# 自动类型转换
user = User(
    id="123",  # 字符串 → 整数
    username="SimonSkywalke",
    email="simon@example.com",
    created_at="2025-03-30T11:27:52"  # 字符串 → datetime
)
print(user.id)  # 输出: 123 (int类型)


from pydantic import ValidationError
try:
    invalid_user = User(
        id="not-a-number",
        username="SimonSkywalke",
        email="invalid-email",
        created_at="2025-03-30"
    )
except ValidationError as e:
    print(e.json())

简单综合应用

from pydantic import BaseModel

# 继承了Pydantic的BaseModel
class Creature(BaseModel):
    # 数据类型提示
    name: str
    country: str
    area: str
    description: str
    aka: str

thing = Creature(
    name="yeti",
    country="CN",
    area="Himalayas",
    description="Hirsute Himalayan",
    aka="Abominable Snowman")

print("Name is", thing.name)
from model import Creature

# 调用了刚才制造的模型
_creatures: list[Creature] = [
    Creature(name="yeti",
             country="CN",
             area="Himalayas",
             description="Hirsute Himalayan",
             aka="Abominable Snowman"
             ),
    Creature(name="sasquatch",
             country="US",
             area="*",
             description="Yeti's Cousin Eddie",
             aka="Bigfoot")
]

def get_creatures() -> list[Creature]:
    return _creatures
from model import Creature
from fastapi import FastAPI

app = FastAPI()

@app.get("/creature")
def get_all() -> list[Creature]:
    from data import get_creatures
    return get_creatures()


if __name__ == "__main__":
    import uvicorn
    uvicorn.run("web:app", reload=True)
# 生成的响应内容
# HTTP/1.1 200 OK
# date: Mon, 31 Mar 2025 02:44:07 GMT
# server: uvicorn
# content-length: 211
# content-type: application/json
#
# [
#   {
#     "name": "yeti",
#     "country": "CN",
#     "area": "Himalayas",
#     "description": "Hirsute Himalayan",
#     "aka": "Abominable Snowman"
#   },
#   {
#     "name": "sasquatch",
#     "country": "US",
#     "area": "*",
#     "description": "Yeti's Cousin Eddie",
#     "aka": "Bigfoot"
#   }
# ]

# FastAPI将自动将对象列表转化成JSON字符串返回
# 服务器端输出 :INFO:     127.0.0.1:61968 - "GET /creature HTTP/1.1" 200 OK

类型验证

# 错误的初始化从而引发报错
from model import Creature

dragon = Creature(
    name="dragon",
    # 会报错 因为根据上面的定义 description是一个str
    description=["incorrect", "string", "list"],
    country="*"
    )

数值验证

对于不同的数据类型可以添加一些限制

整数或浮点数(conint)

  • gt 大于

  • lt 小于

  • ge 大于等于

  • le 小于等于

  • multiple_of 一个数值的整数倍

字符串(constr)

  • min_length 最小长度

  • max_length 最大长度

  • to_upper 将内容转成大写

  • to_lower 将内容转成小写

  • regex 符合某一正则表达式

元组 列表 集合

  • min_items 最小原件数

  • max_items 最大元件数

from pydantic import BaseModel, constr

class Creature(BaseModel):
    # constr意味着使用了一个受限制的字符串
	name: constr(min_length=2)
    # name: str = Field(..., min_length=2)
    # Field()这个字段是必须的,并且没有默认值。
	country: str
	area: str
	description: str
	aka: str
# 会报错 因为name字段要求min_length=2
bad_creature = Creature(name="!",
description="it's a raccoon",
area="your attic")

类型转换

路径函数可以返回任何内容,就算是JSON不支持的日期对象,路径函数也通过内部函数将其转化成可以通过JSON返回的数据

# 一个检验方法
import datetime
import json
import pytest
from fastapi.encoders import jsonable_encoder

@pytest.fixture
def data():
    return datetime.datetime.now()

# 用于证明日期对象是没法直接转化成JSON对象的
# 断言接下来的代码会抛出Exception异常
def test_json_dump(data):
    with pytest.raises(Exception):
        _ = json.dumps(data)

def test_encoder(data):
    # 都能正常转换 返回非空字符串
    # 在python中被视为ture
    out = jsonable_encoder(data)
    assert out
    json_out = json.dumps(out)
    assert json_out
    # 所以assert都能通过

依赖注入

将函数需要的任何特定信息传递到函数中,然后调用它来获取特定的数据。你web处理函数不会直接调用依赖项;它在函数调用时处理。依赖项需要可被执行(callable),可以是函数和类

简单例子

from fastapi import FastAPI, Depends, Query

app = FastAPI()

# 依赖方程
# Query(None) 表示参数是可选的.Query(...)代表参数是必须的
def user_dep(name: str = Query(None), password: str = Query(None)):
    return {"name": name, "valid": True}

# 同时也是一个单路经依赖示例
@app.get("/user")
def get_user(user: dict = Depends(user_dep)) -> dict:
    return user

# /user?name=Simon&password=secret 时,服务会返回 {"name": "Simon", "valid": true}

依赖范围

可以定义依赖关系来覆盖单个路径函数、一组路径函数或整个web应用程序。

单路径依赖
# 单路经示例二
# 依赖函数检查查询的name参数有没有缺失 不返回值
from fastapi import FastAPI, Depends, Query, HTTPException

app = FastAPI()

# the dependency function:
def check_dep(name: str = Query(None), password: str = Query(None)):
    if not name:
         # 进行异常处理
        raise HTTPException(status_code=400, detail="Name is required")
        
# the path function / web endpoint:
@app.get("/check_user", dependencies=[Depends(check_dep)])
def check_user() -> bool:
    return True
多路径依赖
# 一个简单的多路径依赖函数
from fastapi import Depends, APIRouter

def depfunc():
    pass
# ... 表示有其他参数被省略(如前缀、标签等)
# dependencies=[Depends(depfunc)] 将 depfunc 设置为此路由器下所有路由的依赖项
router = APIRouter(..., dependencies=[Depends(depfunc)])
全局依赖
from fastapi import FastAPI, Depends
# 定义了两个依赖项
def depfunc1():
    pass
 
def depfunc2():
    pass

# 将这两个依赖定义为全局依赖
# 通过在 FastAPI() 构造函数中传递 dependencies 参数,这些依赖函数将应用于应用中的所有路由。
app = FastAPI(dependencies=[Depends(depfunc1), Depends(depfunc2)])

@app.get("/main")
def get_main():
    pass
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值