From b892664f257bc24acc9be172845b615bc36e815a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 3 Jul 2023 21:37:26 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Deprecate=20parameter=20`r?= =?UTF-8?q?egex`,=20use=20`pattern`=20instead=20(#9786)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 📝 Update docs to deprecate regex, recommend pattern * ♻️ Update examples to use new pattern instead of regex * 📝 Add new example with deprecated regex * ♻️ Add deprecation notes and warnings for regex * ✅ Add tests for regex deprecation * ✅ Update tests for compatibility with Pydantic v1 --- .../tutorial/query-params-str-validations.md | 18 +- .../tutorial004.py | 2 +- .../tutorial004_an.py | 2 +- .../tutorial004_an_py310.py | 2 +- .../tutorial004_an_py310_regex.py | 17 ++ .../tutorial004_an_py39.py | 2 +- .../tutorial004_py310.py | 2 +- .../tutorial010.py | 2 +- .../tutorial010_an.py | 2 +- .../tutorial010_an_py310.py | 2 +- .../tutorial010_an_py39.py | 2 +- .../tutorial010_py310.py | 2 +- fastapi/param_functions.py | 49 ++++- fastapi/params.py | 75 +++++-- tests/test_regex_deprecated_body.py | 183 ++++++++++++++++++ tests/test_regex_deprecated_params.py | 166 ++++++++++++++++ 16 files changed, 497 insertions(+), 31 deletions(-) create mode 100644 docs_src/query_params_str_validations/tutorial004_an_py310_regex.py create mode 100644 tests/test_regex_deprecated_body.py create mode 100644 tests/test_regex_deprecated_params.py diff --git a/docs/en/docs/tutorial/query-params-str-validations.md b/docs/en/docs/tutorial/query-params-str-validations.md index 549e6c75b..f87adddcb 100644 --- a/docs/en/docs/tutorial/query-params-str-validations.md +++ b/docs/en/docs/tutorial/query-params-str-validations.md @@ -277,7 +277,7 @@ You can also add a parameter `min_length`: ## Add regular expressions -You can define a regular expression that the parameter should match: +You can define a regular expression `pattern` that the parameter should match: === "Python 3.10+" @@ -315,7 +315,7 @@ You can define a ../../../docs_src/query_params_str_validations/tutorial004_an_py310_regex.py!} + ``` + +But know that this is deprecated and it should be updated to use the new parameter `pattern`. 🤓 + ## Default values You can, of course, use default values other than `None`. diff --git a/docs_src/query_params_str_validations/tutorial004.py b/docs_src/query_params_str_validations/tutorial004.py index 5a7129816..3639b6c38 100644 --- a/docs_src/query_params_str_validations/tutorial004.py +++ b/docs_src/query_params_str_validations/tutorial004.py @@ -8,7 +8,7 @@ app = FastAPI() @app.get("/items/") async def read_items( q: Union[str, None] = Query( - default=None, min_length=3, max_length=50, regex="^fixedquery$" + default=None, min_length=3, max_length=50, pattern="^fixedquery$" ) ): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} diff --git a/docs_src/query_params_str_validations/tutorial004_an.py b/docs_src/query_params_str_validations/tutorial004_an.py index 5346b997b..24698c7b3 100644 --- a/docs_src/query_params_str_validations/tutorial004_an.py +++ b/docs_src/query_params_str_validations/tutorial004_an.py @@ -9,7 +9,7 @@ app = FastAPI() @app.get("/items/") async def read_items( q: Annotated[ - Union[str, None], Query(min_length=3, max_length=50, regex="^fixedquery$") + Union[str, None], Query(min_length=3, max_length=50, pattern="^fixedquery$") ] = None ): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} diff --git a/docs_src/query_params_str_validations/tutorial004_an_py310.py b/docs_src/query_params_str_validations/tutorial004_an_py310.py index 8fd375b3d..b7b629ee8 100644 --- a/docs_src/query_params_str_validations/tutorial004_an_py310.py +++ b/docs_src/query_params_str_validations/tutorial004_an_py310.py @@ -8,7 +8,7 @@ app = FastAPI() @app.get("/items/") async def read_items( q: Annotated[ - str | None, Query(min_length=3, max_length=50, regex="^fixedquery$") + str | None, Query(min_length=3, max_length=50, pattern="^fixedquery$") ] = None ): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} diff --git a/docs_src/query_params_str_validations/tutorial004_an_py310_regex.py b/docs_src/query_params_str_validations/tutorial004_an_py310_regex.py new file mode 100644 index 000000000..8fd375b3d --- /dev/null +++ b/docs_src/query_params_str_validations/tutorial004_an_py310_regex.py @@ -0,0 +1,17 @@ +from typing import Annotated + +from fastapi import FastAPI, Query + +app = FastAPI() + + +@app.get("/items/") +async def read_items( + q: Annotated[ + str | None, Query(min_length=3, max_length=50, regex="^fixedquery$") + ] = None +): + results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} + if q: + results.update({"q": q}) + return results diff --git a/docs_src/query_params_str_validations/tutorial004_an_py39.py b/docs_src/query_params_str_validations/tutorial004_an_py39.py index 2fd82db75..8e9a6fc32 100644 --- a/docs_src/query_params_str_validations/tutorial004_an_py39.py +++ b/docs_src/query_params_str_validations/tutorial004_an_py39.py @@ -8,7 +8,7 @@ app = FastAPI() @app.get("/items/") async def read_items( q: Annotated[ - Union[str, None], Query(min_length=3, max_length=50, regex="^fixedquery$") + Union[str, None], Query(min_length=3, max_length=50, pattern="^fixedquery$") ] = None ): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} diff --git a/docs_src/query_params_str_validations/tutorial004_py310.py b/docs_src/query_params_str_validations/tutorial004_py310.py index 180a2e511..f80798bcb 100644 --- a/docs_src/query_params_str_validations/tutorial004_py310.py +++ b/docs_src/query_params_str_validations/tutorial004_py310.py @@ -6,7 +6,7 @@ app = FastAPI() @app.get("/items/") async def read_items( q: str - | None = Query(default=None, min_length=3, max_length=50, regex="^fixedquery$") + | None = Query(default=None, min_length=3, max_length=50, pattern="^fixedquery$") ): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: diff --git a/docs_src/query_params_str_validations/tutorial010.py b/docs_src/query_params_str_validations/tutorial010.py index 35443d194..3314f8b6d 100644 --- a/docs_src/query_params_str_validations/tutorial010.py +++ b/docs_src/query_params_str_validations/tutorial010.py @@ -14,7 +14,7 @@ async def read_items( description="Query string for the items to search in the database that have a good match", min_length=3, max_length=50, - regex="^fixedquery$", + pattern="^fixedquery$", deprecated=True, ) ): diff --git a/docs_src/query_params_str_validations/tutorial010_an.py b/docs_src/query_params_str_validations/tutorial010_an.py index 8995f3f57..c5df00897 100644 --- a/docs_src/query_params_str_validations/tutorial010_an.py +++ b/docs_src/query_params_str_validations/tutorial010_an.py @@ -16,7 +16,7 @@ async def read_items( description="Query string for the items to search in the database that have a good match", min_length=3, max_length=50, - regex="^fixedquery$", + pattern="^fixedquery$", deprecated=True, ), ] = None diff --git a/docs_src/query_params_str_validations/tutorial010_an_py310.py b/docs_src/query_params_str_validations/tutorial010_an_py310.py index cfa81926c..a8e8c099b 100644 --- a/docs_src/query_params_str_validations/tutorial010_an_py310.py +++ b/docs_src/query_params_str_validations/tutorial010_an_py310.py @@ -15,7 +15,7 @@ async def read_items( description="Query string for the items to search in the database that have a good match", min_length=3, max_length=50, - regex="^fixedquery$", + pattern="^fixedquery$", deprecated=True, ), ] = None diff --git a/docs_src/query_params_str_validations/tutorial010_an_py39.py b/docs_src/query_params_str_validations/tutorial010_an_py39.py index 220eaabf4..955880dd6 100644 --- a/docs_src/query_params_str_validations/tutorial010_an_py39.py +++ b/docs_src/query_params_str_validations/tutorial010_an_py39.py @@ -15,7 +15,7 @@ async def read_items( description="Query string for the items to search in the database that have a good match", min_length=3, max_length=50, - regex="^fixedquery$", + pattern="^fixedquery$", deprecated=True, ), ] = None diff --git a/docs_src/query_params_str_validations/tutorial010_py310.py b/docs_src/query_params_str_validations/tutorial010_py310.py index f2839516e..9ea7b3c49 100644 --- a/docs_src/query_params_str_validations/tutorial010_py310.py +++ b/docs_src/query_params_str_validations/tutorial010_py310.py @@ -13,7 +13,7 @@ async def read_items( description="Query string for the items to search in the database that have a good match", min_length=3, max_length=50, - regex="^fixedquery$", + pattern="^fixedquery$", deprecated=True, ) ): diff --git a/fastapi/param_functions.py b/fastapi/param_functions.py index 6ddce3468..5efd8b995 100644 --- a/fastapi/param_functions.py +++ b/fastapi/param_functions.py @@ -18,7 +18,12 @@ def Path( # noqa: N802 min_length: Optional[int] = None, max_length: Optional[int] = None, pattern: Optional[str] = None, - regex: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, examples: Optional[List[Any]] = None, example: Annotated[ Optional[Any], @@ -65,7 +70,12 @@ def Query( # noqa: N802 min_length: Optional[int] = None, max_length: Optional[int] = None, pattern: Optional[str] = None, - regex: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, examples: Optional[List[Any]] = None, example: Annotated[ Optional[Any], @@ -113,7 +123,12 @@ def Header( # noqa: N802 min_length: Optional[int] = None, max_length: Optional[int] = None, pattern: Optional[str] = None, - regex: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, examples: Optional[List[Any]] = None, example: Annotated[ Optional[Any], @@ -161,7 +176,12 @@ def Cookie( # noqa: N802 min_length: Optional[int] = None, max_length: Optional[int] = None, pattern: Optional[str] = None, - regex: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, examples: Optional[List[Any]] = None, example: Annotated[ Optional[Any], @@ -210,7 +230,12 @@ def Body( # noqa: N802 min_length: Optional[int] = None, max_length: Optional[int] = None, pattern: Optional[str] = None, - regex: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, examples: Optional[List[Any]] = None, example: Annotated[ Optional[Any], @@ -256,7 +281,12 @@ def Form( # noqa: N802 min_length: Optional[int] = None, max_length: Optional[int] = None, pattern: Optional[str] = None, - regex: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, examples: Optional[List[Any]] = None, example: Annotated[ Optional[Any], @@ -301,7 +331,12 @@ def File( # noqa: N802 min_length: Optional[int] = None, max_length: Optional[int] = None, pattern: Optional[str] = None, - regex: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, examples: Optional[List[Any]] = None, example: Annotated[ Optional[Any], diff --git a/fastapi/params.py b/fastapi/params.py index c1bdaf1b4..53d30cc33 100644 --- a/fastapi/params.py +++ b/fastapi/params.py @@ -33,7 +33,12 @@ class Param(FieldInfo): min_length: Optional[int] = None, max_length: Optional[int] = None, pattern: Optional[str] = None, - regex: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, examples: Optional[List[Any]] = None, example: Annotated[ Optional[Any], @@ -51,7 +56,7 @@ class Param(FieldInfo): warnings.warn( "`example` has been depreacated, please use `examples` instead", category=DeprecationWarning, - stacklevel=1, + stacklevel=4, ) self.example = example self.include_in_schema = include_in_schema @@ -70,11 +75,17 @@ class Param(FieldInfo): ) if examples is not None: kwargs["examples"] = examples + if regex is not None: + print(f"regex: {regex}") + warnings.warn( + "`regex` has been depreacated, please use `pattern` instead", + category=DeprecationWarning, + stacklevel=4, + ) if PYDANTIC_V2: kwargs["annotation"] = annotation kwargs["pattern"] = pattern or regex else: - # TODO: pv2 figure out how to deprecate regex kwargs["regex"] = pattern or regex super().__init__(**kwargs) @@ -101,7 +112,12 @@ class Path(Param): min_length: Optional[int] = None, max_length: Optional[int] = None, pattern: Optional[str] = None, - regex: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, examples: Optional[List[Any]] = None, example: Annotated[ Optional[Any], @@ -156,7 +172,12 @@ class Query(Param): min_length: Optional[int] = None, max_length: Optional[int] = None, pattern: Optional[str] = None, - regex: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, examples: Optional[List[Any]] = None, example: Annotated[ Optional[Any], @@ -210,7 +231,12 @@ class Header(Param): min_length: Optional[int] = None, max_length: Optional[int] = None, pattern: Optional[str] = None, - regex: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, examples: Optional[List[Any]] = None, example: Annotated[ Optional[Any], @@ -264,7 +290,12 @@ class Cookie(Param): min_length: Optional[int] = None, max_length: Optional[int] = None, pattern: Optional[str] = None, - regex: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, examples: Optional[List[Any]] = None, example: Annotated[ Optional[Any], @@ -317,7 +348,12 @@ class Body(FieldInfo): min_length: Optional[int] = None, max_length: Optional[int] = None, pattern: Optional[str] = None, - regex: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, examples: Optional[List[Any]] = None, example: Annotated[ Optional[Any], @@ -334,7 +370,7 @@ class Body(FieldInfo): warnings.warn( "`example` has been depreacated, please use `examples` instead", category=DeprecationWarning, - stacklevel=1, + stacklevel=4, ) self.example = example kwargs = dict( @@ -352,11 +388,16 @@ class Body(FieldInfo): ) if examples is not None: kwargs["examples"] = examples + if regex is not None: + warnings.warn( + "`regex` has been depreacated, please use `pattern` instead", + category=DeprecationWarning, + stacklevel=4, + ) if PYDANTIC_V2: kwargs["annotation"] = annotation kwargs["pattern"] = pattern or regex else: - # TODO: pv2 figure out how to deprecate regex kwargs["regex"] = pattern or regex super().__init__( **kwargs, @@ -383,7 +424,12 @@ class Form(Body): min_length: Optional[int] = None, max_length: Optional[int] = None, pattern: Optional[str] = None, - regex: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, examples: Optional[List[Any]] = None, example: Annotated[ Optional[Any], @@ -433,7 +479,12 @@ class File(Form): min_length: Optional[int] = None, max_length: Optional[int] = None, pattern: Optional[str] = None, - regex: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, examples: Optional[List[Any]] = None, example: Annotated[ Optional[Any], diff --git a/tests/test_regex_deprecated_body.py b/tests/test_regex_deprecated_body.py new file mode 100644 index 000000000..6384cdd43 --- /dev/null +++ b/tests/test_regex_deprecated_body.py @@ -0,0 +1,183 @@ +from typing import Annotated + +import pytest +from dirty_equals import IsDict +from fastapi import FastAPI, Form +from fastapi.testclient import TestClient +from fastapi.utils import match_pydantic_error_url + +from .utils import needs_py310 + + +def get_client(): + app = FastAPI() + with pytest.warns(DeprecationWarning): + + @app.post("/items/") + async def read_items( + q: Annotated[str | None, Form(regex="^fixedquery$")] = None + ): + if q: + return f"Hello {q}" + else: + return "Hello World" + + client = TestClient(app) + return client + + +@needs_py310 +def test_no_query(): + client = get_client() + response = client.post("/items/") + assert response.status_code == 200 + assert response.json() == "Hello World" + + +@needs_py310 +def test_q_fixedquery(): + client = get_client() + response = client.post("/items/", data={"q": "fixedquery"}) + assert response.status_code == 200 + assert response.json() == "Hello fixedquery" + + +@needs_py310 +def test_query_nonregexquery(): + client = get_client() + response = client.post("/items/", data={"q": "nonregexquery"}) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "string_pattern_mismatch", + "loc": ["body", "q"], + "msg": "String should match pattern '^fixedquery$'", + "input": "nonregexquery", + "ctx": {"pattern": "^fixedquery$"}, + "url": match_pydantic_error_url("string_pattern_mismatch"), + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "ctx": {"pattern": "^fixedquery$"}, + "loc": ["body", "q"], + "msg": 'string does not match regex "^fixedquery$"', + "type": "value_error.str.regex", + } + ] + } + ) + + +@needs_py310 +def test_openapi_schema(): + client = get_client() + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + # insert_assert(response.json()) + assert response.json() == { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "summary": "Read Items", + "operationId": "read_items_items__post", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": IsDict( + { + "allOf": [ + { + "$ref": "#/components/schemas/Body_read_items_items__post" + } + ], + "title": "Body", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "$ref": "#/components/schemas/Body_read_items_items__post" + } + ) + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "Body_read_items_items__post": { + "properties": { + "q": IsDict( + { + "anyOf": [ + {"type": "string", "pattern": "^fixedquery$"}, + {"type": "null"}, + ], + "title": "Q", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + {"type": "string", "pattern": "^fixedquery$", "title": "Q"} + ) + }, + "type": "object", + "title": "Body_read_items_items__post", + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": {"$ref": "#/components/schemas/ValidationError"}, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } diff --git a/tests/test_regex_deprecated_params.py b/tests/test_regex_deprecated_params.py new file mode 100644 index 000000000..5ea4f2ada --- /dev/null +++ b/tests/test_regex_deprecated_params.py @@ -0,0 +1,166 @@ +from typing import Annotated + +import pytest +from dirty_equals import IsDict +from fastapi import FastAPI, Query +from fastapi.testclient import TestClient +from fastapi.utils import match_pydantic_error_url + +from .utils import needs_py310 + + +def get_client(): + app = FastAPI() + with pytest.warns(DeprecationWarning): + + @app.get("/items/") + async def read_items( + q: Annotated[str | None, Query(regex="^fixedquery$")] = None + ): + if q: + return f"Hello {q}" + else: + return "Hello World" + + client = TestClient(app) + return client + + +@needs_py310 +def test_query_params_str_validations_no_query(): + client = get_client() + response = client.get("/items/") + assert response.status_code == 200 + assert response.json() == "Hello World" + + +@needs_py310 +def test_query_params_str_validations_q_fixedquery(): + client = get_client() + response = client.get("/items/", params={"q": "fixedquery"}) + assert response.status_code == 200 + assert response.json() == "Hello fixedquery" + + +@needs_py310 +def test_query_params_str_validations_item_query_nonregexquery(): + client = get_client() + response = client.get("/items/", params={"q": "nonregexquery"}) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "string_pattern_mismatch", + "loc": ["query", "q"], + "msg": "String should match pattern '^fixedquery$'", + "input": "nonregexquery", + "ctx": {"pattern": "^fixedquery$"}, + "url": match_pydantic_error_url("string_pattern_mismatch"), + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "ctx": {"pattern": "^fixedquery$"}, + "loc": ["query", "q"], + "msg": 'string does not match regex "^fixedquery$"', + "type": "value_error.str.regex", + } + ] + } + ) + + +@needs_py310 +def test_openapi_schema(): + client = get_client() + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + # insert_assert(response.json()) + assert response.json() == { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "name": "q", + "in": "query", + "required": False, + "schema": IsDict( + { + "anyOf": [ + {"type": "string", "pattern": "^fixedquery$"}, + {"type": "null"}, + ], + "title": "Q", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "string", + "pattern": "^fixedquery$", + "title": "Q", + } + ), + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": {"$ref": "#/components/schemas/ValidationError"}, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + }