paint-brush
FastAPI 让我快速获得了 OpenAPI 规范经过@johnjvester
1,876 讀數
1,876 讀數

FastAPI 让我快速获得了 OpenAPI 规范

经过 John Vester16m2024/04/22
Read on Terminal Reader

太長; 讀書

当 API First 不是一种选择时,FastAPI 可以允许现有 RESTful 微服务自动使用 OpenAPI v3 进行完整记录和使用,从而节省团队的时间。
featured image - FastAPI 让我快速获得了 OpenAPI 规范
John Vester HackerNoon profile picture

我的文章的读者可能熟悉采用 API First 方法开发微服务的想法。无数次,我意识到在开始任何开发之前描述预期的 URI 和底层对象模型的好处。


然而,在我 30 多年的技术探索历程中,我已经开始期待替代流程的现实。换句话说,我完全预料到,在某些情况下,API First 是不可能实现的。


在本文中,我想通过一个示例来说明如何让生产微服务的团队无需手动定义 openapi.json 文件就能成功地为其他人提供 OpenAPI 规范。


我还想走出自己的舒适区,不使用 Java、.NET 甚至 JavaScript 来做到这一点。

发现 FastAPI

在我的大多数文章的结尾,我经常提到我的个人使命宣言:


“将时间集中在提供能够扩展知识产权价值的功能上。其他一切都可以利用框架、产品和服务。” – J. Vester


我在此使命宣言中的观点是,在努力实现更高层次的目标和目的时,我有责任充分利用自己的时间。基本上,如果我们的重点是销售更多小部件,那么我的时间应该花在寻找实现这一目标的方法上——避开现有框架、产品或服务已经解决的挑战。


我选择 Python 作为我的新微服务的编程语言。到目前为止,我为之前的文章编写的 Python 代码中有 99% 都是Stack Overflow 驱动开发(SODD) 或 ChatGPT 驱动答案的结果。显然,Python 超出了我的舒适区。


现在我已经确定了事情的现状,我想创建一个新的基于 Python 的 RESTful 微服务,该微服务符合我的个人使命宣言,同时对源语言有最少的经验。


就在那时,我发现了FastAPI


FastAPI 自 2018 年问世以来,一直是一个专注于使用 Python 类型提示提供 RESTful API 的框架。FastAPI 最棒的地方在于它能够自动生成 OpenAPI 3 规范,而从开发人员的角度来看,无需任何额外的努力。

文章 API 用例

对于这篇文章,我想到了一个文章 API 的想法,提供一个 RESTful API,允许消费者检索我最近发布的文章列表。


为了简单起见,我们假设给定的Article包含以下属性:

  • id – 简单、唯一的标识符属性(数字)
  • title – 文章标题(字符串)
  • url – 文章的完整 URL(字符串)
  • year – 文章发表的年份(数字)


文章 API 将包含以下 URI:

  • GET /articles – 将检索文章列表
  • GET /articles/{article_id} – 将通过 id 属性检索单个文章
  • POST /articles – 添加新文章

FastAPI 实际应用

在我的终端中,我创建了一个名为 fast-api-demo 的新 Python 项目,然后执行了以下命令:


 $ pip install --upgrade pip $ pip install fastapi $ pip install uvicorn


我创建了一个名为api.py的新 Python 文件并添加了一些导入,并建立了一个app变量:


 from fastapi import FastAPI, HTTPException from pydantic import BaseModel app = FastAPI() if __name__ == "__main__": import uvicorn uvicorn.run(app, host="localhost", port=8000)


接下来,我定义了一个Article对象来匹配 Article API 用例:


 class Article(BaseModel): id: int title: str url: str year: int


建立模型后,我需要添加 URI......这实际上很容易:


 # Route to add a new article @app.post("/articles") def create_article(article: Article): articles.append(article) return article # Route to get all articles @app.get("/articles") def get_articles(): return articles # Route to get a specific article by ID @app.get("/articles/{article_id}") def get_article(article_id: int): for article in articles: if article.id == article_id: return article raise HTTPException(status_code=404, detail="Article not found")


