从您的fastapi应用中导出OpenAPI规范
#网络开发人员 #python #api #fastapi

FastAPI是用于构建API的现代Python网络框架。 FastAPI是构建简单API的绝佳选择,并带有内置支持以生成OpenAPI文档。

在这篇文章中,我们将研究如何从Fastapi项目中生成和提取OpenAPI specification

在我们的测试台上,从the official docs取的一个简单的单文件FastApi示例就足够了。相同的工作流也适用于路由器的大型项目。

步骤1:添加标签和元数据

可能会使用OpenAPI规格生成文档或代码。重要的是要在您的FastApi应用中添加元数据,以使生成的OpenAPI规格完成。

首先,您应该使用tags标记我们的端点,以确保它们将其分组为逻辑操作。此示例不使用routers,但是如果您这样做,则需要标记路由器而不是端点。

标签由文档和代码生成器使用将端点分组在一起。标签可能包括空间和特殊字符,但我们建议将标签保持简单。通常将小写或资本案例用于标签,例如我们的示例中的Items

除了标签外,我们还将在我们的FastApi App实例中添加descriptionversion元数据。 descriptionversion将在概述页面的生成的OpenAPI文档中使用。如果需要在规范中包含其他详细信息,请在Fastapi文档中找到metadata parameters的完整列表。

现在看起来像这样的完整示例main.py

# main.py
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI(description="Example app", version="0.1.0")


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: list[str] = []


@app.post("/items/", tags=["Items"])
async def create_item(item: Item) -> Item:
    return item


@app.get("/items/", tags=["Items"])
async def read_items() -> list[Item]:
    return [
        Item(name="Portal Gun", price=42.0),
        Item(name="Plumbus", price=32.0),
    ]

此示例需要pip install fastapi[all]pip install pydantic运行。

步骤2:创建导出脚本

默认情况下,FastApi将在/docs下生成OpenAPI文档。您可以通过运行应用程序并导航到http://localhost:8000/docs尝试一下。

可以通过导航到/openapi.json直接获得OpenAPI JSON,但是我们希望以编程方式提取文档以能够自动化该过程。 FastAPI不支持直接导出OpenAPI规范,但我们将使用一个小脚本提取它。

创建一个带有以下内容的文件extract-openapi.py

# extract-openapi.py
import argparse
import json
import sys
import yaml
from uvicorn.importer import import_from_string

parser = argparse.ArgumentParser(prog="extract-openapi.py")
parser.add_argument("app",       help='App import string. Eg. "main:app"', default="main:app")
parser.add_argument("--app-dir", help="Directory containing the app", default=None)
parser.add_argument("--out",     help="Output file ending in .json or .yaml", default="openapi.yaml")

if __name__ == "__main__":
    args = parser.parse_args()

    if args.app_dir is not None:
        print(f"adding {args.app_dir} to sys.path")
        sys.path.insert(0, args.app_dir)

    print(f"importing app from {args.app}")
    app = import_from_string(args.app)
    openapi = app.openapi()
    version = openapi.get("openapi", "unknown version")

    print(f"writing openapi spec v{version}")
    with open(args.out, "w") as f:
        if args.out.endswith(".json"):
            json.dump(openapi, f, indent=2)
        else:
            yaml.dump(openapi, f, sort_keys=False)

    print(f"spec written to {args.out}")

此脚本所做的是从给定的导入字符串导入应用程序,然后调用app.openapi()获取OpenAPI Spec。然后将规格写入给定的输出文件。

您可以调用帮助查看可用选项:

$ python3 export-openapi.py --help
usage: extract-openapi.py [-h] [--app-dir APP_DIR] [--out OUT] app

positional arguments:
app App import string. Eg. "main:app"

options:
-h, --help show this help message and exit
--app-dir APP_DIR Directory containing the app
--out OUT Output file ending in .json or .yaml

如果您在uvicorn导入方面遇到麻烦,请确保已安装了包括uvicorn在内的fastapi[all]软件包,或者安装了uvicorn

步骤3:从Fastapi导出OpenAPI规格

运行脚本

要运行export script,您需要知道FastApi应用程序的导入字符串。 导入字符串是您运行应用程序时向Uvicorn传递的内容,例如uvicorn main:app

这取决于您的代码的位置以及fastapi实例的名称。有关如何确定导入字符串的提示,请参见下文。

# Run the script by passing the import string of your FastAPI app
# If you don't know the import string, see below for examples
$ python extract-openapi.py main:app

这应该在您当前目录中创建openapi.jsonopenapi.yaml文件。

示例案例:简单的项目结构

在我们的示例中,我们有一个类似的项目结构:

/my_project
├── extract-openapi.py
└── main.py

我们的fastapi实例是app,因为我们在main.py中编写了app = FastAPI()。我们在当前目录中也有一个main.py,因此导入字符串为main:app

提取我们做的openapi规格

$ python extract-openapi.py main:app

示例案例:嵌套项目结构

对于较大的应用程序,项目结构通常更嵌套,例如:

/my_project
├── extract-openapi.py
└── myapp
    └── main.py

在这种情况下,我们必须将模块名称myapp添加到导入字符串中。导入字符串现在为myapp.main:app。另外,如果您对此遇到麻烦,则可以使用--app-dir参数指定包含应用程序的入口点的目录。

在这种情况下,要提取OpenAPI规格,我们要做

$ python extract-openapi.py myapp.main:app

# or alternatively

$ python extract-openapi.py --app-dir myapp main:app

您现在应该在当前目录中有openapi.jsonopenapi.yaml文件。

步骤4:在您的CI/CD管道中自动化(可选)

您将如何将提取到CI/CD集成取决于您要完成的工作。接近这一点的三种最常见方法是:

  • 在本地提取规格并将其提交给您的存储库。令CI/CD验证所承诺的规格是最新的。
  • 将规格作为CI/CD管道的一部分提取,并将规格用作临时文件来完成某些事情(例如,生成客户端)。
  • 将规格作为CI/CD管道的一部分提取,并在合并到Main时将生成的规格提交给您的存储库。

使用脚本的好处是它也可以在本地运行。本地承诺通常是一种安全且直接的方法,但有时可能会使合并更加困难。但是,如果您只需要作为CI/CD管道的一部分生成OpenAPI规格,则还应考虑专用的GitHub操作。

作为一个例子,我们将演示一个github动作作业,该作业验证了订婚规格与生成的规格匹配:

# .github/workflows/main.yml
name: CI

on: [push]

jobs:
   extract-openapi:
      runs-on: ubuntu-latest
      steps:
         - uses: actions/checkout@v2

         - name: Setup Python
         uses: actions/setup-python@v2
         with:
            python-version: 3.11

         - name: Install dependencies
         run: |
            python -m pip install --upgrade pip
            pip install -r requirements.txt

         - name: Extract OpenAPI spec
         run: python extract-openapi.py main:app --out openapi_generated.yaml

         # Do something with the generated spec here.
         # For example, validate that the committed spec matches the generated one.
         - name: Verify OpenAPI spec has been updated
         run: git diff --exit-code openapi.yaml openapi_generated.yaml

概括

总而言之,我们有

  • tags添加到每个端点或路由器
  • 在我们的fastapi应用程序实例
  • 中添加了descriptionversion和其他元数据
  • 创建一个脚本extract-openapi.py,以从FastApi提取OpenAPI Spec
  • 在CI/CD管道中自动提取