Browse Source
Revert "✨ Add support for Pydantic models in `Form` parameters (#12127)"
This reverts commit 0f3e65b007
.
pull/12129/head
committed by
GitHub
13 changed files with 3 additions and 994 deletions
Before Width: | Height: | Size: 43 KiB |
@ -1,65 +0,0 @@ |
|||||
# Form Models |
|
||||
|
|
||||
You can use Pydantic models to declare form fields in FastAPI. |
|
||||
|
|
||||
/// info |
|
||||
|
|
||||
To use forms, first install <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>. |
|
||||
|
|
||||
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`: |
|
||||
|
|
||||
<div class="screenshot"> |
|
||||
<img src="/img/tutorial/request-form-models/image01.png"> |
|
||||
</div> |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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() |
|
@ -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", |
|
||||
}, |
|
||||
] |
|
||||
} |
|
||||
) |
|
@ -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"}, |
|
||||
} |
|
||||
}, |
|
||||
}, |
|
||||
} |
|
||||
}, |
|
||||
} |
|
@ -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"}, |
|
||||
} |
|
||||
}, |
|
||||
}, |
|
||||
} |
|
||||
}, |
|
||||
} |
|
@ -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"}, |
|
||||
} |
|
||||
}, |
|
||||
}, |
|
||||
} |
|
||||
}, |
|
||||
} |
|
Loading…
Reference in new issue