【问题标题】:OpenAPI is missing schemas for some of the Pydantic models in FastAPI appOpenAPI 缺少 FastAPI 应用程序中某些 Pydantic 模型的模式
【发布时间】:2022-08-19 20:04:52
【问题描述】:

我正在构建一个 FastAPI 应用程序,它有很多 Pydantic 模型。尽管应用程序运行良好,但正如预期的那样,OpenAPI (Swagger UI) 文档并未在 Schemas 部分下显示所有这些模型的架构。

这里是pydanticschemas.py的内容

import socket
from datetime import datetime
from enum import Enum
from typing import Any, Dict, List, Optional, Set, Union

from pydantic import BaseModel, Field, validator
from typing_extensions import Literal

ResponseData = Union[List[Any], Dict[str, Any], BaseModel]


# Not visible in Swagger UI
class PageIn(BaseModel):
    page_size: int = Field(default=100, gt=0)
    num_pages: int = Field(default=1, gt=0, exclude=True)
    start_page: int = Field(default=1, gt=0, exclude=True)

# visible under schemas on Swagger UI
class PageOut(PageIn):
    total_records: int = 0
    total_pages: int = 0
    current_page: int = 1

    class Config:  # pragma: no cover
        @staticmethod
        def schema_extra(schema, model) -> None:
            schema.get(\"properties\").pop(\"num_pages\")
            schema.get(\"properties\").pop(\"start_page\")


# Not visible in Swagger UI
class BaseResponse(BaseModel):
    host_: str = Field(default_factory=socket.gethostname)
    message: Optional[str]


# Not visible in Swagger UI
class APIResponse(BaseResponse):
    count: int = 0
    location: Optional[str]
    page: Optional[PageOut]
    data: ResponseData


# Not visible in Swagger UI
class ErrorResponse(BaseResponse):
    error: str


# visible under schemas on Swagger UI
class BaseFaultMap(BaseModel):
    detection_system: Optional[str] = Field(\"\", example=\"obhc\")
    fault_type: Optional[str] = Field(\"\", example=\"disk\")
    team: Optional[str] = Field(\"\", example=\"dctechs\")
    description: Optional[str] = Field(
        \"\",
        example=\"Hardware raid controller disk failure found. \"
        \"Operation can continue normally,\"
        \"but risk of data loss exist\",
    )



# Not visible in Swagger UI
class FaultQueryParams(BaseModel):
    f_id: Optional[int] = Field(None, description=\"id for the host\", example=12345, title=\"Fault ID\")
    hostname: Optional[str]
    status: Literal[\"open\", \"closed\", \"all\"] = Field(\"open\")
    created_by: Optional[str]
    environment: Optional[str]
    team: Optional[str]
    fault_type: Optional[str]
    detection_system: Optional[str]
    inops_filters: Optional[str] = Field(None)
    date_filter: Optional[str] = Field(\"\",)
    sort_by: Optional[str] = Field(\"created\",)
    sort_order: Literal[\"asc\", \"desc\"] = Field(\"desc\")

所有这些模型实际上都在 FastAPI 路径中用于验证请求正文。 FaultQueryParams 是一个自定义模型,我用它来验证请求查询参数,使用如下:

query_args: FaultQueryParams = Depends()

其余模型与Body 字段一起使用。我无法弄清楚为什么在 Schemas 部分中只有一些模型不可见,而其他模型则可见。

关于FaultQueryParams,我注意到的另一件事是描述,示例不会显示在路径端点上,即使它们是在模型中定义的。

编辑1:

我更深入地研究并意识到所有在 swagger UI 中不可见的模型都是没有直接在路径操作中使用的模型,即这些模型没有被用作 response_modelBody 类型并且是排序的间接使用的辅助模型。因此,FastAPI 似乎没有为这些模型生成模式。

上述语句的一个例外是query_args: FaultQueryParams = Depends(),它直接在路径操作中用于将端点的Query 参数映射到自定义模型。这是一个问题,因为 swagger 没有从这个模型的字段中识别元参数,如 titledescriptionexample,并且没有显示在 UI 上,这对这个端点的用户很重要。

有没有办法欺骗 FastAPI 为自定义模型 FaultQueryParams 生成架构,就像它为 BodyQuery 等生成架构一样?

    标签: python swagger openapi fastapi pydantic


    【解决方案1】:

    FastAPI 将为用作Request BodyResponse Model 的模型生成模式。当声明query_args: FaultQueryParams = Depends()(使用Depends)时,你的端点不会期望request body,而是query参数;因此,FaultQueryParams 不会包含在 OpenAPI 文档的架构中。

    要添加其他架构,您可以extend/modify the OpenAPI schema。下面给出了示例(确保在定义所有路由之后添加用于修改架构的代码,即在代码末尾)。

    class FaultQueryParams(BaseModel):
        f_id: Optional[int] = Field(None, description="id for the host", example=12345, title="Fault ID")
        hostname: Optional[str]
        status: Literal["open", "closed", "all"] = Field("open")
        ...
        
    @app.post('/predict')
    def predict(query_args: FaultQueryParams = Depends()):
        return query_args
    
    def get_extra_schemas():
        return {
                  "FaultQueryParams": {
                    "title": "FaultQueryParams",
                    "type": "object",
                    "properties": {
                      "f_id": {
                        "title": "Fault ID",
                        "type": "integer",
                        "description": "id for the host",
                        "example": 12345
                      },
                      "hostname": {
                        "title": "Hostname",
                        "type": "string"
                      },
                      "status": {
                        "title": "Status",
                        "enum": [
                          "open",
                          "closed",
                          "all"
                        ],
                        "type": "string",
                        "default": "open"
                      },
                       ...
                    }
                  }
                }
    
    from fastapi.openapi.utils import get_openapi
    
    def custom_openapi():
        if app.openapi_schema:
            return app.openapi_schema
        openapi_schema = get_openapi(
            title="FastAPI",
            version="1.0.0",
            description="This is a custom OpenAPI schema",
            routes=app.routes,
        )
        new_schemas = openapi_schema["components"]["schemas"]
        new_schemas.update(get_extra_schemas())
        openapi_schema["components"]["schemas"] = new_schemas
        
        app.openapi_schema = openapi_schema
        return app.openapi_schema
    
    
    app.openapi = custom_openapi
    

    一些有用的说明

    注1

    无需手动为要添加到文档中的额外模型键入架构,您可以让 FastAPI 为您执行此操作,方法是使用该模型向您的代码添加一个端点(您随后将在获取架构后将其删除)作为请求体或响应模型,例如:

    @app.post('/predict') 
    def predict(query_args: FaultQueryParams):
        return query_args
    

    然后,您可以在http://127.0.0.1:8000/openapi.json 获取生成的 JSON 模式,如documentation 中所述。从那里,您可以将模型的模式复制并粘贴到您的代码中并直接使用它(如上面的 get_extra_schema() 方法所示)或将其保存到文件并从文件中加载 JSON 数据,如下所示:

    import json
    ...
    
    new_schemas = openapi_schema["components"]["schemas"]
    
    with open('extra_schemas.json') as f:    
        extra_schemas = json.load(f)
        
    new_schemas.update(extra_schemas)   
    openapi_schema["components"]["schemas"] = new_schemas
    
    ...
    

    笔记2

    对于declare metadata,例如descriptionexample等,对于您的查询参数,您应该使用Query而不是Field来定义您的参数,并且由于您不能使用Pydantic模型做到这一点,所以您可以声明一个custom dependency class,描述为here,如下所示:

    from fastapi import FastAPI, Query, Depends
    from typing import Optional
    
    class FaultQueryParams:
        def __init__(
            self,
            f_id: Optional[int] = Query(None, description="id for the host", example=12345)
    
        ):
            self.f_id = f_id
    
    app = FastAPI()
    
    @app.post('/predict')
    def predict(query_args: FaultQueryParams = Depends()):
        return query_args
    

    上面可以用@dataclass装饰器重写,如下图:

    from fastapi import FastAPI, Query, Depends
    from typing import Optional
    from dataclasses import dataclass
    
    @dataclass
    class FaultQueryParams:
            f_id: Optional[int] = Query(None, description="id for the host", example=12345)
    
    app = FastAPI()
    
    @app.post('/predict')
    def predict(query_args: FaultQueryParams = Depends()):
        return query_args
    

    【讨论】:

      【解决方案2】:

      感谢@Chris 提供的指针,最终导致我使用dataclasses 批量定义查询参数,它工作得很好。

      @dataclass
      class FaultQueryParams1:
          f_id: Optional[int] = Query(None, description="id for the host", example=55555)
          hostname: Optional[str] = Query(None, example="test-host1.domain.com")
          status: Literal["open", "closed", "all"] = Query(
              None, description="fetch open/closed or all records", example="all"
          )
          created_by: Optional[str] = Query(
              None,
              description="fetch records created by particular user",
              example="user-id",
          )
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-06-21
        • 2021-03-04
        • 1970-01-01
        • 1970-01-01
        • 2021-10-12
        • 2021-11-01
        • 1970-01-01
        相关资源
        最近更新 更多