如果你读过我的其他文章,那么你就会知道我非常喜欢FastAPI。我用了好几年Django,其他框架也没有能将它从第一名位置取代,不过FastAPI确实很特别。
当你使用FastAPI时,有几个方面需要注意,以便更好地推进项目。
关于人生大事FastAPI 可以在启动应用程序时执行一组任务,并在应用程序停止时执行另一组任务。有点像中间件,但它包裹的是整个应用程序,而不是请求。
这原本是通过 .on_event()
参数来选择启动或关机的。
from fastapi import FastAPI
app = FastAPI()
@app.on_event("startup")
def startup_event():
with open("log.txt", mode="a") as log:
log.write("应用程序已启动")
@app.on_event("shutdown")
def shutdown_event():
with open("log.txt", mode="a") as log:
log.write("应用程序已关闭")
这个系统虽然还在,但正在被废弃,你可以继续使用它,但他们添加了一个新版本,所以你只能用新版本,不能同时使用两个版本。
新系统允许你创建一个上下文管理器,并将其作为参数传递给 FastAPI() 对象。如果你不了解的话,上下文管理器允许在代码块执行前和执行后运行代码,例如在打开和关闭数据库连接前。
from fastapi import FastAPI, Request
import settings
from contextlib import asynccontextmanager
from typing import AsyncGenerator
from tortoise.contrib.fastapi import RegisterTortoise
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
# 应用初始化完成
async with RegisterTortoise(
app,
db_url=settings.db_url,
modules=settings.db_modules,
generate_schemas=True,
add_exception_handlers=True,
):
# 数据库连接
yield
# 应用清理完成
main_app = FastAPI(lifespan=lifespan)
所以利用这个方法,启动时的任务会在yield
前插入,而关闭时的任务会在yield
后插入。这是一个相当巧妙的想法。比如,我在应用程序启动时注册了Tortoise ORM。移除是自动的,因此无需执行任何关闭操作。
这一步很重要,因为FastAPI本身并没有自带任何数据库相关的ORM和连接。在这里你需要在生命周期中添加这些东西。此外,因为这个函数存在于应用的生命周期里,你可以在这里启动一些后台异步任务。
请注意,你不能同时使用这两个系统。如果你设置了生命周期参数,所有 on_event()
请求将被忽略。也值得一提的是,on_event()
已被弃用(deprecated),所以如果你正在使用它,你可能需要快速重新编码。
虽然这不完全是FastAPI的一部分,但对于所有的web应用来说真的非常必要。我们需要一个地方来存放应用设置,而不能把它们放在你的主应用文件里。将设置放在应用文件中会导致循环导入问题。你需要把它们放在一个单独的文件里。我建议使用python的dotenv库,这样你的敏感信息应该存放在.env文件中,而不是和你的代码一起提交到版本控制仓库。
从 dotenv 导入 find_dotenv, dotenv_values as dotenv_values
导入 pathlib
导入 string
file_path = pathlib.Path().cwd() # 获取当前工作目录路径
static_dir = str(pathlib.Path(pathlib.Path().cwd(), "static")) # 获取静态文件目录路径
config = dotenv_values(find_dotenv(".test_fastapi_config.env")) # 从配置文件中获取数据库配置信息
db_user = config.get("DB_USER")
db_password = config.get("DB_PASSWORD")
db_host = config.get("DB_HOST")
db_name = config.get("DB_NAME")
db_port = config.get("DB_PORT")
db_url = f"asyncpg://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}" # 数据库连接字符串
db_modules = {"models": ["models"]} # 数据库模块
max_age = 3600 # 最大缓存时间 (单位: 秒)
中间件,也就是软件中间件
如果你是从像Django这样的全栈框架过来的,那么中间件(middleware)通常是别人写的东西。但在FastAPI中则不然。虽然有一些中间件模块可用,但并不多。文档中还提到了从Starlette来的中间件函数。我试过了,但它们不起作用。不过编写中间件其实非常简单。FastAPI确实做到了这一点。
这里有一个简单却实用的例子。我决定模仿Django的会话机制,其中会话数据存放在数据库而非cookie中,所以cookie仅指向数据库记录。
from fastapi import FastAPI, Request
main_app = FastAPI(lifespan=lifespan)
@main_app.middleware("http")
async def session_middleware(request: Request, call_next):
cookie_val = request.cookies.get("session")
if cookie_val:
request.scope['session'] = cookie_val
else:
request.scope['session'] = "".join(random.choices(settings.session_choices, k=128))
response = await call_next(request)
response.set_cookie("session", value=request.session,
max_age=settings.max_age, httponly=True)
return response
如你所见,中间件函数接收请求并调用 call_next。call_next 实际上就是视图函数,因此在调用 call_next 之前的代码会在视图运行之前执行。响应对象从这里返回,因此你可以在发送给用户之前对其进行修改。
这里我所做的仅仅是获取或生成会话密钥,并确保在视图调用前将其放入 cookie,在响应返回时确保它在 cookie 中。
异常处理这是一个非常棒的功能,特别需要留意。你可以添加监听器来监听 Python 异常,并通过视图向用户返回响应。这样你在处理异常时就不会发送 http 500 状态码了。
from fastapi import FastAPI, Request
app = FastAPI()
class PermissionFailedException(Exception):
def __init__(self, permissions: list):
self.permissions = permissions
def permission_failed_handler(request: Request, exc: PermissionFailedException):
"""当发生PermissionFailedException时,显示一个错误页面"""
return HTMLResponse(content="templates/permission_failed.html", status_code=401)
app.add_exception_handler(PermissionFailedException, permission_failed_handler)
这虽然非常适合捕捉意外情况,但与依赖注入结合使用时效果最佳。如果你定义了全局依赖、路由依赖或装饰器依赖,它们不能返回任何值。它只能成功完成或抛出异常。为了解决这个问题,你可以创建自定义异常,并编写异常处理器来处理重定向等情形。
基于依赖注入 基于依赖注入(Note: After reviewing the expert suggestions, the term "依赖注入" is more appropriate and concise for the title. However, since the format in the provided translation includes a hashtag which implies it's maintaining the header style from the source text, I've kept the hashtag for consistency in the output. Thus, the translation remains "依赖注入", but presented twice due to the source text format.)
文档里对这些内容讲得很详细,但有时会让人有点摸不着头脑。其中一个就是我刚才提到的依赖项。依赖项可以设置在很多地方。如果你把依赖项放在视图路径里,它就需要返回一些结果。
在其他地方,结果要么成功要么失败。我一开始觉得这有点没用,但当你把它和异常处理结合时,你会发现它有很多用武之地。
最好是把它看作有两个不同的依赖注入系统。一个用来准备数据,另一个在视图运行前进行验证操作。可以这么想,这两个系统分别处理不同的任务。
后台任务如果你有长时间运行的任务需要在后台任务管理器中执行,或许可以这么做,但你需要明白其中的情况。
在大多数系统中,请求进来,开始处理任务,然后返回响应,任务完成。由于 FastAPI 的异步特性,处理任务的方式略有不同。“return” 语句会返回一个响应对象,格式化响应并发送给用户,但 FastAPI 会继续检查是否有任何异步任务尚未完成。它不会在这些异步任务完成之前结束处理。这意味着在用户收到响应后,这些任务仍可以继续运行。
from fastapi import BackgroundTasks, FastAPI
from asyncio import sleep
app = FastAPI()
def write_notification(email: str, message=""):
await sleep(1)
with open("log.txt", mode="w") as email_file:
content = f"为 {email} 发送的 {message} 通知,在用户响应后发送"
email_file.write(content)
@app.post("/send-notification/{email}")
async def send_notification(email: str, background_tasks: BackgroundTasks):
background_tasks.add_task(write_notification, email, message="通知")
return {"message": "回复在通知完成之前发送出去"}
一个简单的例子,但它展示了发生了什么。“background_tasks.add_task()”启动了一个asyncio任务,然后运行了return。然而,return调用默认的响应对象,这会将响应发送给用户,然后等待背景任务完成,再等待协程完成。这样的任务,比如发送邮件、将文件写入磁盘或进行大量数据库更新,可以放入后台任务中,在这些任务完成之前,响应就被发送给了用户。
记得你的回复已经发出去了,如果任务失败了,你还可以再发一条消息。
基础响应类有一个选项可以设置后台任务,但它的行为有一些差异。使用“background_tasks”时,用户的响应会被处理,中间件也会运行,并发送响应。而当使用请求对象时,中间件则会在任务完成之后才会运行。因此,如果你通过中间件来调整响应,这可能会对任务产生影响。
来学学 Pydantic 吧这点我怎么强调都不为过。仅凭对 Pydantic 的基本理解远远不够。还是建议你好好研究一下这个模块,它确实非常有用,可以为你的应用添加更多功能。
FastAPI 并不是 Starlette。在许多方面,我们会从Starlette中获取对象来使用。事实上,许多如请求类这样的对象实际上只是指向Starlette的引用。说到这里,不要以为所有的Starlette功能在FastAPI中都能正常工作。它们可能无法运行。
超越API的限制FastAPI 可以处理 HTML 响应,就像处理 API 一样。它包含处理 Jinja2 的模块,这些更像是简化的封装。添加任何你想要的模板引擎都很简单。
最后的思考FastAPI 的特别之处在于它包含了让你创建出色应用的所有元素,但不像Django,它没有预设的做事方式。另外,添加Django的那些功能模块或其中的一部分其实非常简单。
共同学习,写下你的评论
评论加载中...
作者其他优质文章