AI聊天无敏感词:技术原理与应用实践
FastAPI 手册 – 如何开发、测试和部署 API
欢迎来到 FastAPI 的世界,这是一个用于构建 Python API 的时尚高性能 Web 框架。如果您是 API 编程的新手,请不用担心——我们会从基础开始。
API(应用程序编程接口)连接多个软件程序,允许它们进行对话和交换信息。API 在现代软件开发中是必不可少的,因为它们是应用程序的后端架构。
阅读完本快速入门指南后,你将能够使用FastAPI和MongoDB开发一个课程管理API。最好的是,你不仅将编写API,还将对其进行测试和容器化。
在本实战项目中,我们将使用FastAPI(一个快速的Web框架)和MongoDB数据库(用于存储和检索课程信息)来创建一个Python后端系统。
该系统将允许用户访问课程详细信息、查看章节、对单个章节进行评分和汇总评分。
本项目旨在为具有基本编程知识和一些NoSQL知识的Python开发人员设计。对于MongoDB、Docker和PyTest的熟悉程度并不是必需的,因为我会在本项目的范围内重点介绍你需要了解的一切。
我们将构建的内容
以下是我们将要构建的内容:
FastAPI 后端:它将用作处理 API 请求和响应的接口。选择 FastAPI 是因为其易用性、性能和直观的设计。
MongoDB 数据库:用于存储课程信息的 NoSQL 数据库。MongoDB 的灵活架构允许我们将数据存储在类似 JSON 的文档中,使其适合此项目。
课程信息:用户将能够查看各种课程详细信息,例如课程名称、描述、讲师等。
章节详情:系统将提供有关课程中章节的信息,包括章节名称、描述和任何其他相关数据。
章节评分:用户将能够对单个章节进行评分。我们将实现记录和检索章节评级的功能。
课程综合评分:系统将根据每门课程章节的评分计算并显示每门课程的汇总评分。
本指南将展示如何设置开发环境、构建FastAPI后端、集成MongoDB、定义API端点、添加章节评分功能以及计算课程综合评分。它涵盖了项目的基本概念以及Python、MongoDB和NoSQL数据库的使用。
最终,这个实用的后端系统将管理章节详细信息、课程信息和用户评分,为复杂且有价值的项目奠定基础。
我们的目标是创建一个处理与课程相关查询的系统。必须根据请求从MongoDB中检索课程信息。最后,这些回答数据必须以标准格式(JSON)返回。
我们将从一个脚本开始,该脚本从courses.json中读取课程信息。这些数据将被存储在MongoDB实例中。一旦数据加载完成,我们的API代码就可以连接到这个数据库,以便轻松检索数据。
有趣的是,使用 FastAPI 创建多个端点。我们的 API 将能够:
- 获取所有课程的列表
- 显示全面的课程概述
- 列出有关某些章节的详细信息
- 记录每个章节的用户分数。
此外,对于每门课程,我们将汇总所有评论,为访问者提供有关课程受欢迎程度和质量的相关信息。
本教程专注于构建一个可扩展、高效且用户友好的API。完成所有测试后,我们将使用Docker将应用程序容器化。这将极大地简化部署、维护和安装过程。
API 方法
HTTP(超文本传输协议)方法指定要对资源执行的操作。以下是最常用的 API 开发方法:
GET:从服务器请求信息。当客户端提交 GET 请求时,它正在从服务器请求数据。
POST:将数据发送到服务器进行处理。当客户端提交 POST 请求时,它通常会将数据传送到服务器以创建或更新资源。
PUT:更新服务器数据。当客户端提交 PUT 请求时,请求中指示的资源将更新。
DELETE:发送 DELETE 请求的客户端请求删除指定的资源。
客户端和服务器
客户端通常是发送请求到服务器的前端应用程序,如网页浏览器或移动应用。而服务器则是负责处理客户端请求并作出相应响应的后端应用程序。
请求是客户端发送给服务器的通信,它指定了要执行的操作和任何所需的数据。请求包括HTTP方法、URL(统一资源定位符)、头部,以及在POST或PUT请求的情况下,还包括数据负载。
服务器收到请求后,会对其进行处理并返回响应。响应是服务器返回给客户端的消息,其中包含请求的数据或活动的结果。
响应通常包括一个HTTP状态码,指示请求的成功或失败,以及服务器返回给客户端的任何数据。
显示 API 工作原理的图表
如何设置 MongoDB 数据库
MongoDB是一种NoSQL数据库。它是非关系型的,并以集合和文档的形式保存信息。
从官方网站为您的操作系统安装MongoDB。
现在运行终端的命令以验证安装是否成功。运行mongosh命令应该得到以下输出:
运行 mongosh 命令应生成此输出
使用 MongoDB Compass 连接到 MongoDB 服务器。我建议您通过指定端口号、存储引擎、身份验证等设置来配置 MongoDB。
创建新的 MongoDB 连接
现在连接已经建立,下一步是创建一个数据库或“文档”。将这个数据库命名为“courses”。目前它对你来说还是空的。稍后我们会使用Python脚本来插入文档。
如何解析课程数据并将其插入 MongoDB
你可以逐个插入记录,但最好使用JSON文件来简化这个过程。从GitHub下载名为courses.json的文件。所有课程信息都包含在这个文件中(作为课程列表)。
具体来说,每门课程都有以下结构:
- 名字:课程的标题。
- 日期:创建日期作为 UNIX 时间戳。
- 描述:课程的描述。
- 域:课程域列表。
- 章:课程章节列表。每个章节都有一个标题名称和内容文本。
此项目需要一些 Python 包。
BSON
– MongoDB 中使用的二进制序列化格式,用于高效的数据存储和检索。它与 PyMongo 捆绑在一起。FastAPI
– 用于创建 Python API 的 Web 框架,这些 API 提供高性能、自动验证、交互式文档和异步操作支持。PyMongo
– 适用于 Python 的官方 MongoDB 驱动程序。它用作在 Python 中集成 MongoDB 的高级 API。Uvicorn
– 提高应用程序性能的主 ASGI 服务器。它负责服务器启动。Starlette
– ASGI 框架,为 FastAPI 提供支持,并允许快速原型开发。Pydantic
– 集成数据验证和解析库。我们需要它来创建交互式 API 文档,同时自动验证传入的请求数据并强制执行数据类型规则。
通过 pip 命令安装它们,如下所示:
pip install fastapi pymongo uvicorn starlette pydantic
现在,让我们编写一个Python脚本来将所有这些课程数据插入数据库,这样我们就可以开始构建API路由了。启动你的IDE,创建一个名为script.py的文件,并确保它与courses.json文件在同一个目录中。
"""
Script to parse course information from courses.json, create the appropriate databases and
collection(s) on a local instance of MongoDB, create the appropriate indices (for efficient retrieval)
and finally add the course data on the collection(s).
"""
import pymongo
import json
# Connect to MongoDB
client = pymongo.MongoClient("mongodb://localhost:27017/")
db = client["courses"]
collection = db["courses"]
# Read courses from courses.json
with open("courses.json", "r") as f:
courses = json.load(f)
# Create index for efficient retrieval
collection.create_index("name")
# add rating field to each course
for course in courses:
course['rating'] = {'total': 0, 'count': 0}
# add rating field to each chapter
for course in courses:
for chapter in course['chapters']:
chapter['rating'] = {'total': 0, 'count': 0}
# Add courses to collection
for course in courses:
collection.insert_one(course)
# Close MongoDB connection
client.close()
此脚本使用 JSON 文件中的课程信息填充 MongoDB 数据库。
它首先连接到本地MongoDB实例。它从名为courses.json的文件中读取课程数据,并为课程评分创建一个新字段。然后,它开发一个索引以加快数据检索速度。最后,将课程数据添加到MongoDB集合中。
这是一个用于管理数据库中课程数据的简单脚本。运行脚本后,courses.json中的所有记录应该都已插入到courses数据库中。切换到MongoDB Compass进行验证。
运行 python 脚本后,您应该能够在 courses 数据库中看到 JSON 项目
如何设计 FastAPI 端点
这些 API 端点提供了一种有效的方法来管理课程信息、检索课程详细信息,并允许用户交互以对章节进行评级。
我建议在编写代码之前,先设计 API 终端节点以及 HTTP 请求类型。这是一个很好的参考,并在编码过程中提供清晰度。
端点 | 请求类型 | 描述 |
/courses | GET | 获取所有可用课程的列表以及排序选项。 |
选项:按标题(升序)、日期(降序)或课程总评分(降序)排序。
支持基于域的可选筛选。
端点 | 请求类型 | 描述 |
---|---|---|
/courses | GET | 获取所有可用课程的列表。 |
/courses/{course_id} | GET | 通过课程ID获取特定课程的概述。 |
/courses/{course_id}/{chapter_id} | GET | 获取课程中特定章节的信息。 |
/courses/{course_id}/{chapter_id} | POST | 对课程中特定章节进行评分。 |
选项:好评 (1)、好评 (-1)。
每个课程的评分是汇总得出的。
好的,现在让我们深入API代码。创建一个全新的Python文件,并将其命名为main.py
。
import contextlib
from fastapi import FastAPI, HTTPException, Query
from pymongo import MongoClient
from bson import ObjectId
from fastapi.encoders import jsonable_encoder
app = FastAPI()
client = MongoClient('mongodb://localhost:27017/')
db = client['courses']
代码导入了必要的模块,并创建了名为app
的FastAPI类的活动实例。它还使用PyMongo库建立了到本地MongoDB数据库的连接,变量db
现在存储了对courses
文档的连接引用。
现在让我们更详细地讨论这些端点。
获取所有课程端点 ( – GET)
此端点允许您检索所有可用课程的列表。您可以根据不同的标准对课程进行排序,例如字母顺序(基于课程标题的升序)、日期(降序)或课程总评分(降序)。此外,我们还允许用户根据其域筛选课程。
@app.get('/courses')
def get_courses(sort_by: str = 'date', domain: str = None):
# set the rating.total and rating.count to all the courses based on the sum of the chapters rating
for course in db.courses.find():
total = 0
count = 0
for chapter in course['chapters']:
with contextlib.suppress(KeyError):
total += chapter['rating']['total']
count += chapter['rating']['count']
db.courses.update_one({'_id': course['_id']}, {'$set': {'rating': {'total': total, 'count': count}}})
# sort_by == 'date' [DESCENDING]
if sort_by == 'date':
sort_field = 'date'
sort_order = -1
# sort_by == 'rating' [DESCENDING]
elif sort_by == 'rating':
sort_field = 'rating.total'
sort_order = -1
# sort_by == 'alphabetical' [ASCENDING]
else:
sort_field = 'name'
sort_order = 1
query = {}
if domain:
query['domain'] = domain
courses = db.courses.find(query, {'name': 1, 'date': 1, 'description': 1, 'domain':1,'rating':1,'_id': 0}).sort(sort_field, sort_order)
return list(courses)
此代码在FastAPI应用程序中定义了一个端点,用于检索所有可用课程的列表。可以通过向/courses
URL发送HTTP GET请求来访问该端点。
装饰器附加到该函数上,并负责处理此操作:@app.get()
当向此端点发出请求时,代码首先通过计算每个课程中所有章节的评分总和来计算课程总评分。然后,它更新MongoDB数据库中每个课程的字段,包含计算出的总评分和评分计数。
接下来,代码根据查询参数确定排序模式。如果sort_by
设置为date
,则课程将按其创建日期的降序排序。如果设置为rating
,则课程将按其总评分的降序排序。否则,课程将按其名称的字母顺序升序排序。
如果提供了可选的查询参数domain
,则代码将基于指定的领域过滤课程。
最后,代码查询MongoDB数据库以检索相关的课程信息,包括课程名称、创建日期、描述、领域和评分。课程根据所选的排序模式进行排序,并以列表形式返回。
这是对代码的解释,但实际的API响应呢?在当前工作目录中,从终端运行以下命令:
uvicorn main:app --reload
Uvicorn 是一个 ASGI Web 服务器。您可以直接在本地计算机上与 API 终端节点交互,而无需任何外部服务器。运行上述命令时,您应该会看到一条成功消息,指出服务器已启动。
打开浏览器并在 URL 栏中输入 http://127.0.0.1:8000/courses
。您将看到的输出将是直接来自服务器的 JSON 响应。
验证第一个对象是否包含以下内容:
{
"name": "Introduction to Programming",
"date": 1659906000,
"description": "An introduction to programming using a language called Python. Learn how to read and write code as well as how to test and \"debug\" it. Designed for students with or without prior programming experience who'd like to learn Python specifically. Learn about functions, arguments, and return values (oh my!); variables and types; conditionals and Boolean expressions; and loops. Learn how to handle exceptions, find and fix bugs, and write unit tests; use third-party libraries; validate and extract data with regular expressions; model real-world entities with classes, objects, methods, and properties; and read and write files. Hands-on opportunities for lots of practice. Exercises inspired by real-world programming problems. No software required except for a web browser, or you can write code on your own PC or Mac.",
"domain": [
"programming"
],
"rating": {
"total": 6,
"count": 12
}
}
猜猜看?这是我们存储在数据库中的所有课程的列表。现在,您的前端应用程序可以遍历所有这些项目,并以一种吸引人的方式向用户展示它们。这就是API的强大之处。
整个课程的评级将根据作业文档中提到的章节总和进行更新。
此时,如果您想查看API的文档,可以通过导航到http://127.0.0.1:8000/docs端点来查看。这个可导航的API是与FastAPI一起预先打包的。是不是很酷?
适用于所有 API 端点的 FastAPI 文档
不喜欢文档的朴素外观吗?别担心,还有一个具有稍微更华丽界面的端点。只需导航到http://127.0.0.1:8000/redoc,您将会看到这个屏幕:
FastAPI 备用 redoc 界面,带有搜索和下载选项
获取课程概述端点 ( – GET)
您将使用此终端节点来获取特定课程的概述。只需在 URL 中提供course_id,API 就会返回有关该特定课程的详细信息。
@app.get('/courses/{course_id}')
def get_course(course_id: str):
course = db.courses.find_one({'_id': ObjectId(course_id)}, {'_id': 0, 'chapters': 0})
if not course:
raise HTTPException(status_code=404, detail='Course not found')
try:
course['rating'] = course['rating']['total']
except KeyError:
course['rating'] = 'Not rated yet'
return course
此代码片段会在MongoDB数据库中搜索具有指定course_id的课程,并提取课程信息,同时省略field.course_id和chapters字段。
如果找不到该课程,它会抛出一个状态码为404的HTTPException异常。如果找到了,它会尝试访问rating字段,并将其替换为其’total’值以显示总评分。如果没有找到rating字段,则会将框设置为“尚未评分”。
最后,省略chapters字段后,它返回包含总评分的课程信息的JSON响应。
单个课程概述端点响应
获取特定章节信息端点 ( – GET)
访问此端点将返回课程中某个章节的具体信息。通过在URL中指定course_id
和chapter_id
,您可以获取该特定章节的详细信息。
@app.get('/courses/{course_id}/{chapter_id}')
def get_chapter(course_id: str, chapter_id: str):
course = db.courses.find_one({'_id': ObjectId(course_id)}, {'_id': 0, })
if not course:
raise HTTPException(status_code=404, detail='Course not found')
chapters = course.get('chapters', [])
try:
chapter = chapters[int(chapter_id)]
except (ValueError, IndexError) as e:
raise HTTPException(status_code=404, detail='Chapter not found') from e
return chapter
其中,course_id
代表课程的身份标识,而chapter_id
则是在该课程内部的章节标识符。
当向此端点发送请求时,代码会首先在MongoDB数据库中搜索具有指定course_id
的课程,并在响应中忽略_id
列。
如果数据库中找不到具有所提供course_id
的课程,代码将抛出一个状态码为404的HTTPException异常,表明无法找到该课程。
然后,代码使用GET函数来检索该课程的章节列表,如果chapters
字段不存在,则将其默认值设置为空列表。
利用请求中提供的chapter_id
,代码会尝试在章节列表中检索出确切的章节。如果chapter_id
不是有效的整数或超出了章节列表的范围,代码将抛出一个状态码为404的HTTPException异常,表明无法找到该章节。
如果找到了该章节,响应中将包含该课程内该章节的详细信息。
章节详细信息端点
Rate Chapter 终端节点 ( – POST)
此端点允许用户对课程中的各个章节进行评分。您可以提供1分表示正面评价,或-1分表示负面评价。API会汇总每个课程的所有评分,为未来改进提供宝贵的反馈。
到目前为止,我们主要看到了GET请求。但现在,让我们来看看如何向服务器发送数据、验证数据并将其插入到应用程序数据库中。
@app.post('/courses/{course_id}/{chapter_id}')
def rate_chapter(course_id: str, chapter_id: str, rating: int = Query(..., gt=-2, lt=2)):
course = db.courses.find_one({'_id': ObjectId(course_id)}, {'_id': 0, })
if not course:
raise HTTPException(status_code=404, detail='Course not found')
chapters = course.get('chapters', [])
try:
chapter = chapters[int(chapter_id)]
except (ValueError, IndexError) as e:
raise HTTPException(status_code=404, detail='Chapter not found') from e
try:
chapter['rating']['total'] += rating
chapter['rating']['count'] += 1
except KeyError:
chapter['rating'] = {'total': rating, 'count': 1}
db.courses.update_one({'_id': ObjectId(course_id)}, {'$set': {'chapters': chapters}})
return chapter
我们已经设置了一个端点,允许用户通过向特定URL发送HTTP POST请求来为课程中的每个章节打分。用户可以为正面评价提供1作为评分值,或为负面评价提供-1。代码查询MongoDB数据库以找到具有指定course_id
和chapter_id
的课程,同时排除某些字段。
如果找不到该课程,它会抛出一个状态码为404的HTTP异常。代码会检索章节列表,并将其默认值设置为空列表。
如果chapter_id
不是有效的整数或超出范围,它会抛出一个状态码为404的HTTPException
。如果找到了该章节,代码会根据提供的评分更新其评分,具体做法是:将评分值与提供的评分相加,并增加总计数。
如果章节没有现有的评分字段,它会创建一个新的字段,并用提供的评分和计数1进行初始化。然后,将更新后的评分更新到数据库中,并返回更新后的章节作为响应,向用户提供他们对该章节评分的反馈。
向章节添加评级的 POST 请求
要发出 POST 请求,请打开文档并单击上图中突出显示的请求。然后,点击 “Try it out”,填写帖子数据,然后按下面的 Execute 按钮。这会将 POST 数据发送到服务器,然后对其进行验证。
如果提交的所有数据都符合预期,则服务器将接受并显示 200 状态代码,这意味着操作成功。提交的数据现在位于 MongoDB 文档中。
发布请求成功
这就是 API 开发部分的总结。
使用 PyTest 进行自动化 API 端点测试
随着现代 Web 应用程序复杂性的增加,API 终端节点的数量及其交互也在增加。
在动态电子商务 Web 应用程序中,可能有数百个终端节点,每个终端节点都支持多种 HTTP 请求方法。这些端点可能错综复杂地相互连接。
确保每次开发迭代后所有这些端点的正常运行成为开发人员和 QA 团队的一项艰巨任务。这就是自动化测试的用武之地。
在与main.py
和courses.json
相同的目录中创建一个文件:test_app.py
。
from fastapi.testclient import TestClient
from pymongo import MongoClient
from bson import ObjectId
import pytest
from main import app
client = TestClient(app)
mongo_client = MongoClient('mongodb://localhost:27017/')
db = mongo_client['courses']
这将建立一个自动化测试环境。
FastAPI 测试客户端模拟对 Web 应用的 HTTP 请求。有了这个功能,您可以假装自己是一个用户,向您的应用发送请求并获取响应,就像真实用户一样。
我们使用 MongoDB 连接来存储课程数据,而 MongoClient 支持在测试期间进行交互和数据更新。
Test Database 是一个用于测试的单独数据库。这不会影响实际的课程文件。
通过此配置,您现在可以创建使用 TestClient 向 FastAPI 应用程序发送请求的测试函数。在这些测试期间,您将与 MongoDB 数据库进行交互,但不要担心,这只是测试数据库,因此不会损害任何重要内容。
如何测试 “Get Courses List” 端点
这些测试函数用于与FastAPI应用的“/courses”端点进行交互。它们检查当提供不同的参数(如按领域排序和过滤)时,该端点是否按预期行为工作,TestClient用于此目的。
这些测试会验证 API 响应中的状态代码、数据存在、排序顺序和域筛选,从而确保课程端点的功能正确可靠。
def test_get_courses_no_params():
response = client.get("/courses")
assert response.status_code == 200
def test_get_courses_sort_by_alphabetical():
response = client.get("/courses?sort_by=alphabetical")
assert response.status_code == 200
courses = response.json()
assert len(courses) > 0
assert sorted(courses, key=lambda x: x['name']) == courses
def test_get_courses_sort_by_date():
response = client.get("/courses?sort_by=date")
assert response.status_code == 200
courses = response.json()
assert len(courses) > 0
assert sorted(courses, key=lambda x: x['date'], reverse=True) == courses
def test_get_courses_sort_by_rating():
response = client.get("/courses?sort_by=rating")
assert response.status_code == 200
courses = response.json()
assert len(courses) > 0
assert sorted(courses, key=lambda x: x['rating']['total'], reverse=True) == courses
def test_get_courses_filter_by_domain():
response = client.get("/courses?domain=mathematics")
assert response.status_code == 200
courses = response.json()
assert len(courses) > 0
assert all([c['domain'][0] == 'mathematics' for c in courses])
def test_get_courses_filter_by_domain_and_sort_by_alphabetical():
response = client.get("/courses?domain=mathematics&sort_by=alphabetical")
assert response.status_code == 200
courses = response.json()
assert len(courses) > 0
assert all([c['domain'][0] == 'mathematics' for c in courses])
assert sorted(courses, key=lambda x: x['name']) == courses
def test_get_courses_filter_by_domain_and_sort_by_date():
response = client.get("/courses?domain=mathematics&sort_by=date")
assert response.status_code == 200
courses = response.json()
assert len(courses) > 0
assert all([c['domain'][0] == 'mathematics' for c in courses])
assert sorted(courses, key=lambda x: x['date'], reverse=True) == courses
请注意assert语句。它会将预期结果与实际结果进行比较,并根据此比较返回True或False(布尔值)。目标是使所有这些测试通过,即让这些值相等。
如何测试“获取单个课程信息”端点
测试使用TestClient向FastAPI的“/courses/course_id”端点发送查询,使用db.courses.find_one
函数从MongoDB数据库检索课程数据。通过比较API响应数据和数据库数据,可以帮助你确定该端点是否正确处理了存在和不存在的课程ID。
def test_get_course_by_id_exists():
response = client.get("/courses/6431137ab5da949e5978a281")
assert response.status_code == 200
course = response.json()
# get the course from the database
course_db = db.courses.find_one({'_id': ObjectId('6431137ab5da949e5978a281')})
# get the name of the course from the database
name_db = course_db['name']
# get the name of the course from the response
name_response = course['name']
# compare the two
assert name_db == name_response
def test_get_course_by_id_not_exists():
response = client.get("/courses/6431137ab5da949e5978a280")
assert response.status_code == 404
assert response.json() == {'detail': 'Course not found'}
如何测试 “Get Course Chapter Info” 端点
测试预期当使用TestClient发送请求时,FastAPI应用的“/courses/course_id/chapter_number”端点将为特定的课程ID和编号提供章节信息。
我们使用断言来确定响应是否包含预期数据,或者对于不存在的章节是否给出了“未找到”响应。它验证了是否检索到了正确的API章节,并处理了存在和不存在的章节。
def test_get_chapter_info():
response = client.get("/courses/6431137ab5da949e5978a281/1")
assert response.status_code == 200
chapter = response.json()
assert chapter['name'] == 'Big Picture of Calculus'
assert chapter['text'] == 'Highlights of Calculus'
def test_get_chapter_info_not_exists():
response = client.get("/courses/6431137ab5da949e5978a281/990")
assert response.status_code == 404
assert response.json() == {'detail': 'Chapter not found'}
如何测试 “Post Course Rating” 端点
为了测试评级功能,测试函数指定课程 ID、章节 ID 和评级变量。它使用 TestClient 的 post 方法向“/courses/course id/chapter id”API 提交 POST 请求,在 URL 中提供课程 ID 和章节编号,并将 rating 变量作为查询参数传递。
FastAPI 模拟用户的活动来评价课程的某个章节。响应成功,状态代码为 200。JSON 内容针对“name”和“rating”键以及“total”和“count”键进行验证。总评分和评分计数大于 0,表示用户已对该章节进行评分。
def test_rate_chapter():
course_id = "6431137ab5da949e5978a281"
chapter_id = "1"
rating = 1
response = client.post(f"/courses/{course_id}/{chapter_id}?rating={rating}")
assert response.status_code == 200
# Check if the response body has the expected structure
assert "name" in response.json()
assert "rating" in response.json()
assert "total" in response.json()["rating"]
assert "count" in response.json()["rating"]
assert response.json()["rating"]["total"] > 0
assert response.json()["rating"]["count"] > 0
def test_rate_chapter_not_exists():
response = client.post("/courses/6431137ab5da949e5978a281/990/rate", json={"rating": 1})
assert response.status_code == 404
assert response.json() == {'detail': 'Not Found'}
此验证可确保评级添加端点按预期工作,API 会返回正确的成功代码和有关章节的预期信息,包括其名称和更新的评级详细信息。
通过运行命令,文件中的所有测试函数都将被执行,并且您将获得反馈,了解端点是否按预期工作,或者是否出现了任何错误或回归。这允许开发人员和QA团队在开发周期的早期发现问题,并保持应用程序的可靠性和稳定性。运行命令pytest test_app.py
即可执行测试。
如下图所示,所有测试都通过了。干得好!随着您在应用程序中添加更多功能和端点,请继续添加相关的测试以验证正确性。这被称为测试驱动开发(TDD)。
使用 Pytest 运行 API 测试
运行Pytest命令会显示如上图所示的输出。它表示有13个测试通过了。这意味着我们的所有端点都能正常工作,并返回预期的响应。
通过检测回归、集成组件、解决错误、进行负载和性能测试以及安全测试,端点测试验证了应用程序的基本操作是否正确。所有潜在的弱点和漏洞都会被记录下来并标记以供检查。
Pytest有助于确保API端点能够很好地协同工作,并帮助您处理失败情况和边缘案例。它能够在实际情况中管理大量的并发大请求。
如何使用 Docker 容器化应用程序
您可以将应用程序及其所有依赖项放在一个称为容器的单元中。这称为容器化。它将应用程序与底层系统分开,从而在不同操作系统之间保持一致性。
Docker 是一种现代容器化技术,可以更轻松地创建、分发和执行容器。它使开发人员能够一致且可重复地构建、交付和执行应用程序,而无需从源代码构建。
从以下网址安装Docker:https://www.docker.com/get-started。
Docker 化 Python 程序可帮助您确保它们在多台计算机上一致地运行,从而消除兼容性问题。它将软件、其依赖项和自定义内容容器化,使其具有可移植性。
在与其他文件相同的目录中,创建一个新文件,命名为Dockerfile(注意,它不需要任何扩展名)。
# Use an official Python runtime as a parent image
FROM python:3.9-slim-buster
# Set the working directory to /app
WORKDIR /app
# Copy the current directory contents into the container at /code
WORKDIR /app
COPY ./requirements.txt /app/requirements.txt
# Install any needed packages specified in requirements.txt
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
COPY . /app
# Run app.py when the container launches
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]
从官方的Python 3.9精简镜像开始,Dockerfile定义了镜像的蓝图。
它更改工作目录到/app,这是应用程序代码将存储的位置。该项目的依赖项被列在requirements.txt文件中,该文件已被放入容器中。
RUN命令使用pip来安装Python依赖项。COPY命令将应用程序的代码从主机移动到容器的/app目录。CMD命令提供了容器启动时将要执行的命令。
在这种情况下,它运行“uvicorn main:app”(main.py中的FastAPI应用程序),并将主机设置为0.0.0.0,端口设置为80。
如何运行 Docker 容器
在与Dockerfile相同的目录中,使用以下命令构建Docker镜像:docker build -t my_python_app .
使用 Docker 容器化 FastAPI 应用程序
使用以下命令以分离模式运行容器:docker run -d -p 80:80 my_python_app
完成此操作后,您可以从 Docker Desktop 查看容器和映像的状态。
Docker Desktop 显示我们的容器映像现在在端口 80 上处于运行状态
如何终止 Docker 容器
使用docker ps命令查找容器ID或名称。使用其ID或名称停止容器:
docker stop <container_id_or_name>
本指南仅涵盖了开发、测试和容器化方面的内容。但请注意,如果忽视部署后的容器安全,可能会带来漏洞、配置错误和攻击等风险。理想情况下,您应该利用CNAPP(云原生应用保护平台)来扫描镜像、遵循最佳实践,并监控运行中的容器以确保安全。
重要的是,Docker容器化允许将Python脚本与其依赖项捆绑在一起,使它们保持一致性和可移植性。Dockerfile描述了如何创建镜像。
在构建完镜像后运行容器只需执行一个命令。同样,停止容器也非常简单。Docker简化了Python应用程序的分发管理。
结论
本教程是一个快速入门指南,可帮助您利用 FastAPI 的强大功能。我们构建了一个课程管理 API,可以有效地处理与课程相关的查询。
我们通过将课程数据从JSON文件导入MongoDB,然后创建了多个端点,供用户访问课程列表、概览、章节信息和用户分数。我们还添加了一个评论聚合功能,以展示如何使用HTTP POST和HTTP GET方法,从而可以从服务器抓取数据以及向服务器发送数据。
PyTest帮助我们处理自动化测试,确保了可靠性和稳定性。接着,我们将应用程序容器化,使用Docker来简化部署和维护。
我的GitHub仓库中包含了本快速入门指南所涵盖的完整代码。欢迎订阅我的技术博客,获取技术速查表和资源。