使用 FastAPI 和 MongoDB 构建 CRUD 应用程序
在本教程中,您将学习如何使用 FastAPI 和 MongoDB 开发异步 API。我们将使用 Motor 包与 MongoDB 进行异步交互。
目标
在本教程结束时,您将能够:
- 使用 Python 和 FastAPI 开发 RESTful API
- 与MongoDB异步交互
- 使用 MongoDB Atlas 在云中运行 MongoDB
- 将 FastAPI 应用部署到 Heroku
初始设置
首先创建一个新文件夹来保存名为“fastapi-mongo”的项目:
$ mkdir fastapi-mongo
$ cd fastapi-mongo
接下来,创建并激活虚拟环境:
$ python3.9 -m venv venv
$ source venv/bin/activate
$ export PYTHONPATH=$PWD
随意将虚拟环境与Pip换成 Poetry或Pipenv。有关详细信息,请查看 Modern Python Environments.
接下来,创建以下文件和文件夹:
├── app
│ ├── __init__.py
│ ├── main.py
│ └── server
│ ├── app.py
│ ├── database.py
│ ├── models
│ └── routes
└── requirements.txt
将以下依赖项添加到*您的requirements.txt 文件中:
fastapi==0.73.0
uvicorn==0.17.4
安装它们:
(venv)$ pip install -r requirements.txt
在 app/main.py 文件中,定义用于运行应用程序的入口点:
import uvicorn
if __name__ == "__main__":
uvicorn.run("server.app:app", host="0.0.0.0", port=8000, reload=True)
在这里,我们指示文件在端口 8000 上运行 Uvicorn 服务器,并在每次文件更改时重新加载。
在通过入口点文件启动服务器之前,请在 app/server/app.py 中创建基本路由:
from fastapi import FastAPI
app = FastAPI()
@app.get("/", tags=["Root"])
async def read_root():
return {"message": "Welcome to this fantastic app!"}
Tags是用于对路由进行分组的标识符。具有相同标签的路由将分组到 API 文档的一个部分中。
从控制台运行入口点文件:
(venv)$ python app/main.py
导航到浏览器中的 http://localhost:8000。您应该看到:
{
"message": "Welcome to this fantastic app!"
}
您还可以在以下位置查看交互式 API 文档 http://localhost:8000/docs:
路线
我们将构建一个简单的应用程序,用于使用以下 CRUD 路由存储student数据:
在我们深入研究编写路由之前,让我们首先定义相关模式并配置 MongoDB。
图式
让我们定义数据将基于的模式,它将表示数据在MongoDB数据库中的存储方式。
Pydantic Schema用于验证数据以及序列化(JSON -> Python)和反序列化(Python -> JSON)。换句话说,它不用作 Mongo schema validator。
在“应用/服务器/模型”文件夹中,创建一个名为 student.py 的新文件:
from typing import Optional
from pydantic import BaseModel, EmailStr, Field
class StudentSchema(BaseModel):
fullname: str = Field(...)
email: EmailStr = Field(...)
course_of_study: str = Field(...)
year: int = Field(..., gt=0, lt=9)
gpa: float = Field(..., le=4.0)
class Config:
schema_extra = {
"example": {
"fullname": "John Doe",
"email": "jdoe@x.edu.ng",
"course_of_study": "Water resources engineering",
"year": 2,
"gpa": "3.0",
}
}
class UpdateStudentModel(BaseModel):
fullname: Optional[str]
email: Optional[EmailStr]
course_of_study: Optional[str]
year: Optional[int]
gpa: Optional[float]
class Config:
schema_extra = {
"example": {
"fullname": "John Doe",
"email": "jdoe@x.edu.ng",
"course_of_study": "Water resources and environmental engineering",
"year": 4,
"gpa": "4.0",
}
}
def ResponseModel(data, message):
return {
"data": [data],
"code": 200,
"message": message,
}
def ErrorResponseModel(error, code, message):
return {"error": error, "code": code, "message": message}
在上面的代码中,我们定义了一个名为 Pydantic Schema,它表示student数据将如何存储在 MongoDB 数据库中。StudentSchema
在 Pydantic 中,ellipsis表示字段是必需的。它可以替换为默认值或默认值。在 中,每个字段都有一个省略号,因为每个字段都很重要,程序不应在没有设置值的情况下继续。...``None``StudentSchema
在 的 和 字段中,我们添加了validators 、 和 :gpa``year``StudentSchema``gt``lt``le
gt
并在字段中确保传递的值大于 0 且小于 9。因此,0、10、11 等值将导致错误。lt``year
le
字段中的验证器可确保传递的值小于或等于 4.0。gpa
此架构将帮助用户以适当的形状向 API 发送 HTTP 请求,即要发送的数据类型以及如何发送。
FastAPI 使用 Pyantic Schemas 与 Json Schema 一起自动记录数据模型。然后,Swagger UI 从生成的数据模型呈现数据。您可以在此处阅读有关 FastAPI 如何生成 API 文档的更多信息。
由于我们使用了 ,我们需要安装 email-validator.EmailStr
将其添加到需求文件:
pydantic[email]
安装:
(venv)$ pip install -r requirements.txt
有了模式,让我们在为 API 编写路由之前设置 MongoDB。
MongoDB数据库
在本节中,我们将连接MongoDB并配置我们的应用程序以与之通信。
根据维基百科,MongoDB是一个跨平台的面向文档的数据库程序。被归类为NoSQL数据库程序,MongoDB使用具有可选模式的类似JSON的文档。
MongoDB数据库设置
如果您的机器上没有安装 MongoDB,请参阅文档中的安装指南。安装后,继续按照指南运行 mongod 守护进程。完成后,您可以通过 shell 命令连接到实例来验证 MongoDB 是否已启动并正在运行:mongo
$ mongo
作为参考,本教程使用 MongoDB 社区版 v5.0.6。
$ mongo --version
MongoDB shell version v5.0.6
Build Info: {
"version": "5.0.6",
"gitVersion": "212a8dbb47f07427dae194a9c75baec1d81d9259",
"modules": [],
"allocator": "system",
"environment": {
"distarch": "x86_64",
"target_arch": "x86_64"
}
}
电机设置
接下来,我们将配置 Motor(一个异步 MongoDB 驱动程序)以与数据库进行交互。
首先将依赖项添加到需求文件:
motor==2.5.1
安装:
(venv)$ pip install -r requirements.txt
返回应用,将数据库连接信息添加到应用/服务器/数据库.py:
import motor.motor_asyncio
MONGO_DETAILS = "mongodb://localhost:27017"
client = motor.motor_asyncio.AsyncIOMotorClient(MONGO_DETAILS)
database = client.students
student_collection = database.get_collection("students_collection")
在上面的代码中,我们导入了 Motor,定义了连接详细信息,并通过 AsyncIOMotorClient 创建了一个客户端。
然后,我们引用了一个名为的数据库和一个名为 的集合(类似于关系数据库中的表)。由于这些只是引用,而不是实际的 I/O,因此两者都不需要表达式。执行第一个 I/O 操作时,将创建数据库和集合(如果它们尚不存在)。students``students_collection``await
接下来,创建一个快速帮助程序函数,用于将数据库查询的结果解析为 Python 字典。
将此也添加到 database.py 文件中:
import motor.motor_asyncio
MONGO_DETAILS = "mongodb://localhost:27017"
client = motor.motor_asyncio.AsyncIOMotorClient(MONGO_DETAILS)
database = client.students
student_collection = database.get_collection("students_collection")
# helpers
def student_helper(student) -> dict:
return {
"id": str(student["_id"]),
"fullname": student["fullname"],
"email": student["email"],
"course_of_study": student["course_of_study"],
"year": student["year"],
"GPA": student["gpa"],
}
接下来,让我们编写 CRUD 数据库操作。
数据库 CRUD 操作
首先从 database.py 文件顶部的 bson 包导入方法:ObjectId
from bson.objectid import ObjectId
bson 作为电机的附件安装。
接下来,为 CRUD 操作添加以下每个函数:
# Retrieve all students present in the database
async def retrieve_students():
students = []
async for student in student_collection.find():
students.append(student_helper(student))
return students
# Add a new student into to the database
async def add_student(student_data: dict) -> dict:
student = await student_collection.insert_one(student_data)
new_student = await student_collection.find_one({"_id": student.inserted_id})
return student_helper(new_student)
# Retrieve a student with a matching ID
async def retrieve_student(id: str) -> dict:
student = await student_collection.find_one({"_id": ObjectId(id)})
if student:
return student_helper(student)
# Update a student with a matching ID
async def update_student(id: str, data: dict):
# Return false if an empty request body is sent.
if len(data) < 1:
return False
student = await student_collection.find_one({"_id": ObjectId(id)})
if student:
updated_student = await student_collection.update_one(
{"_id": ObjectId(id)}, {"$set": data}
)
if updated_student:
return True
return False
# Delete a student from the database
async def delete_student(id: str):
student = await student_collection.find_one({"_id": ObjectId(id)})
if student:
await student_collection.delete_one({"_id": ObjectId(id)})
return True
在上面的代码中,我们定义了异步操作,通过 motor 创建、读取、更新和删除数据库中的student数据。
在更新和删除操作中,在数据库中搜索student以决定是否执行该操作。返回值指导如何向用户发送响应,我们将在下一节中处理这些响应。
CRUD Routes
在本节中,我们将添加路由以补充数据库文件中的数据库操作。
在“routes”文件夹中,创建一个名为 student.py 的新文件,并向其添加以下内容:
from fastapi import APIRouter, Body
from fastapi.encoders import jsonable_encoder
from app.server.database import (
add_student,
delete_student,
retrieve_student,
retrieve_students,
update_student,
)
from app.server.models.student import (
ErrorResponseModel,
ResponseModel,
StudentSchema,
UpdateStudentModel,
)
router = APIRouter()
我们将使用 FastAPI 中的 JSON 兼容编码器将我们的模型转换为与 JSON Compatible Encoder的格式。
接下来,在应用程序/服务器/应用程序.py中连接student路由:
from fastapi import FastAPI
from app.server.routes.student import router as StudentRouter
app = FastAPI()
app.include_router(StudentRouter, tags=["Student"], prefix="/student")
@app.get("/", tags=["Root"])
async def read_root():
return {"message": "Welcome to this fantastic app!"}
创造
返回路由文件,添加以下处理程序以创建新student:
@router.post("/", response_description="Student data added into the database")
async def add_student_data(student: StudentSchema = Body(...)):
student = jsonable_encoder(student)
new_student = await add_student(student)
return ResponseModel(new_student, "Student added successfully.")
因此,路由需要与 格式匹配的有效负载。例:StudentSchema
{
"fullname": "John Doe",
"email": "jdoe@x.edu.ng",
"course_of_study": "Water resources engineering",
"year": 2,
"gpa": "3.0",
}
启动 Uvicorn 服务器:
(venv)$ python app/main.py
并在 http://localhost:8000/docs 刷新交互式 API 文档页面以查看新路由:
也测试一下:
因此,当请求发送到终结点时,它会在调用数据库方法并将响应存储在变量中之前,将 JSON 编码的请求正文存储在变量中。然后通过 返回来自数据库的响应。student``add_student``new_student``ResponseModel
还要测试验证器:
- 年份必须大于 0 且小于 10
- GPA 必须小于或等于 4.0
读
添加以下routes以检索所有student和单个student:
@router.get("/", response_description="Students retrieved")
async def get_students():
students = await retrieve_students()
if students:
return ResponseModel(students, "Students data retrieved successfully")
return ResponseModel(students, "Empty list returned")
@router.get("/{id}", response_description="Student data retrieved")
async def get_student_data(id):
student = await retrieve_student(id)
if student:
return ResponseModel(student, "Student data retrieved successfully")
return ErrorResponseModel("An error occurred.", 404, "Student doesn't exist.")
如果您没有传入有效的 ObjectId(例如,)for ID 以检索单个student路由,会发生什么情况?如何在应用程序中更好地处理此问题?
1
实现删除操作后,你将有机会测试空数据库的响应。
更新
接下来,编写用于更新student数据的单个路由:
@router.put("/{id}")
async def update_student_data(id: str, req: UpdateStudentModel = Body(...)):
req = {k: v for k, v in req.dict().items() if v is not None}
updated_student = await update_student(id, req)
if updated_student:
return ResponseModel(
"Student with ID: {} name update is successful".format(id),
"Student name updated successfully",
)
return ErrorResponseModel(
"An error occurred",
404,
"There was an error updating the student data.",
)
删除
最后,添加删除路由:
@router.delete("/{id}", response_description="Student data deleted from the database")
async def delete_student_data(id: str):
deleted_student = await delete_student(id)
if deleted_student:
return ResponseModel(
"Student with ID: {} removed".format(id), "Student deleted successfully"
)
return ErrorResponseModel(
"An error occurred", 404, "Student with id {0} doesn't exist".format(id)
)
检索之前创建的用户的 ID 并测试删除路由:
删除所有剩余的student并再次测试读取路由,确保响应适用于空数据库。
部署
在本节中,我们将应用程序部署到 Heroku 并为 MongoDB 配置云数据库。
MongoDB Atlas
在部署之前,我们需要设置 MongoDB Atlas,这是一个用于 MongoDB 的云数据库服务,用于托管我们的数据库。
按照入门指南操作,您将在其中创建账户、部署免费套餐集群、设置用户并将 IP 地址列入白名单。
出于测试目的,请使用列入白名单的 IP 以允许从任何地方访问。对于生产应用,需要限制对静态 IP 的访问。
0.0.0.0/0
完成后,通过单击“连接”按钮从集群中获取数据库连接信息:
单击第二个选项“连接到您的应用程序”:
复制连接 URL,确保更新密码。将默认数据库也设置为“student”。它看起来类似于:
mongodb+srv://foobar:foobar@cluster0.0reol.mongodb.net/students?retryWrites=true&w=majority
我们将定义它有一个环境变量,而不是在我们的应用中硬编码此值。在项目根目录中创建一个名为 .env 的新文件及其连接信息:
MONGO_DETAILS=your_connection_url
确保替换为复制的 URL。
your_connection_url
接下来,为了简化应用中环境变量的管理,让我们安装 Python 解耦包。将其添加到您的需求文件中,如下所示:
python-decouple==3.6
安装:
(venv)$ pip install -r requirements.txt
在应用程序/服务器/数据库.py文件中,导入库:
from decouple import config
导入的方法扫描根目录以查找 .env 文件,并读取传递给该文件的内容。因此,在我们的例子中,它将读取变量。config``MONGO_DETAILS
接下来,将变量更改为:MONGO_DETAILS
MONGO_DETAILS = config("MONGO_DETAILS") # read environment variable
本地测试
在部署之前,让我们使用云数据库在本地测试应用,以确保正确配置连接。重新启动您的 Uvicorn 服务器,并从 http://localhost:8000/docs 的交互式文档中测试每个路由。
您应该能够在 Atlas 仪表板上看到数据:
部署到 Heroku
最后,让我们将应用部署到 Heroku。
Heroku 是一个云平台即服务 (PaaS),用于部署和扩展应用程序。
如有必要,请注册一个 Heroku 帐户并安装 Heroku CLI。
在继续之前,请在项目中创建一个 .gitignore 文件,以防止将 “venv” 文件夹和 .env 文件签入到 git:
(venv)$ touch .gitignore
添加以下内容:
.env
venv/
__pycache__
接下来,将 Procfile 添加到项目的根目录:
web: uvicorn app.server.app:app --host 0.0.0.0 --port=$PORT
笔记:
- Procfile 是一个文本文件,位于项目的根目录下,用于指导 Heroku 如何运行您的应用程序。由于我们正在提供 Web 应用程序,因此我们定义了进程类型以及提供 Uvicorn 的命令。
web
- Heroku 动态公开一个端口,供应用在部署时运行,该端口通过环境变量公开。
$PORT
您的项目现在应具有以下文件和文件夹:
├── .env
├── .gitignore
├── Procfile
├── app
│ ├── __init__.py
│ ├── main.py
│ └── server
│ ├── app.py
│ ├── database.py
│ ├── models
│ │ └── student.py
│ └── routes
│ └── student.py
└── requirements.txt
在项目的根目录中,初始化一个新的 git 存储库:
(venv)$ git init
(venv)$ git add .
(venv)$ git commit -m "My fastapi and mongo application"
现在,我们可以在 Heroku 上创建一个新应用程序:
(venv)$ heroku create
除了创建新应用程序外,此命令还会在 Heroku 上创建一个远程 git 存储库,供我们推送应用程序以进行部署。然后,它会自动将其设置为本地存储库上的远程数据库。
您可以通过运行 来验证远程是否已设置。
git remote -v
记下应用程序的 URL。
由于我们没有将 .env 文件添加到 git 中,我们需要在 Heroku 环境中设置环境变量:
(venv)$ heroku config:set MONGO_DETAILS="your_mongo_connection_url"
同样,请确保替换为实际连接 URL。
your_connection_url
将您的代码推送到 Heroku,并确保至少有一个应用程序实例正在运行:
(venv)$ git push heroku master
(venv)$ heroku ps:scale web=1
运行以在默认浏览器中打开应用。heroku open
您已成功将应用程序部署到 Heroku。测试一下。
结论
在本教程中,您学习了如何使用 FastAPI 和 MongoDB 创建 CRUD 应用程序并将其部署到 Heroku。通过查看本教程开头的目标来执行快速自检。您可以在 GitHub 上找到本教程中使用的代码。
寻找更多?
- 使用 pytest 设置单元测试和集成测试。
- 添加其他路由。
- 为应用程序创建 GitHub 存储库,并使用 GitHub 操作配置 CI/CD。
- 使用Fixie Socks在Heroku上配置静态IP,并限制对MongoDB Atlas数据库的访问。
- 查看使用 FastAPI、MongoDB 和 Beanie 构建 CRUD 应用程序教程,了解如何利用 Beanie ODM,它为 Motor 提供了一个额外的抽象层,可以更轻松地与 Mongo 数据库中的集合进行交互。
查看使用 FastAPI 和 Docker 进行测试驱动开发课程,了解有关为 FastAPI 应用测试和设置 CI/CD 的更多信息。
。测试一下。
结论
在本教程中,您学习了如何使用 FastAPI 和 MongoDB 创建 CRUD 应用程序并将其部署到 Heroku。通过查看本教程开头的目标来执行快速自检。您可以在 GitHub 上找到本教程中使用的代码。
寻找更多?
- 使用 pytest 设置单元测试和集成测试。
- 添加其他路由。
- 为应用程序创建 GitHub 存储库,并使用 GitHub 操作配置 CI/CD。
- 使用Fixie Socks在Heroku上配置静态IP,并限制对MongoDB Atlas数据库的访问。
- 查看使用 FastAPI、MongoDB 和 Beanie 构建 CRUD 应用程序教程,了解如何利用 Beanie ODM,它为 Motor 提供了一个额外的抽象层,可以更轻松地与 Mongo 数据库中的集合进行交互。
查看使用 FastAPI 和 Docker 进行测试驱动开发课程,了解有关为 FastAPI 应用测试和设置 CI/CD 的更多信息。