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

Rest 使用Pydantic使用可选元素进行API JSON模式验证,rest,jsonschema,fastapi,pydantic,Rest,Jsonschema,Fastapi,Pydantic,我正在使用pydantic的fastapi和BaseModel来验证和记录API返回的JSON模式 这对于固定返回非常有效,但我有一些可选参数可以更改返回,因此我希望将其包括在验证中,但在参数丢失且API中未返回字段时,它不会失败 例如:我有一个可选的布尔参数,名为transparency,当该参数设置为true时,我返回一个名为search\u transparency的块,并返回弹性查询 { "info": { "totalrecords"

我正在使用pydantic的fastapi和BaseModel来验证和记录API返回的JSON模式

这对于固定返回非常有效,但我有一些可选参数可以更改返回,因此我希望将其包括在验证中,但在参数丢失且API中未返回字段时,它不会失败

例如:我有一个可选的布尔参数,名为
transparency
,当该参数设置为true时,我返回一个名为search\u transparency的块,并返回弹性查询

{
  "info": {
    "totalrecords": 52
  },
  "records": [],
  "search_transparency": {"full_query": "blah blah"}
}
如果未设置参数
transparency=true
,我希望返回值为:

{
  "info": {
    "totalrecords": 52
  },
  "records": []
}
但是,当我在pydantic中将该元素设置为可选时,返回的结果是:

{
  "info": {
    "totalrecords": 52
  },
  "records": [],
  "search_transparency": None
}
我对记录下的字段也有类似的内容。默认值是字段的最小返回值,但如果设置参数
full=true
,则会返回更多字段。我希望以类似的方式处理这个问题,字段只是不存在,而不是显示为
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\u model=V1PlaceAPI,tags=[“v1\u api”)

我怀疑,也许我试图实现的是糟糕的API实践,也许我不应该有可变的回报

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

例如
api/v1/place/search?q=test
vs
api/v1/place/full/transparent/search?q=test

编辑

有关my 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)
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
其中,ESQuery仅返回elasticsearch响应。 这是我的解析函数:

@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)
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\u model\u exclude\u none=True
作为路径参数

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

您可以进一步定制您的响应模型,下面是我的一个很好的解释,我建议您检查一下。

您可以返回两个类的并集,其中一个是透明的,而另一个不是透明的。您介意显示您正在使用的端点函数吗?因此,我可以提供一个接近您需要的示例,谢谢-我已经添加了这个细节