From: Sebastián Ramírez Date: Thu, 5 Sep 2024 14:55:44 +0000 (+0200) Subject: ⏪️ Temporarily revert "✨ Add support for Pydantic models in `Form` parameters" to... X-Git-Tag: 0.112.4~4 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=8e6cf9ee9c9d87b6b658cc240146121c80f71476;p=thirdparty%2Ffastapi%2Ffastapi.git ⏪️ Temporarily revert "✨ Add support for Pydantic models in `Form` parameters" to make a checkpoint release (#12128) Revert "✨ Add support for Pydantic models in `Form` parameters (#12127)" This reverts commit 0f3e65b00712a59d763cf9c7715cde353bb94b02. --- diff --git a/docs/en/docs/img/tutorial/request-form-models/image01.png b/docs/en/docs/img/tutorial/request-form-models/image01.png deleted file mode 100644 index 3fe32c03d5..0000000000 Binary files a/docs/en/docs/img/tutorial/request-form-models/image01.png and /dev/null differ diff --git a/docs/en/docs/tutorial/request-form-models.md b/docs/en/docs/tutorial/request-form-models.md deleted file mode 100644 index 8bb1ffb1fc..0000000000 --- a/docs/en/docs/tutorial/request-form-models.md +++ /dev/null @@ -1,65 +0,0 @@ -# Form Models - -You can use Pydantic models to declare form fields in FastAPI. - -/// info - -To use forms, first install `python-multipart`. - -Make sure you create a [virtual environment](../virtual-environments.md){.internal-link target=_blank}, activate it, and then install it, for example: - -```console -$ pip install python-multipart -``` - -/// - -/// note - -This is supported since FastAPI version `0.113.0`. 🤓 - -/// - -## Pydantic Models for Forms - -You just need to declare a Pydantic model with the fields you want to receive as form fields, and then declare the parameter as `Form`: - -//// tab | Python 3.9+ - -```Python hl_lines="9-11 15" -{!> ../../../docs_src/request_form_models/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="8-10 14" -{!> ../../../docs_src/request_form_models/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="7-9 13" -{!> ../../../docs_src/request_form_models/tutorial001.py!} -``` - -//// - -FastAPI will extract the data for each field from the form data in the request and give you the Pydantic model you defined. - -## Check the Docs - -You can verify it in the docs UI at `/docs`: - -
- -
diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index 7c810c2d7c..528c80b8e6 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -129,7 +129,6 @@ nav: - tutorial/extra-models.md - tutorial/response-status-code.md - tutorial/request-forms.md - - tutorial/request-form-models.md - tutorial/request-files.md - tutorial/request-forms-and-files.md - tutorial/handling-errors.md diff --git a/docs_src/request_form_models/tutorial001.py b/docs_src/request_form_models/tutorial001.py deleted file mode 100644 index 98feff0b9f..0000000000 --- a/docs_src/request_form_models/tutorial001.py +++ /dev/null @@ -1,14 +0,0 @@ -from fastapi import FastAPI, Form -from pydantic import BaseModel - -app = FastAPI() - - -class FormData(BaseModel): - username: str - password: str - - -@app.post("/login/") -async def login(data: FormData = Form()): - return data diff --git a/docs_src/request_form_models/tutorial001_an.py b/docs_src/request_form_models/tutorial001_an.py deleted file mode 100644 index 30483d4455..0000000000 --- a/docs_src/request_form_models/tutorial001_an.py +++ /dev/null @@ -1,15 +0,0 @@ -from fastapi import FastAPI, Form -from pydantic import BaseModel -from typing_extensions import Annotated - -app = FastAPI() - - -class FormData(BaseModel): - username: str - password: str - - -@app.post("/login/") -async def login(data: Annotated[FormData, Form()]): - return data diff --git a/docs_src/request_form_models/tutorial001_an_py39.py b/docs_src/request_form_models/tutorial001_an_py39.py deleted file mode 100644 index 7cc81aae95..0000000000 --- a/docs_src/request_form_models/tutorial001_an_py39.py +++ /dev/null @@ -1,16 +0,0 @@ -from typing import Annotated - -from fastapi import FastAPI, Form -from pydantic import BaseModel - -app = FastAPI() - - -class FormData(BaseModel): - username: str - password: str - - -@app.post("/login/") -async def login(data: Annotated[FormData, Form()]): - return data diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 98ce17b55d..7ac18d941c 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -33,7 +33,6 @@ from fastapi._compat import ( field_annotation_is_scalar, get_annotation_from_field_info, get_missing_field_error, - get_model_fields, is_bytes_field, is_bytes_sequence_field, is_scalar_field, @@ -57,7 +56,6 @@ from fastapi.security.base import SecurityBase from fastapi.security.oauth2 import OAuth2, SecurityScopes from fastapi.security.open_id_connect_url import OpenIdConnect from fastapi.utils import create_model_field, get_path_param_names -from pydantic import BaseModel from pydantic.fields import FieldInfo from starlette.background import BackgroundTasks as StarletteBackgroundTasks from starlette.concurrency import run_in_threadpool @@ -745,9 +743,7 @@ def _should_embed_body_fields(fields: List[ModelField]) -> bool: return True # If it's a Form (or File) field, it has to be a BaseModel to be top level # otherwise it has to be embedded, so that the key value pair can be extracted - if isinstance(first_field.field_info, params.Form) and not lenient_issubclass( - first_field.type_, BaseModel - ): + if isinstance(first_field.field_info, params.Form): return True return False @@ -787,8 +783,7 @@ async def _extract_form_body( for sub_value in value: tg.start_soon(process_fn, sub_value.read) value = serialize_sequence_value(field=field, value=results) - if value is not None: - values[field.name] = value + values[field.name] = value return values @@ -803,14 +798,8 @@ async def request_body_to_args( single_not_embedded_field = len(body_fields) == 1 and not embed_body_fields first_field = body_fields[0] body_to_process = received_body - - fields_to_extract: List[ModelField] = body_fields - - if single_not_embedded_field and lenient_issubclass(first_field.type_, BaseModel): - fields_to_extract = get_model_fields(first_field.type_) - if isinstance(received_body, FormData): - body_to_process = await _extract_form_body(fields_to_extract, received_body) + body_to_process = await _extract_form_body(body_fields, received_body) if single_not_embedded_field: loc: Tuple[str, ...] = ("body",) diff --git a/scripts/playwright/request_form_models/image01.py b/scripts/playwright/request_form_models/image01.py deleted file mode 100644 index 15bd3858c5..0000000000 --- a/scripts/playwright/request_form_models/image01.py +++ /dev/null @@ -1,36 +0,0 @@ -import subprocess -import time - -import httpx -from playwright.sync_api import Playwright, sync_playwright - - -# Run playwright codegen to generate the code below, copy paste the sections in run() -def run(playwright: Playwright) -> None: - browser = playwright.chromium.launch(headless=False) - context = browser.new_context() - page = context.new_page() - page.goto("http://localhost:8000/docs") - page.get_by_role("button", name="POST /login/ Login").click() - page.get_by_role("button", name="Try it out").click() - page.screenshot(path="docs/en/docs/img/tutorial/request-form-models/image01.png") - - # --------------------- - context.close() - browser.close() - - -process = subprocess.Popen( - ["fastapi", "run", "docs_src/request_form_models/tutorial001.py"] -) -try: - for _ in range(3): - try: - response = httpx.get("http://localhost:8000/docs") - except httpx.ConnectError: - time.sleep(1) - break - with sync_playwright() as playwright: - run(playwright) -finally: - process.terminate() diff --git a/tests/test_forms_single_model.py b/tests/test_forms_single_model.py deleted file mode 100644 index 7ed3ba3a2d..0000000000 --- a/tests/test_forms_single_model.py +++ /dev/null @@ -1,129 +0,0 @@ -from typing import List, Optional - -from dirty_equals import IsDict -from fastapi import FastAPI, Form -from fastapi.testclient import TestClient -from pydantic import BaseModel -from typing_extensions import Annotated - -app = FastAPI() - - -class FormModel(BaseModel): - username: str - lastname: str - age: Optional[int] = None - tags: List[str] = ["foo", "bar"] - - -@app.post("/form/") -def post_form(user: Annotated[FormModel, Form()]): - return user - - -client = TestClient(app) - - -def test_send_all_data(): - response = client.post( - "/form/", - data={ - "username": "Rick", - "lastname": "Sanchez", - "age": "70", - "tags": ["plumbus", "citadel"], - }, - ) - assert response.status_code == 200, response.text - assert response.json() == { - "username": "Rick", - "lastname": "Sanchez", - "age": 70, - "tags": ["plumbus", "citadel"], - } - - -def test_defaults(): - response = client.post("/form/", data={"username": "Rick", "lastname": "Sanchez"}) - assert response.status_code == 200, response.text - assert response.json() == { - "username": "Rick", - "lastname": "Sanchez", - "age": None, - "tags": ["foo", "bar"], - } - - -def test_invalid_data(): - response = client.post( - "/form/", - data={ - "username": "Rick", - "lastname": "Sanchez", - "age": "seventy", - "tags": ["plumbus", "citadel"], - }, - ) - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["body", "age"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "seventy", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "age"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) - - -def test_no_data(): - response = client.post("/form/") - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "username"], - "msg": "Field required", - "input": {"tags": ["foo", "bar"]}, - }, - { - "type": "missing", - "loc": ["body", "lastname"], - "msg": "Field required", - "input": {"tags": ["foo", "bar"]}, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "username"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "lastname"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) diff --git a/tests/test_tutorial/test_request_form_models/__init__.py b/tests/test_tutorial/test_request_form_models/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial001.py b/tests/test_tutorial/test_request_form_models/test_tutorial001.py deleted file mode 100644 index 46c130ee8c..0000000000 --- a/tests/test_tutorial/test_request_form_models/test_tutorial001.py +++ /dev/null @@ -1,232 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.request_form_models.tutorial001 import app - - client = TestClient(app) - return client - - -def test_post_body_form(client: TestClient): - response = client.post("/login/", data={"username": "Foo", "password": "secret"}) - assert response.status_code == 200 - assert response.json() == {"username": "Foo", "password": "secret"} - - -def test_post_body_form_no_password(client: TestClient): - response = client.post("/login/", data={"username": "Foo"}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "password"], - "msg": "Field required", - "input": {"username": "Foo"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "password"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) - - -def test_post_body_form_no_username(client: TestClient): - response = client.post("/login/", data={"password": "secret"}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "username"], - "msg": "Field required", - "input": {"password": "secret"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "username"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) - - -def test_post_body_form_no_data(client: TestClient): - response = client.post("/login/") - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "username"], - "msg": "Field required", - "input": {}, - }, - { - "type": "missing", - "loc": ["body", "password"], - "msg": "Field required", - "input": {}, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "username"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "password"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -def test_post_body_json(client: TestClient): - response = client.post("/login/", json={"username": "Foo", "password": "secret"}) - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "username"], - "msg": "Field required", - "input": {}, - }, - { - "type": "missing", - "loc": ["body", "password"], - "msg": "Field required", - "input": {}, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "username"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "password"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/login/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Login", - "operationId": "login_login__post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": {"$ref": "#/components/schemas/FormData"} - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "FormData": { - "properties": { - "username": {"type": "string", "title": "Username"}, - "password": {"type": "string", "title": "Password"}, - }, - "type": "object", - "required": ["username", "password"], - "title": "FormData", - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial001_an.py b/tests/test_tutorial/test_request_form_models/test_tutorial001_an.py deleted file mode 100644 index 4e14d89c84..0000000000 --- a/tests/test_tutorial/test_request_form_models/test_tutorial001_an.py +++ /dev/null @@ -1,232 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.request_form_models.tutorial001_an import app - - client = TestClient(app) - return client - - -def test_post_body_form(client: TestClient): - response = client.post("/login/", data={"username": "Foo", "password": "secret"}) - assert response.status_code == 200 - assert response.json() == {"username": "Foo", "password": "secret"} - - -def test_post_body_form_no_password(client: TestClient): - response = client.post("/login/", data={"username": "Foo"}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "password"], - "msg": "Field required", - "input": {"username": "Foo"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "password"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) - - -def test_post_body_form_no_username(client: TestClient): - response = client.post("/login/", data={"password": "secret"}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "username"], - "msg": "Field required", - "input": {"password": "secret"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "username"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) - - -def test_post_body_form_no_data(client: TestClient): - response = client.post("/login/") - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "username"], - "msg": "Field required", - "input": {}, - }, - { - "type": "missing", - "loc": ["body", "password"], - "msg": "Field required", - "input": {}, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "username"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "password"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -def test_post_body_json(client: TestClient): - response = client.post("/login/", json={"username": "Foo", "password": "secret"}) - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "username"], - "msg": "Field required", - "input": {}, - }, - { - "type": "missing", - "loc": ["body", "password"], - "msg": "Field required", - "input": {}, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "username"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "password"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/login/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Login", - "operationId": "login_login__post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": {"$ref": "#/components/schemas/FormData"} - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "FormData": { - "properties": { - "username": {"type": "string", "title": "Username"}, - "password": {"type": "string", "title": "Password"}, - }, - "type": "object", - "required": ["username", "password"], - "title": "FormData", - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial001_an_py39.py b/tests/test_tutorial/test_request_form_models/test_tutorial001_an_py39.py deleted file mode 100644 index 2e6426aa78..0000000000 --- a/tests/test_tutorial/test_request_form_models/test_tutorial001_an_py39.py +++ /dev/null @@ -1,240 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from tests.utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.request_form_models.tutorial001_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_post_body_form(client: TestClient): - response = client.post("/login/", data={"username": "Foo", "password": "secret"}) - assert response.status_code == 200 - assert response.json() == {"username": "Foo", "password": "secret"} - - -@needs_py39 -def test_post_body_form_no_password(client: TestClient): - response = client.post("/login/", data={"username": "Foo"}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "password"], - "msg": "Field required", - "input": {"username": "Foo"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "password"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) - - -@needs_py39 -def test_post_body_form_no_username(client: TestClient): - response = client.post("/login/", data={"password": "secret"}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "username"], - "msg": "Field required", - "input": {"password": "secret"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "username"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) - - -@needs_py39 -def test_post_body_form_no_data(client: TestClient): - response = client.post("/login/") - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "username"], - "msg": "Field required", - "input": {}, - }, - { - "type": "missing", - "loc": ["body", "password"], - "msg": "Field required", - "input": {}, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "username"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "password"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -@needs_py39 -def test_post_body_json(client: TestClient): - response = client.post("/login/", json={"username": "Foo", "password": "secret"}) - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "username"], - "msg": "Field required", - "input": {}, - }, - { - "type": "missing", - "loc": ["body", "password"], - "msg": "Field required", - "input": {}, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "username"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "password"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/login/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Login", - "operationId": "login_login__post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": {"$ref": "#/components/schemas/FormData"} - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "FormData": { - "properties": { - "username": {"type": "string", "title": "Username"}, - "password": {"type": "string", "title": "Password"}, - }, - "type": "object", - "required": ["username", "password"], - "title": "FormData", - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - }