API JSON 使用 Pydantic 对可选元素进行模式验证

API JSON Schema Validation with Optional Element using Pydantic

我正在使用 pydantic 的 fastapi 和 BaseModel 来验证和记录 API return.

的 JSON 模式

这适用于固定的 return 但我有可选参数可以更改 return 所以我想将它包含在验证中但是当参数丢失时它不会失败并且该字段未在 API.

中 returned

例如:我有一个名为 transparency 的可选布尔参数,当它设置为 true 时,我 return 一个名为 search_transparency 的块,带有弹性查询 returned .

{
  "info": {
    "totalrecords": 52
  },
  "records": [],
  "search_transparency": {"full_query": "blah blah"}
}

如果未设置参数 transparency=true 我希望 return 为:

{
  "info": {
    "totalrecords": 52
  },
  "records": []
}

但是,当我在 pydantic 中将该元素设置为可选时,我得到的是 returned:

{
  "info": {
    "totalrecords": 52
  },
  "records": [],
  "search_transparency": None
}

对于记录下的字段,我有类似的东西。默认是最少 return 个字段,但如果您设置参数 full=true,那么您会得到更多 return 字段。我想以类似的方式处理这个问题,只是字段不存在而不是显示 None.

的值

这就是我用 pydantic 处理它的方式:

class Info(BaseModel):
    totalrecords: int

class Transparency(BaseModel):
    full_query: str

class V1Place(BaseModel):
    name: str

class V1PlaceAPI(BaseModel):
    info: Info
    records: List[V1Place] = []
    search_transparency: Optional[Transparency]

这就是我使用 fastapi 执行验证的方式:

@app.get("/api/v1/place/search", response_model=V1PlaceAPI, tags=["v1_api"])

我怀疑我想要实现的可能是糟糕的 API 实践,也许我不应该有变量 returns.

我是否应该创建多个单独的端点来处理这个问题?

例如。 api/v1/place/search?q=test 对比 api/v1/place/full/transparent/search?q=test

编辑

我的 API 函数的更多细节:

@app.get("/api/v1/place/search", response_model=V1PlaceAPI, tags=["v1_api"])

def v1_place_search(q: str = Query(None, min_length=3, max_length=500, title="search through all place fields"),
                    transparency: Optional[bool] = None,
                    offset: Optional[int] = Query(0),
                    limit: Optional[int] = Query(15)):

    search_limit = offset + limit

    results, transparency_query = ESQuery(client=es_client,
                                          index='places',
                                          transparency=transparency,
                                          track_hits=True,
                                          offset=offset,
                                          limit=search_limit)

    return v1_place_parse(results.to_dict(), 
    show_transparency=transparency_query)

其中 ESQuery 只是 return 一个 elasticsearch 响应。 这是我的解析函数:

def v1_place_parse(resp, show_transparency=None):
    """This takes a response from elasticsearch and parses it for our legacy V1 elasticapi

    Args:
        resp (dict): This is the response from Search.execute after passing to_dict()

    Returns:
        dict: A dictionary that is passed to API
    """

    new_resp = {}
    total_records = resp['hits']['total']['value']
    query_records = len(resp.get('hits', {}).get('hits', []))

    new_resp['info'] = {'totalrecords': total_records,
                        'totalrecords_relation': resp['hits']['total']['relation'],
                        'totalrecordsperquery': query_records,
                        }
    if show_transparency is not None:
        search_string = show_transparency.get('query', '')
        new_resp['search_transparency'] = {'full_query': str(search_string),
                                           'components': {}}
    new_resp['records'] = []
    for hit in resp.get('hits', {}).get('hits', []):
        new_record = hit['_source']
        new_resp['records'].append(new_record)

    return new_resp

可能排除该字段,如果它 None 可以完成工作。

只需添加一个response_model_exclude_none = True作为路径参数

@app.get(
    "/api/v1/place/search",
    response_model=V1PlaceAPI,
    tags=["v1_api"],
    response_model_exclude_none=True,
)

您可以进一步自定义您的 Response 模型,这里有一个很好的解释 answer 我真的建议您检查一下。