mirror of https://github.com/tiangolo/fastapi.git
🐛 Fix inconsistent processing of model docstring formfeed char with Pydantic V1 (#6039)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Yurii Motov <yurii.motov.monte@gmail.com> Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
This commit is contained in:
parent
03123c00f1
commit
86e515784d
|
|
@ -393,9 +393,10 @@ else:
|
|||
)
|
||||
definitions.update(m_definitions)
|
||||
model_name = model_name_map[model]
|
||||
definitions[model_name] = m_schema
|
||||
for m_schema in definitions.values():
|
||||
if "description" in m_schema:
|
||||
m_schema["description"] = m_schema["description"].split("\f")[0]
|
||||
definitions[model_name] = m_schema
|
||||
return definitions
|
||||
|
||||
def is_pv1_scalar_field(field: ModelField) -> bool:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,180 @@
|
|||
from typing import Any, Iterator, Set, Type
|
||||
|
||||
import fastapi._compat
|
||||
import fastapi.openapi.utils
|
||||
import pydantic.schema
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from .utils import needs_pydanticv1
|
||||
|
||||
|
||||
class Address(BaseModel):
|
||||
"""
|
||||
This is a public description of an Address
|
||||
\f
|
||||
You can't see this part of the docstring, it's private!
|
||||
"""
|
||||
|
||||
line_1: str
|
||||
city: str
|
||||
state_province: str
|
||||
|
||||
|
||||
class Facility(BaseModel):
|
||||
id: str
|
||||
address: Address
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
@app.get("/facilities/{facility_id}")
|
||||
def get_facility(facility_id: str) -> Facility: ...
|
||||
|
||||
|
||||
openapi_schema = {
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Address": {
|
||||
# NOTE: the description of this model shows only the public-facing text, before the `\f` in docstring
|
||||
"description": "This is a public description of an Address\n",
|
||||
"properties": {
|
||||
"city": {"title": "City", "type": "string"},
|
||||
"line_1": {"title": "Line 1", "type": "string"},
|
||||
"state_province": {"title": "State Province", "type": "string"},
|
||||
},
|
||||
"required": ["line_1", "city", "state_province"],
|
||||
"title": "Address",
|
||||
"type": "object",
|
||||
},
|
||||
"Facility": {
|
||||
"properties": {
|
||||
"address": {"$ref": "#/components/schemas/Address"},
|
||||
"id": {"title": "Id", "type": "string"},
|
||||
},
|
||||
"required": ["id", "address"],
|
||||
"title": "Facility",
|
||||
"type": "object",
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"properties": {
|
||||
"detail": {
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
}
|
||||
},
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
},
|
||||
"ValidationError": {
|
||||
"properties": {
|
||||
"loc": {
|
||||
"items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
"required": ["loc", "msg", "type"],
|
||||
"title": "ValidationError",
|
||||
"type": "object",
|
||||
},
|
||||
}
|
||||
},
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"openapi": "3.1.0",
|
||||
"paths": {
|
||||
"/facilities/{facility_id}": {
|
||||
"get": {
|
||||
"operationId": "get_facility_facilities__facility_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "facility_id",
|
||||
"required": True,
|
||||
"schema": {"title": "Facility Id", "type": "string"},
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Facility"}
|
||||
}
|
||||
},
|
||||
"description": "Successful Response",
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Validation Error",
|
||||
},
|
||||
},
|
||||
"summary": "Get Facility",
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
"""
|
||||
Sanity check to ensure our app's openapi schema renders as we expect
|
||||
"""
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == openapi_schema
|
||||
|
||||
|
||||
class SortedTypeSet(set):
|
||||
"""
|
||||
Set of Types whose `__iter__()` method yields results sorted by the type names
|
||||
"""
|
||||
|
||||
def __init__(self, seq: Set[Type[Any]], *, sort_reversed: bool):
|
||||
super().__init__(seq)
|
||||
self.sort_reversed = sort_reversed
|
||||
|
||||
def __iter__(self) -> Iterator[Type[Any]]:
|
||||
members_sorted = sorted(
|
||||
super().__iter__(),
|
||||
key=lambda type_: type_.__name__,
|
||||
reverse=self.sort_reversed,
|
||||
)
|
||||
yield from members_sorted
|
||||
|
||||
|
||||
@needs_pydanticv1
|
||||
@pytest.mark.parametrize("sort_reversed", [True, False])
|
||||
def test_model_description_escaped_with_formfeed(sort_reversed: bool):
|
||||
"""
|
||||
Regression test for bug fixed by https://github.com/fastapi/fastapi/pull/6039.
|
||||
|
||||
Test `get_model_definitions` with models passed in different order.
|
||||
"""
|
||||
all_fields = fastapi.openapi.utils.get_fields_from_routes(app.routes)
|
||||
|
||||
flat_models = fastapi._compat.get_flat_models_from_fields(
|
||||
all_fields, known_models=set()
|
||||
)
|
||||
model_name_map = pydantic.schema.get_model_name_map(flat_models)
|
||||
|
||||
expected_address_description = "This is a public description of an Address\n"
|
||||
|
||||
models = fastapi._compat.get_model_definitions(
|
||||
flat_models=SortedTypeSet(flat_models, sort_reversed=sort_reversed),
|
||||
model_name_map=model_name_map,
|
||||
)
|
||||
assert models["Address"]["description"] == expected_address_description
|
||||
Loading…
Reference in New Issue