为了避免涉及外部数据存储,我决定以编程方式添加一些我最近发布的文章:


 articles = [ Article(id=1, title="Distributed Cloud Architecture for Resilient Systems: Rethink Your Approach To Resilient Cloud Services", url="https://dzone.com/articles/distributed-cloud-architecture-for-resilient-syste", year=2023), Article(id=2, title="Using Unblocked to Fix a Service That Nobody Owns", url="https://dzone.com/articles/using-unblocked-to-fix-a-service-that-nobody-owns", year=2023), Article(id=3, title="Exploring the Horizon of Microservices With KubeMQ's New Control Center", url="https://dzone.com/articles/exploring-the-horizon-of-microservices-with-kubemq", year=2024), Article(id=4, title="Build a Digital Collectibles Portal Using Flow and Cadence (Part 1)", url="https://dzone.com/articles/build-a-digital-collectibles-portal-using-flow-and-1", year=2024), Article(id=5, title="Build a Flow Collectibles Portal Using Cadence (Part 2)", url="https://dzone.com/articles/build-a-flow-collectibles-portal-using-cadence-par-1", year=2024), Article(id=6, title="Eliminate Human-Based Actions With Automated Deployments: Improving Commit-to-Deploy Ratios Along the Way", url="https://dzone.com/articles/eliminate-human-based-actions-with-automated-deplo", year=2024), Article(id=7, title="Vector Tutorial: Conducting Similarity Search in Enterprise Data", url="https://dzone.com/articles/using-pgvector-to-locate-similarities-in-enterpris", year=2024), Article(id=8, title="DevSecOps: It's Time To Pay for Your Demand, Not Ingestion", url="https://dzone.com/articles/devsecops-its-time-to-pay-for-your-demand", year=2024), ]


无论你是否相信,这已经完成了 Article API 微服务的开发。


为了快速检查完整性,我在本地启动了我的 API 服务:


 $ python api.py INFO: Started server process [320774] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://localhost:8000 (Press CTRL+C to quit)


然后,在另一个终端窗口中,我发送了一个 curl 请求(并将其通过管道传输到json_pp ):


 $ curl localhost:8000/articles/1 | json_pp { "id": 1, "title": "Distributed Cloud Architecture for Resilient Systems: Rethink Your Approach To Resilient Cloud Services", "url": "https://dzone.com/articles/distributed-cloud-architecture-for-resilient-syste", "year": 2023 }


准备部署

与其在本地运行 Article API,我还想看看部署微服务有多容易。由于我之前从未在Heroku部署过 Python 微服务,我觉得现在是尝试的好时机。


在深入研究 Heroku 之前,我需要创建一个requirements.txt文件来描述服务的依赖关系。为此,我安装并执行了pipreqs


 $ pip install pipreqs $ pipreqs


这为我创建了一个requirements.txt文件,其中包含以下信息:


 fastapi==0.110.1 pydantic==2.6.4 uvicorn==0.29.0


我还需要一个名为Procfile的文件,它告诉 Heroku 如何使用uvicorn启动我的微服务。其内容如下:


 web: uvicorn api:app --host=0.0.0.0 --port=${PORT}


让我们部署到 Heroku

对于那些刚接触 Python 的人(就像我一样),我使用了《使用 Python 开始使用 Heroku》文档作为有用的指南。


由于我已经安装了 Heroku CLI,所以我只需要从我的终端登录到 Heroku 生态系统:


 $ heroku login


我确保将所有更新都签入到 GitLab 上的存储库中。


接下来,可以通过以下命令使用 CLI 在 Heroku 中创建新应用程序:


 $ heroku create


CLI 使用唯一的应用程序名称以及应用程序的 URL 和与应用程序关联的基于 git 的存储库进行响应:


 Creating app... done, powerful-bayou-23686 https://powerful-bayou-23686-2d5be7cf118b.herokuapp.com/ | https://git.heroku.com/powerful-bayou-23686.git


