使用 FastAPI 和 MongoDB 构建 CRUD 应用程序

使用 FastAPI 和 MongoDB 构建 CRUD 应用程序

在本教程中,您将学习如何使用 FastAPIMongoDB 开发异步 API。我们将使用 Motor 包与 MongoDB 进行异步交互。

目标

在本教程结束时,您将能够:

  1. 使用 Python 和 FastAPI 开发 RESTful API
  2. 与MongoDB异步交互
  3. 使用 MongoDB Atlas 在云中运行 MongoDB
  4. 将 FastAPI 应用部署到 Heroku

初始设置

首先创建一个新文件夹来保存名为“fastapi-mongo”的项目:

$ mkdir fastapi-mongo
$ cd fastapi-mongo

接下来,创建并激活虚拟环境:

$ python3.9 -m venv venv
$ source venv/bin/activate
$ export PYTHONPATH=$PWD

随意将虚拟环境与Pip换成 PoetryPipenv。有关详细信息,请查看 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:

Fastapi Swagger UI

路线

我们将构建一个简单的应用程序,用于使用以下 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

  1. gt并在字段中确保传递的值大于 0 且小于 9。因此,01011 等值将导致错误。lt``year
  2. le字段中的验证器可确保传递的值小于或等于 4.0gpa

此架构将帮助用户以适当的形状向 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

还要测试验证器:

  1. 年份必须大于 0 且小于 10
  2. 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

完成后,通过单击“连接”按钮从集群中获取数据库连接信息:

mongo德布地图集

单击第二个选项“连接到您的应用程序”:

蒙戈德布地图集

复制连接 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

笔记:

  1. Procfile 是一个文本文件,位于项目的根目录下,用于指导 Heroku 如何运行您的应用程序。由于我们正在提供 Web 应用程序,因此我们定义了进程类型以及提供 Uvicorn 的命令。web
  2. 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 上找到本教程中使用的代码。

寻找更多?

  1. 使用 pytest 设置单元测试和集成测试。
  2. 添加其他路由。
  3. 为应用程序创建 GitHub 存储库,并使用 GitHub 操作配置 CI/CD。
  4. 使用Fixie Socks在Heroku上配置静态IP,并限制对MongoDB Atlas数据库的访问。
  5. 查看使用 FastAPI、MongoDB 和 Beanie 构建 CRUD 应用程序教程,了解如何利用 Beanie ODM,它为 Motor 提供了一个额外的抽象层,可以更轻松地与 Mongo 数据库中的集合进行交互。

查看使用 FastAPI 和 Docker 进行测试驱动开发课程,了解有关为 FastAPI 应用测试和设置 CI/CD 的更多信息。

。测试一下。

结论

在本教程中,您学习了如何使用 FastAPI 和 MongoDB 创建 CRUD 应用程序并将其部署到 Heroku。通过查看本教程开头的目标来执行快速自检。您可以在 GitHub 上找到本教程中使用的代码。

寻找更多?

  1. 使用 pytest 设置单元测试和集成测试。
  2. 添加其他路由。
  3. 为应用程序创建 GitHub 存储库,并使用 GitHub 操作配置 CI/CD。
  4. 使用Fixie Socks在Heroku上配置静态IP,并限制对MongoDB Atlas数据库的访问。
  5. 查看使用 FastAPI、MongoDB 和 Beanie 构建 CRUD 应用程序教程,了解如何利用 Beanie ODM,它为 Motor 提供了一个额外的抽象层,可以更轻松地与 Mongo 数据库中的集合进行交互。

查看使用 FastAPI 和 Docker 进行测试驱动开发课程,了解有关为 FastAPI 应用测试和设置 CI/CD 的更多信息。

原文链接

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值