请注意 - 当您阅读本文时,我的应用程序将不再在线。


看看这个。当我发出 git remote 命令时,我可以看到远程服务器已自动添加到 Heroku 生态系统中:


 $ git remote heroku origin


要将fast-api-demo应用程序部署到 Heroku,我只需使用以下命令:


 $ git push heroku main


一切设置完成后,我能够在 Heroku 仪表板中验证我的新基于 Python 的服务是否已启动并正在运行:


在服务运行时,可以通过发出以下 curl 命令从 Article API 中检索id = 1 Article


 $ curl --location 'https://powerful-bayou-23686-2d5be7cf118b.herokuapp.com/articles/1'


curl 命令返回200 OK响应和以下 JSON 负载:


 { "id": 1, "title": "Distributed Cloud Architecture for Resilient Systems: Rethink Your Approach To Resilient Cloud Services", "url": "https://dzone.com/articles/distributed-cloud-architecture-for-resilient-syste", "year": 2023 }


自动交付 OpenAPI 3 规范

利用 FastAPI 的内置 OpenAPI 功能,消费者可以通过导航到自动生成的/docs URI 来接收功能齐全的 v3 规范:


 https://powerful-bayou-23686-2d5be7cf118b.herokuapp.com/docs


调用此 URL 将使用广泛采用的 Swagger UI 返回 Article API 微服务:


对于那些寻找openapi.json文件来生成客户端以使用 Article API 的人来说,可以使用/openapi.json URI:


 https://powerful-bayou-23686-2d5be7cf118b.herokuapp.com/openapi.json


就我的示例而言,基于 JSON 的 OpenAPI v3 规范如下所示:


 { "openapi": "3.1.0", "info": { "title": "FastAPI", "version": "0.1.0" }, "paths": { "/articles": { "get": { "summary": "Get Articles", "operationId": "get_articles_articles_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { } } } } } }, "post": { "summary": "Create Article", "operationId": "create_article_articles_post", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Article" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/articles/{article_id}": { "get": { "summary": "Get Article", "operationId": "get_article_articles__article_id__get", "parameters": [ { "name": "article_id", "in": "path", "required": true, "schema": { "type": "integer", "title": "Article Id" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } } }, "components": { "schemas": { "Article": { "properties": { "id": { "type": "integer", "title": "Id" }, "title": { "type": "string", "title": "Title" }, "url": { "type": "string", "title": "Url" }, "year": { "type": "integer", "title": "Year" } }, "type": "object", "required": [ "id", "title", "url", "year" ], "title": "Article" }, "HTTPValidationError": { "properties": { "detail": { "items": { "$ref": "#/components/schemas/ValidationError" }, "type": "array", "title": "Detail" } }, "type": "object", "title": "HTTPValidationError" }, "ValidationError": { "properties": { "loc": { "items": { "anyOf": [ { "type": "string" }, { "type": "integer" } ] }, "type": "array", "title": "Location" }, "msg": { "type": "string", "title": "Message" }, "type": { "type": "string", "title": "Error Type" } }, "type": "object", "required": [ "loc", "msg", "type" ], "title": "ValidationError" } } } }


因此,以下规范可用于通过OpenAPI Generator生成多种不同语言的客户端。

结论

在本文开头,我已经准备好与那些不愿意使用 API First 方法的人展开斗争。从这次练习中我学到的是,像 FastAPI 这样的产品可以帮助快速定义和生成有效的 RESTful 微服务,同时还自动包含完全可用的 OpenAPI v3 规范……


事实证明,FastAPI 允许团队通过利用一个框架来产生供其他人依赖的标准化合同,从而专注于他们的目标和目的。因此,出现了另一条坚持我的个人使命宣言的道路。


在此过程中,我第一次使用 Heroku 部署基于 Python 的服务。事实证明,除了查看一些精心编写的文档外,我几乎不需要做任何努力。因此,Heroku 平台的另一个使命宣言奖励也需要提及。


如果你对本文的源代码感兴趣,可以在GitLab上找到它。


祝您度过愉快的一天!