Add missing tests for code examples (#14569)

Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Nils-Hero Lindemann <nilsherolindemann@proton.me>
This commit is contained in:
Motov Yurii 2025-12-26 11:43:02 +01:00 committed by GitHub
parent 5eb8d6ed8a
commit 3063ada72f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
183 changed files with 10459 additions and 86 deletions

View File

@ -4,7 +4,7 @@ FastAPI basiert auf **Pydantic**, und ich habe Ihnen gezeigt, wie Sie Pydantic-M
Aber FastAPI unterstützt auf die gleiche Weise auch die Verwendung von <a href="https://docs.python.org/3/library/dataclasses.html" class="external-link" target="_blank">`dataclasses`</a>:
{* ../../docs_src/dataclasses/tutorial001_py310.py hl[1,6:11,18:19] *}
{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *}
Das ist dank **Pydantic** ebenfalls möglich, da es <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/#use-of-stdlib-dataclasses-with-basemodel" class="external-link" target="_blank">`dataclasses` intern unterstützt</a>.
@ -32,7 +32,7 @@ Wenn Sie jedoch eine Menge Datenklassen herumliegen haben, ist dies ein guter Tr
Sie können `dataclasses` auch im Parameter `response_model` verwenden:
{* ../../docs_src/dataclasses/tutorial002_py310.py hl[1,6:12,18] *}
{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *}
Die Datenklasse wird automatisch in eine Pydantic-Datenklasse konvertiert.
@ -48,7 +48,7 @@ In einigen Fällen müssen Sie möglicherweise immer noch Pydantics Version von
In diesem Fall können Sie einfach die Standard-`dataclasses` durch `pydantic.dataclasses` ersetzen, was einen direkten Ersatz darstellt:
{* ../../docs_src/dataclasses/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
1. Wir importieren `field` weiterhin von Standard-`dataclasses`.

View File

@ -35,7 +35,7 @@ Abhängig von Ihrem Anwendungsfall könnten Sie eine andere Bibliothek vorziehen
Hier ist eine kleine Vorschau, wie Sie Strawberry mit FastAPI integrieren können:
{* ../../docs_src/graphql/tutorial001_py39.py hl[3,22,25] *}
{* ../../docs_src/graphql_/tutorial001_py39.py hl[3,22,25] *}
Weitere Informationen zu Strawberry finden Sie in der <a href="https://strawberry.rocks/" class="external-link" target="_blank">Strawberry-Dokumentation</a>.

View File

@ -4,7 +4,7 @@ FastAPI is built on top of **Pydantic**, and I have been showing you how to use
But FastAPI also supports using <a href="https://docs.python.org/3/library/dataclasses.html" class="external-link" target="_blank">`dataclasses`</a> the same way:
{* ../../docs_src/dataclasses/tutorial001_py310.py hl[1,6:11,18:19] *}
{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *}
This is still supported thanks to **Pydantic**, as it has <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/#use-of-stdlib-dataclasses-with-basemodel" class="external-link" target="_blank">internal support for `dataclasses`</a>.
@ -32,7 +32,7 @@ But if you have a bunch of dataclasses laying around, this is a nice trick to us
You can also use `dataclasses` in the `response_model` parameter:
{* ../../docs_src/dataclasses/tutorial002_py310.py hl[1,6:12,18] *}
{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *}
The dataclass will be automatically converted to a Pydantic dataclass.
@ -48,7 +48,7 @@ In some cases, you might still have to use Pydantic's version of `dataclasses`.
In that case, you can simply swap the standard `dataclasses` with `pydantic.dataclasses`, which is a drop-in replacement:
{* ../../docs_src/dataclasses/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
1. We still import `field` from standard `dataclasses`.

View File

@ -35,7 +35,7 @@ Depending on your use case, you might prefer to use a different library, but if
Here's a small preview of how you could integrate Strawberry with FastAPI:
{* ../../docs_src/graphql/tutorial001_py39.py hl[3,22,25] *}
{* ../../docs_src/graphql_/tutorial001_py39.py hl[3,22,25] *}
You can learn more about Strawberry in the <a href="https://strawberry.rocks/" class="external-link" target="_blank">Strawberry documentation</a>.

View File

@ -4,7 +4,7 @@ FastAPI está construido sobre **Pydantic**, y te he estado mostrando cómo usar
Pero FastAPI también soporta el uso de <a href="https://docs.python.org/3/library/dataclasses.html" class="external-link" target="_blank">`dataclasses`</a> de la misma manera:
{* ../../docs_src/dataclasses/tutorial001_py310.py hl[1,6:11,18:19] *}
{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *}
Esto sigue siendo soportado gracias a **Pydantic**, ya que tiene <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/#use-of-stdlib-dataclasses-with-basemodel" class="external-link" target="_blank">soporte interno para `dataclasses`</a>.
@ -32,7 +32,7 @@ Pero si tienes un montón de dataclasses por ahí, este es un buen truco para us
También puedes usar `dataclasses` en el parámetro `response_model`:
{* ../../docs_src/dataclasses/tutorial002_py310.py hl[1,6:12,18] *}
{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *}
El dataclass será automáticamente convertido a un dataclass de Pydantic.
@ -48,7 +48,7 @@ En algunos casos, todavía podrías tener que usar la versión de `dataclasses`
En ese caso, simplemente puedes intercambiar los `dataclasses` estándar con `pydantic.dataclasses`, que es un reemplazo directo:
{* ../../docs_src/dataclasses/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
1. Todavía importamos `field` de los `dataclasses` estándar.

View File

@ -35,7 +35,7 @@ Dependiendo de tu caso de uso, podrías preferir usar un paquete diferente, pero
Aquí tienes una pequeña vista previa de cómo podrías integrar Strawberry con FastAPI:
{* ../../docs_src/graphql/tutorial001_py39.py hl[3,22,25] *}
{* ../../docs_src/graphql_/tutorial001_py39.py hl[3,22,25] *}
Puedes aprender más sobre Strawberry en la <a href="https://strawberry.rocks/" class="external-link" target="_blank">documentación de Strawberry</a>.

View File

@ -4,7 +4,7 @@ FastAPI é construído em cima do **Pydantic**, e eu tenho mostrado como usar mo
Mas o FastAPI também suporta o uso de <a href="https://docs.python.org/3/library/dataclasses.html" class="external-link" target="_blank">`dataclasses`</a> da mesma forma:
{* ../../docs_src/dataclasses/tutorial001_py310.py hl[1,6:11,18:19] *}
{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *}
Isso ainda é suportado graças ao **Pydantic**, pois ele tem <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/#use-of-stdlib-dataclasses-with-basemodel" class="external-link" target="_blank">suporte interno para `dataclasses`</a>.
@ -32,7 +32,7 @@ Mas se você tem um monte de dataclasses por aí, este é um truque legal para u
Você também pode usar `dataclasses` no parâmetro `response_model`:
{* ../../docs_src/dataclasses/tutorial002_py310.py hl[1,6:12,18] *}
{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *}
A dataclass será automaticamente convertida para uma dataclass Pydantic.
@ -48,7 +48,7 @@ Em alguns casos, você ainda pode ter que usar a versão do Pydantic das `datacl
Nesse caso, você pode simplesmente trocar as `dataclasses` padrão por `pydantic.dataclasses`, que é um substituto direto:
{* ../../docs_src/dataclasses/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
1. Ainda importamos `field` das `dataclasses` padrão.

View File

@ -35,7 +35,7 @@ Dependendo do seu caso de uso, você pode preferir usar uma biblioteca diferente
Aqui está uma pequena prévia de como você poderia integrar Strawberry com FastAPI:
{* ../../docs_src/graphql/tutorial001_py39.py hl[3,22,25] *}
{* ../../docs_src/graphql_/tutorial001_py39.py hl[3,22,25] *}
Você pode aprender mais sobre Strawberry na <a href="https://strawberry.rocks/" class="external-link" target="_blank">documentação do Strawberry</a>.

View File

@ -4,7 +4,7 @@ FastAPI построен поверх **Pydantic**, и я показывал в
Но FastAPI также поддерживает использование <a href="https://docs.python.org/3/library/dataclasses.html" class="external-link" target="_blank">`dataclasses`</a> тем же способом:
{* ../../docs_src/dataclasses/tutorial001_py310.py hl[1,6:11,18:19] *}
{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *}
Это по-прежнему поддерживается благодаря **Pydantic**, так как в нём есть <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/#use-of-stdlib-dataclasses-with-basemodel" class="external-link" target="_blank">встроенная поддержка `dataclasses`</a>.
@ -32,7 +32,7 @@ FastAPI построен поверх **Pydantic**, и я показывал в
Вы также можете использовать `dataclasses` в параметре `response_model`:
{* ../../docs_src/dataclasses/tutorial002_py310.py hl[1,6:12,18] *}
{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *}
Этот dataclass будет автоматически преобразован в Pydantic dataclass.
@ -48,7 +48,7 @@ FastAPI построен поверх **Pydantic**, и я показывал в
В таком случае вы можете просто заменить стандартные `dataclasses` на `pydantic.dataclasses`, которая является полностью совместимой заменой (drop-in replacement):
{* ../../docs_src/dataclasses/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
1. Мы по-прежнему импортируем `field` из стандартных `dataclasses`.

View File

@ -35,7 +35,7 @@
Вот небольшой пример того, как можно интегрировать Strawberry с FastAPI:
{* ../../docs_src/graphql/tutorial001_py39.py hl[3,22,25] *}
{* ../../docs_src/graphql_/tutorial001_py39.py hl[3,22,25] *}
Подробнее о Strawberry можно узнать в <a href="https://strawberry.rocks/" class="external-link" target="_blank">документации Strawberry</a>.

View File

@ -4,7 +4,7 @@ FastAPI 基于 **Pydantic** 构建,前文已经介绍过如何使用 Pydantic
但 FastAPI 还可以使用数据类(<a href="https://docs.python.org/3/library/dataclasses.html" class="external-link" target="_blank">`dataclasses`</a>
{* ../../docs_src/dataclasses/tutorial001.py hl[1,7:12,19:20] *}
{* ../../docs_src/dataclasses_/tutorial001.py hl[1,7:12,19:20] *}
这还是借助于 **Pydantic** 及其<a href="https://pydantic-docs.helpmanual.io/usage/dataclasses/#use-of-stdlib-dataclasses-with-basemodel" class="external-link" target="_blank">内置的 `dataclasses`</a>
@ -32,7 +32,7 @@ FastAPI 基于 **Pydantic** 构建,前文已经介绍过如何使用 Pydantic
`response_model` 参数中使用 `dataclasses`
{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *}
{* ../../docs_src/dataclasses_/tutorial002.py hl[1,7:13,19] *}
本例把数据类自动转换为 Pydantic 数据类。
@ -49,7 +49,7 @@ API 文档中也会显示相关概图:
本例把标准的 `dataclasses` 直接替换为 `pydantic.dataclasses`
```{ .python .annotate hl_lines="1 5 8-11 14-17 23-25 28" }
{!../../docs_src/dataclasses/tutorial003.py!}
{!../../docs_src/dataclasses_/tutorial003.py!}
```
1. 本例依然要从标准的 `dataclasses` 中导入 `field`

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

@ -196,6 +196,17 @@ dynamic_context = "test_function"
omit = [
"docs_src/response_model/tutorial003_04_py39.py",
"docs_src/response_model/tutorial003_04_py310.py",
"docs_src/dependencies/tutorial008_an_py39.py", # difficult to mock
"docs_src/dependencies/tutorial013_an_py310.py", # temporary code example?
"docs_src/dependencies/tutorial014_an_py310.py", # temporary code example?
# Pydantic V1
"docs_src/schema_extra_example/tutorial001_pv1_py310.py",
"docs_src/query_param_models/tutorial002_pv1_py310.py",
"docs_src/query_param_models/tutorial002_pv1_an_py310.py",
"docs_src/header_param_models/tutorial002_pv1_py310.py",
"docs_src/header_param_models/tutorial002_pv1_an_py310.py",
"docs_src/cookie_param_models/tutorial002_pv1_py310.py",
"docs_src/cookie_param_models/tutorial002_pv1_an_py310.py",
]
[tool.coverage.report]

View File

@ -6,6 +6,7 @@ mypy ==1.14.1
dirty-equals ==0.9.0
sqlmodel==0.0.27
flask >=1.1.2,<4.0.0
strawberry-graphql >=0.200.0,< 1.0.0
anyio[trio] >=3.2.1,<5.0.0
PyJWT==2.9.0
pyyaml >=5.3.1,<7.0.0

View File

@ -0,0 +1,161 @@
import importlib
from typing import Union
import pytest
from fastapi.testclient import TestClient
from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
pytest.param("tutorial002_py39"),
pytest.param("tutorial002_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.body.{request.param}")
client = TestClient(mod.app)
return client
@pytest.mark.parametrize("price", ["50.5", 50.5])
def test_post_with_tax(client: TestClient, price: Union[str, float]):
response = client.post(
"/items/",
json={"name": "Foo", "price": price, "description": "Some Foo", "tax": 0.3},
)
assert response.status_code == 200
assert response.json() == {
"name": "Foo",
"price": 50.5,
"description": "Some Foo",
"tax": 0.3,
"price_with_tax": 50.8,
}
@pytest.mark.parametrize("price", ["50.5", 50.5])
def test_post_without_tax(client: TestClient, price: Union[str, float]):
response = client.post(
"/items/", json={"name": "Foo", "price": price, "description": "Some Foo"}
)
assert response.status_code == 200
assert response.json() == {
"name": "Foo",
"price": 50.5,
"description": "Some Foo",
"tax": None,
}
def test_post_with_no_data(client: TestClient):
response = client.post("/items/", json={})
assert response.status_code == 422
assert response.json() == {
"detail": [
{
"type": "missing",
"loc": ["body", "name"],
"msg": "Field required",
"input": {},
},
{
"type": "missing",
"loc": ["body", "price"],
"msg": "Field required",
"input": {},
},
]
}
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": {
"/items/": {
"post": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Create Item",
"operationId": "create_item_items__post",
"requestBody": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/Item"}
}
},
"required": True,
},
}
}
},
"components": {
"schemas": {
"Item": {
"title": "Item",
"required": ["name", "price"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"price": {"title": "Price", "type": "number"},
"description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
"tax": {
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
},
},
},
"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"},
}
},
},
}
},
}

View File

@ -0,0 +1,171 @@
import importlib
import pytest
from fastapi.testclient import TestClient
from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
pytest.param("tutorial003_py39"),
pytest.param("tutorial003_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.body.{request.param}")
client = TestClient(mod.app)
return client
def test_put_all(client: TestClient):
response = client.put(
"/items/123",
json={"name": "Foo", "price": 50.1, "description": "Some Foo", "tax": 0.3},
)
assert response.status_code == 200
assert response.json() == {
"item_id": 123,
"name": "Foo",
"price": 50.1,
"description": "Some Foo",
"tax": 0.3,
}
def test_put_only_required(client: TestClient):
response = client.put(
"/items/123",
json={"name": "Foo", "price": 50.1},
)
assert response.status_code == 200
assert response.json() == {
"item_id": 123,
"name": "Foo",
"price": 50.1,
"description": None,
"tax": None,
}
def test_put_with_no_data(client: TestClient):
response = client.put("/items/123", json={})
assert response.status_code == 422
assert response.json() == {
"detail": [
{
"type": "missing",
"loc": ["body", "name"],
"msg": "Field required",
"input": {},
},
{
"type": "missing",
"loc": ["body", "price"],
"msg": "Field required",
"input": {},
},
]
}
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": {
"/items/{item_id}": {
"put": {
"parameters": [
{
"in": "path",
"name": "item_id",
"required": True,
"schema": {
"title": "Item Id",
"type": "integer",
},
},
],
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"requestBody": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/Item"}
}
},
"required": True,
},
}
}
},
"components": {
"schemas": {
"Item": {
"title": "Item",
"required": ["name", "price"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"price": {"title": "Price", "type": "number"},
"description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
"tax": {
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
},
},
},
"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"},
}
},
},
}
},
}

View File

@ -0,0 +1,182 @@
import importlib
import pytest
from fastapi.testclient import TestClient
from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
pytest.param("tutorial004_py39"),
pytest.param("tutorial004_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.body.{request.param}")
client = TestClient(mod.app)
return client
def test_put_all(client: TestClient):
response = client.put(
"/items/123",
json={"name": "Foo", "price": 50.1, "description": "Some Foo", "tax": 0.3},
params={"q": "somequery"},
)
assert response.status_code == 200
assert response.json() == {
"item_id": 123,
"name": "Foo",
"price": 50.1,
"description": "Some Foo",
"tax": 0.3,
"q": "somequery",
}
def test_put_only_required(client: TestClient):
response = client.put(
"/items/123",
json={"name": "Foo", "price": 50.1},
)
assert response.status_code == 200
assert response.json() == {
"item_id": 123,
"name": "Foo",
"price": 50.1,
"description": None,
"tax": None,
}
def test_put_with_no_data(client: TestClient):
response = client.put("/items/123", json={})
assert response.status_code == 422
assert response.json() == {
"detail": [
{
"type": "missing",
"loc": ["body", "name"],
"msg": "Field required",
"input": {},
},
{
"type": "missing",
"loc": ["body", "price"],
"msg": "Field required",
"input": {},
},
]
}
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": {
"/items/{item_id}": {
"put": {
"parameters": [
{
"in": "path",
"name": "item_id",
"required": True,
"schema": {
"title": "Item Id",
"type": "integer",
},
},
{
"required": False,
"schema": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Q",
},
"name": "q",
"in": "query",
},
],
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"requestBody": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/Item"}
}
},
"required": True,
},
}
}
},
"components": {
"schemas": {
"Item": {
"title": "Item",
"required": ["name", "price"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"price": {"title": "Price", "type": "number"},
"description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
"tax": {
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
},
},
},
"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"},
}
},
},
}
},
}

View File

@ -0,0 +1,361 @@
import importlib
import pytest
from fastapi.testclient import TestClient
from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
pytest.param("tutorial002_py39"),
pytest.param("tutorial002_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.body_multiple_params.{request.param}")
client = TestClient(mod.app)
return client
def test_post_all(client: TestClient):
response = client.put(
"/items/5",
json={
"item": {
"name": "Foo",
"price": 50.5,
"description": "Some Foo",
"tax": 0.1,
},
"user": {"username": "johndoe", "full_name": "John Doe"},
},
)
assert response.status_code == 200
assert response.json() == {
"item_id": 5,
"item": {
"name": "Foo",
"price": 50.5,
"description": "Some Foo",
"tax": 0.1,
},
"user": {"username": "johndoe", "full_name": "John Doe"},
}
def test_post_required(client: TestClient):
response = client.put(
"/items/5",
json={
"item": {"name": "Foo", "price": 50.5},
"user": {"username": "johndoe"},
},
)
assert response.status_code == 200
assert response.json() == {
"item_id": 5,
"item": {
"name": "Foo",
"price": 50.5,
"description": None,
"tax": None,
},
"user": {"username": "johndoe", "full_name": None},
}
def test_post_no_body(client: TestClient):
response = client.put("/items/5", json=None)
assert response.status_code == 422
assert response.json() == {
"detail": [
{
"input": None,
"loc": [
"body",
"item",
],
"msg": "Field required",
"type": "missing",
},
{
"input": None,
"loc": [
"body",
"user",
],
"msg": "Field required",
"type": "missing",
},
],
}
def test_post_no_item(client: TestClient):
response = client.put("/items/5", json={"user": {"username": "johndoe"}})
assert response.status_code == 422
assert response.json() == {
"detail": [
{
"input": None,
"loc": [
"body",
"item",
],
"msg": "Field required",
"type": "missing",
},
],
}
def test_post_no_user(client: TestClient):
response = client.put("/items/5", json={"item": {"name": "Foo", "price": 50.5}})
assert response.status_code == 422
assert response.json() == {
"detail": [
{
"input": None,
"loc": [
"body",
"user",
],
"msg": "Field required",
"type": "missing",
},
],
}
def test_post_missing_required_field_in_item(client: TestClient):
response = client.put(
"/items/5", json={"item": {"name": "Foo"}, "user": {"username": "johndoe"}}
)
assert response.status_code == 422
assert response.json() == {
"detail": [
{
"input": {"name": "Foo"},
"loc": [
"body",
"item",
"price",
],
"msg": "Field required",
"type": "missing",
},
],
}
def test_post_missing_required_field_in_user(client: TestClient):
response = client.put(
"/items/5",
json={"item": {"name": "Foo", "price": 50.5}, "user": {"ful_name": "John Doe"}},
)
assert response.status_code == 422
assert response.json() == {
"detail": [
{
"input": {"ful_name": "John Doe"},
"loc": [
"body",
"user",
"username",
],
"msg": "Field required",
"type": "missing",
},
],
}
def test_post_id_foo(client: TestClient):
response = client.put(
"/items/foo",
json={
"item": {"name": "Foo", "price": 50.5},
"user": {"username": "johndoe"},
},
)
assert response.status_code == 422
assert response.json() == {
"detail": [
{
"type": "int_parsing",
"loc": ["path", "item_id"],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "foo",
}
]
}
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"info": {
"title": "FastAPI",
"version": "0.1.0",
},
"openapi": "3.1.0",
"paths": {
"/items/{item_id}": {
"put": {
"operationId": "update_item_items__item_id__put",
"parameters": [
{
"in": "path",
"name": "item_id",
"required": True,
"schema": {
"title": "Item Id",
"type": "integer",
},
},
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Body_update_item_items__item_id__put",
},
},
},
"required": True,
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {},
},
},
"description": "Successful Response",
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError",
},
},
},
"description": "Validation Error",
},
},
"summary": "Update Item",
},
},
},
"components": {
"schemas": {
"Body_update_item_items__item_id__put": {
"properties": {
"item": {
"$ref": "#/components/schemas/Item",
},
"user": {
"$ref": "#/components/schemas/User",
},
},
"required": [
"item",
"user",
],
"title": "Body_update_item_items__item_id__put",
"type": "object",
},
"HTTPValidationError": {
"properties": {
"detail": {
"items": {
"$ref": "#/components/schemas/ValidationError",
},
"title": "Detail",
"type": "array",
},
},
"title": "HTTPValidationError",
"type": "object",
},
"Item": {
"properties": {
"name": {
"title": "Name",
"type": "string",
},
"description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
"price": {"title": "Price", "type": "number"},
"tax": {
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
},
},
"required": [
"name",
"price",
],
"title": "Item",
"type": "object",
},
"User": {
"properties": {
"username": {
"title": "Username",
"type": "string",
},
"full_name": {
"title": "Full Name",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
},
"required": [
"username",
],
"title": "User",
"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",
},
},
},
}

View File

@ -0,0 +1,290 @@
import importlib
import pytest
from fastapi.testclient import TestClient
from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
pytest.param("tutorial004_py39"),
pytest.param("tutorial004_py310", marks=needs_py310),
pytest.param("tutorial004_an_py39"),
pytest.param("tutorial004_an_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.body_multiple_params.{request.param}")
client = TestClient(mod.app)
return client
def test_put_all(client: TestClient):
response = client.put(
"/items/5",
json={
"importance": 2,
"item": {"name": "Foo", "price": 50.5},
"user": {"username": "Dave"},
},
params={"q": "somequery"},
)
assert response.status_code == 200
assert response.json() == {
"item_id": 5,
"importance": 2,
"item": {
"name": "Foo",
"price": 50.5,
"description": None,
"tax": None,
},
"user": {"username": "Dave", "full_name": None},
"q": "somequery",
}
def test_put_only_required(client: TestClient):
response = client.put(
"/items/5",
json={
"importance": 2,
"item": {"name": "Foo", "price": 50.5},
"user": {"username": "Dave"},
},
)
assert response.status_code == 200
assert response.json() == {
"item_id": 5,
"importance": 2,
"item": {
"name": "Foo",
"price": 50.5,
"description": None,
"tax": None,
},
"user": {"username": "Dave", "full_name": None},
}
def test_put_missing_body(client: TestClient):
response = client.put("/items/5")
assert response.status_code == 422
assert response.json() == {
"detail": [
{
"input": None,
"loc": [
"body",
"item",
],
"msg": "Field required",
"type": "missing",
},
{
"input": None,
"loc": [
"body",
"user",
],
"msg": "Field required",
"type": "missing",
},
{
"input": None,
"loc": [
"body",
"importance",
],
"msg": "Field required",
"type": "missing",
},
],
}
def test_put_empty_body(client: TestClient):
response = client.put("/items/5", json={})
assert response.status_code == 422
assert response.json() == {
"detail": [
{
"type": "missing",
"loc": ["body", "item"],
"msg": "Field required",
"input": None,
},
{
"type": "missing",
"loc": ["body", "user"],
"msg": "Field required",
"input": None,
},
{
"type": "missing",
"loc": ["body", "importance"],
"msg": "Field required",
"input": None,
},
]
}
def test_put_invalid_importance(client: TestClient):
response = client.put(
"/items/5",
json={
"importance": 0,
"item": {"name": "Foo", "price": 50.5},
"user": {"username": "Dave"},
},
)
assert response.status_code == 422
assert response.json() == {
"detail": [
{
"loc": ["body", "importance"],
"msg": "Input should be greater than 0",
"type": "greater_than",
"input": 0,
"ctx": {"gt": 0},
},
],
}
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": {
"/items/{item_id}": {
"put": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"parameters": [
{
"required": True,
"schema": {"title": "Item Id", "type": "integer"},
"name": "item_id",
"in": "path",
},
{
"required": False,
"schema": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Q",
},
"name": "q",
"in": "query",
},
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Body_update_item_items__item_id__put"
}
}
},
"required": True,
},
}
}
},
"components": {
"schemas": {
"Item": {
"title": "Item",
"required": ["name", "price"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
"price": {"title": "Price", "type": "number"},
"tax": {
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
},
},
},
"User": {
"title": "User",
"required": ["username"],
"type": "object",
"properties": {
"username": {"title": "Username", "type": "string"},
"full_name": {
"title": "Full Name",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
},
},
"Body_update_item_items__item_id__put": {
"title": "Body_update_item_items__item_id__put",
"required": ["item", "user", "importance"],
"type": "object",
"properties": {
"item": {"$ref": "#/components/schemas/Item"},
"user": {"$ref": "#/components/schemas/User"},
"importance": {
"title": "Importance",
"type": "integer",
"exclusiveMinimum": 0.0,
},
},
},
"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"},
}
},
},
}
},
}

View File

@ -0,0 +1,272 @@
import importlib
import pytest
from fastapi.testclient import TestClient
from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
pytest.param("tutorial005_py39"),
pytest.param("tutorial005_py310", marks=needs_py310),
pytest.param("tutorial005_an_py39"),
pytest.param("tutorial005_an_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.body_multiple_params.{request.param}")
client = TestClient(mod.app)
return client
def test_post_all(client: TestClient):
response = client.put(
"/items/5",
json={
"item": {
"name": "Foo",
"price": 50.5,
"description": "Some Foo",
"tax": 0.1,
},
},
)
assert response.status_code == 200
assert response.json() == {
"item_id": 5,
"item": {
"name": "Foo",
"price": 50.5,
"description": "Some Foo",
"tax": 0.1,
},
}
def test_post_required(client: TestClient):
response = client.put(
"/items/5",
json={
"item": {"name": "Foo", "price": 50.5},
},
)
assert response.status_code == 200
assert response.json() == {
"item_id": 5,
"item": {
"name": "Foo",
"price": 50.5,
"description": None,
"tax": None,
},
}
def test_post_no_body(client: TestClient):
response = client.put("/items/5", json=None)
assert response.status_code == 422
assert response.json() == {
"detail": [
{
"input": None,
"loc": [
"body",
"item",
],
"msg": "Field required",
"type": "missing",
},
],
}
def test_post_like_not_embeded(client: TestClient):
response = client.put(
"/items/5",
json={
"name": "Foo",
"price": 50.5,
},
)
assert response.status_code == 422
assert response.json() == {
"detail": [
{
"input": None,
"loc": [
"body",
"item",
],
"msg": "Field required",
"type": "missing",
},
],
}
def test_post_missing_required_field_in_item(client: TestClient):
response = client.put(
"/items/5", json={"item": {"name": "Foo"}, "user": {"username": "johndoe"}}
)
assert response.status_code == 422
assert response.json() == {
"detail": [
{
"input": {"name": "Foo"},
"loc": [
"body",
"item",
"price",
],
"msg": "Field required",
"type": "missing",
},
],
}
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"info": {
"title": "FastAPI",
"version": "0.1.0",
},
"openapi": "3.1.0",
"paths": {
"/items/{item_id}": {
"put": {
"operationId": "update_item_items__item_id__put",
"parameters": [
{
"in": "path",
"name": "item_id",
"required": True,
"schema": {
"title": "Item Id",
"type": "integer",
},
},
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Body_update_item_items__item_id__put",
},
},
},
"required": True,
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {},
},
},
"description": "Successful Response",
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError",
},
},
},
"description": "Validation Error",
},
},
"summary": "Update Item",
},
},
},
"components": {
"schemas": {
"Body_update_item_items__item_id__put": {
"properties": {
"item": {
"$ref": "#/components/schemas/Item",
},
},
"required": ["item"],
"title": "Body_update_item_items__item_id__put",
"type": "object",
},
"HTTPValidationError": {
"properties": {
"detail": {
"items": {
"$ref": "#/components/schemas/ValidationError",
},
"title": "Detail",
"type": "array",
},
},
"title": "HTTPValidationError",
"type": "object",
},
"Item": {
"properties": {
"name": {
"title": "Name",
"type": "string",
},
"description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
"price": {"title": "Price", "type": "number"},
"tax": {
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
},
},
"required": [
"name",
"price",
],
"title": "Item",
"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",
},
},
},
}

View File

@ -0,0 +1,251 @@
import importlib
import pytest
from dirty_equals import IsList
from fastapi.testclient import TestClient
from ...utils import needs_py310
UNTYPED_LIST_SCHEMA = {"type": "array", "items": {}}
LIST_OF_STR_SCHEMA = {"type": "array", "items": {"type": "string"}}
SET_OF_STR_SCHEMA = {"type": "array", "items": {"type": "string"}, "uniqueItems": True}
@pytest.fixture(
name="mod_name",
params=[
pytest.param("tutorial001_py39"),
pytest.param("tutorial001_py310", marks=needs_py310),
pytest.param("tutorial002_py39"),
pytest.param("tutorial002_py310", marks=needs_py310),
pytest.param("tutorial003_py39"),
pytest.param("tutorial003_py310", marks=needs_py310),
],
)
def get_mod_name(request: pytest.FixtureRequest):
return request.param
@pytest.fixture(name="client")
def get_client(mod_name: str):
mod = importlib.import_module(f"docs_src.body_nested_models.{mod_name}")
client = TestClient(mod.app)
return client
def test_put_all(client: TestClient, mod_name: str):
if mod_name.startswith("tutorial003"):
tags_expected = IsList("foo", "bar", check_order=False)
else:
tags_expected = ["foo", "bar", "foo"]
response = client.put(
"/items/123",
json={
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
"tags": ["foo", "bar", "foo"],
},
)
assert response.status_code == 200, response.text
assert response.json() == {
"item_id": 123,
"item": {
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
"tags": tags_expected,
},
}
def test_put_only_required(client: TestClient):
response = client.put(
"/items/5",
json={"name": "Foo", "price": 35.4},
)
assert response.status_code == 200, response.text
assert response.json() == {
"item_id": 5,
"item": {
"name": "Foo",
"description": None,
"price": 35.4,
"tax": None,
"tags": [],
},
}
def test_put_empty_body(client: TestClient):
response = client.put(
"/items/5",
json={},
)
assert response.status_code == 422, response.text
assert response.json() == {
"detail": [
{
"loc": ["body", "name"],
"input": {},
"msg": "Field required",
"type": "missing",
},
{
"loc": ["body", "price"],
"input": {},
"msg": "Field required",
"type": "missing",
},
]
}
def test_put_missing_required(client: TestClient):
response = client.put(
"/items/5",
json={"description": "A very nice Item"},
)
assert response.status_code == 422, response.text
assert response.json() == {
"detail": [
{
"loc": ["body", "name"],
"input": {"description": "A very nice Item"},
"msg": "Field required",
"type": "missing",
},
{
"loc": ["body", "price"],
"input": {"description": "A very nice Item"},
"msg": "Field required",
"type": "missing",
},
]
}
def test_openapi_schema(client: TestClient, mod_name: str):
tags_schema = {"default": [], "title": "Tags"}
if mod_name.startswith("tutorial001"):
tags_schema.update(UNTYPED_LIST_SCHEMA)
elif mod_name.startswith("tutorial002"):
tags_schema.update(LIST_OF_STR_SCHEMA)
elif mod_name.startswith("tutorial003"):
tags_schema.update(SET_OF_STR_SCHEMA)
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": {
"/items/{item_id}": {
"put": {
"parameters": [
{
"in": "path",
"name": "item_id",
"required": True,
"schema": {
"title": "Item Id",
"type": "integer",
},
},
],
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Item",
}
}
},
"required": True,
},
}
}
},
"components": {
"schemas": {
"Item": {
"properties": {
"name": {
"title": "Name",
"type": "string",
},
"description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
"price": {
"title": "Price",
"type": "number",
},
"tax": {
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
},
"tags": tags_schema,
},
"required": [
"name",
"price",
],
"title": "Item",
"type": "object",
},
"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"},
}
},
},
}
},
}

View File

@ -0,0 +1,275 @@
import importlib
import pytest
from dirty_equals import IsList
from fastapi.testclient import TestClient
from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
pytest.param("tutorial004_py39"),
pytest.param("tutorial004_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.body_nested_models.{request.param}")
client = TestClient(mod.app)
return client
def test_put_all(client: TestClient):
response = client.put(
"/items/123",
json={
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
"tags": ["foo", "bar", "foo"],
"image": {"url": "http://example.com/image.png", "name": "example image"},
},
)
assert response.status_code == 200, response.text
assert response.json() == {
"item_id": 123,
"item": {
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
"tags": IsList("foo", "bar", check_order=False),
"image": {"url": "http://example.com/image.png", "name": "example image"},
},
}
def test_put_only_required(client: TestClient):
response = client.put(
"/items/5",
json={"name": "Foo", "price": 35.4},
)
assert response.status_code == 200, response.text
assert response.json() == {
"item_id": 5,
"item": {
"name": "Foo",
"description": None,
"price": 35.4,
"tax": None,
"tags": [],
"image": None,
},
}
def test_put_empty_body(client: TestClient):
response = client.put(
"/items/5",
json={},
)
assert response.status_code == 422, response.text
assert response.json() == {
"detail": [
{
"loc": ["body", "name"],
"input": {},
"msg": "Field required",
"type": "missing",
},
{
"loc": ["body", "price"],
"input": {},
"msg": "Field required",
"type": "missing",
},
]
}
def test_put_missing_required_in_item(client: TestClient):
response = client.put(
"/items/5",
json={"description": "A very nice Item"},
)
assert response.status_code == 422, response.text
assert response.json() == {
"detail": [
{
"loc": ["body", "name"],
"input": {"description": "A very nice Item"},
"msg": "Field required",
"type": "missing",
},
{
"loc": ["body", "price"],
"input": {"description": "A very nice Item"},
"msg": "Field required",
"type": "missing",
},
]
}
def test_put_missing_required_in_image(client: TestClient):
response = client.put(
"/items/5",
json={
"name": "Foo",
"price": 35.4,
"image": {"url": "http://example.com/image.png"},
},
)
assert response.status_code == 422, response.text
assert response.json() == {
"detail": [
{
"loc": ["body", "image", "name"],
"input": {"url": "http://example.com/image.png"},
"msg": "Field required",
"type": "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": {
"/items/{item_id}": {
"put": {
"parameters": [
{
"in": "path",
"name": "item_id",
"required": True,
"schema": {
"title": "Item Id",
"type": "integer",
},
},
],
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Item",
}
}
},
"required": True,
},
}
}
},
"components": {
"schemas": {
"Image": {
"properties": {
"url": {
"title": "Url",
"type": "string",
},
"name": {
"title": "Name",
"type": "string",
},
},
"required": ["url", "name"],
"title": "Image",
"type": "object",
},
"Item": {
"properties": {
"name": {
"title": "Name",
"type": "string",
},
"description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
"price": {
"title": "Price",
"type": "number",
},
"tax": {
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
},
"tags": {
"title": "Tags",
"default": [],
"type": "array",
"items": {"type": "string"},
"uniqueItems": True,
},
"image": {
"anyOf": [
{"$ref": "#/components/schemas/Image"},
{"type": "null"},
],
},
},
"required": [
"name",
"price",
],
"title": "Item",
"type": "object",
},
"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"},
}
},
},
}
},
}

View File

@ -0,0 +1,301 @@
import importlib
import pytest
from dirty_equals import IsList
from fastapi.testclient import TestClient
from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
pytest.param("tutorial005_py39"),
pytest.param("tutorial005_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.body_nested_models.{request.param}")
client = TestClient(mod.app)
return client
def test_put_all(client: TestClient):
response = client.put(
"/items/123",
json={
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
"tags": ["foo", "bar", "foo"],
"image": {"url": "http://example.com/image.png", "name": "example image"},
},
)
assert response.status_code == 200, response.text
assert response.json() == {
"item_id": 123,
"item": {
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
"tags": IsList("foo", "bar", check_order=False),
"image": {"url": "http://example.com/image.png", "name": "example image"},
},
}
def test_put_only_required(client: TestClient):
response = client.put(
"/items/5",
json={"name": "Foo", "price": 35.4},
)
assert response.status_code == 200, response.text
assert response.json() == {
"item_id": 5,
"item": {
"name": "Foo",
"description": None,
"price": 35.4,
"tax": None,
"tags": [],
"image": None,
},
}
def test_put_empty_body(client: TestClient):
response = client.put(
"/items/5",
json={},
)
assert response.status_code == 422, response.text
assert response.json() == {
"detail": [
{
"loc": ["body", "name"],
"input": {},
"msg": "Field required",
"type": "missing",
},
{
"loc": ["body", "price"],
"input": {},
"msg": "Field required",
"type": "missing",
},
]
}
def test_put_missing_required_in_item(client: TestClient):
response = client.put(
"/items/5",
json={"description": "A very nice Item"},
)
assert response.status_code == 422, response.text
assert response.json() == {
"detail": [
{
"loc": ["body", "name"],
"input": {"description": "A very nice Item"},
"msg": "Field required",
"type": "missing",
},
{
"loc": ["body", "price"],
"input": {"description": "A very nice Item"},
"msg": "Field required",
"type": "missing",
},
]
}
def test_put_missing_required_in_image(client: TestClient):
response = client.put(
"/items/5",
json={
"name": "Foo",
"price": 35.4,
"image": {"url": "http://example.com/image.png"},
},
)
assert response.status_code == 422, response.text
assert response.json() == {
"detail": [
{
"loc": ["body", "image", "name"],
"input": {"url": "http://example.com/image.png"},
"msg": "Field required",
"type": "missing",
},
]
}
def test_put_wrong_url(client: TestClient):
response = client.put(
"/items/5",
json={
"name": "Foo",
"price": 35.4,
"image": {"url": "not a valid url", "name": "example image"},
},
)
assert response.status_code == 422, response.text
assert response.json() == {
"detail": [
{
"loc": ["body", "image", "url"],
"input": "not a valid url",
"msg": "Input should be a valid URL, relative URL without a base",
"type": "url_parsing",
"ctx": {"error": "relative URL without a base"},
},
]
}
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": {
"/items/{item_id}": {
"put": {
"parameters": [
{
"in": "path",
"name": "item_id",
"required": True,
"schema": {
"title": "Item Id",
"type": "integer",
},
},
],
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Item",
}
}
},
"required": True,
},
}
}
},
"components": {
"schemas": {
"Image": {
"properties": {
"url": {
"title": "Url",
"type": "string",
"format": "uri",
"maxLength": 2083,
"minLength": 1,
},
"name": {
"title": "Name",
"type": "string",
},
},
"required": ["url", "name"],
"title": "Image",
"type": "object",
},
"Item": {
"properties": {
"name": {
"title": "Name",
"type": "string",
},
"description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
"price": {
"title": "Price",
"type": "number",
},
"tax": {
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
},
"tags": {
"title": "Tags",
"default": [],
"type": "array",
"items": {"type": "string"},
"uniqueItems": True,
},
"image": {
"anyOf": [
{"$ref": "#/components/schemas/Image"},
{"type": "null"},
],
},
},
"required": [
"name",
"price",
],
"title": "Item",
"type": "object",
},
"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"},
}
},
},
}
},
}

View File

@ -0,0 +1,269 @@
import importlib
import pytest
from dirty_equals import IsList
from fastapi.testclient import TestClient
from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
pytest.param("tutorial006_py39"),
pytest.param("tutorial006_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.body_nested_models.{request.param}")
client = TestClient(mod.app)
return client
def test_put_all(client: TestClient):
response = client.put(
"/items/123",
json={
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
"tags": ["foo", "bar", "foo"],
"images": [
{"url": "http://example.com/image.png", "name": "example image"}
],
},
)
assert response.status_code == 200, response.text
assert response.json() == {
"item_id": 123,
"item": {
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
"tags": IsList("foo", "bar", check_order=False),
"images": [
{"url": "http://example.com/image.png", "name": "example image"}
],
},
}
def test_put_only_required(client: TestClient):
response = client.put(
"/items/5",
json={"name": "Foo", "price": 35.4},
)
assert response.status_code == 200, response.text
assert response.json() == {
"item_id": 5,
"item": {
"name": "Foo",
"description": None,
"price": 35.4,
"tax": None,
"tags": [],
"images": None,
},
}
def test_put_empty_body(client: TestClient):
response = client.put(
"/items/5",
json={},
)
assert response.status_code == 422, response.text
assert response.json() == {
"detail": [
{
"loc": ["body", "name"],
"input": {},
"msg": "Field required",
"type": "missing",
},
{
"loc": ["body", "price"],
"input": {},
"msg": "Field required",
"type": "missing",
},
]
}
def test_put_images_not_list(client: TestClient):
response = client.put(
"/items/5",
json={
"name": "Foo",
"price": 35.4,
"images": {"url": "http://example.com/image.png", "name": "example image"},
},
)
assert response.status_code == 422, response.text
assert response.json() == {
"detail": [
{
"loc": ["body", "images"],
"input": {
"url": "http://example.com/image.png",
"name": "example image",
},
"msg": "Input should be a valid list",
"type": "list_type",
},
]
}
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": {
"/items/{item_id}": {
"put": {
"parameters": [
{
"in": "path",
"name": "item_id",
"required": True,
"schema": {
"title": "Item Id",
"type": "integer",
},
},
],
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Item",
}
}
},
"required": True,
},
}
}
},
"components": {
"schemas": {
"Image": {
"properties": {
"url": {
"title": "Url",
"type": "string",
"format": "uri",
"maxLength": 2083,
"minLength": 1,
},
"name": {
"title": "Name",
"type": "string",
},
},
"required": ["url", "name"],
"title": "Image",
"type": "object",
},
"Item": {
"properties": {
"name": {
"title": "Name",
"type": "string",
},
"description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
"price": {
"title": "Price",
"type": "number",
},
"tax": {
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
},
"tags": {
"title": "Tags",
"default": [],
"type": "array",
"items": {"type": "string"},
"uniqueItems": True,
},
"images": {
"anyOf": [
{
"items": {
"$ref": "#/components/schemas/Image",
},
"type": "array",
},
{
"type": "null",
},
],
"title": "Images",
},
},
"required": [
"name",
"price",
],
"title": "Item",
"type": "object",
},
"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"},
}
},
},
}
},
}

View File

@ -0,0 +1,344 @@
import importlib
import pytest
from fastapi.testclient import TestClient
from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
pytest.param("tutorial007_py39"),
pytest.param("tutorial007_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.body_nested_models.{request.param}")
client = TestClient(mod.app)
return client
def test_post_all(client: TestClient):
data = {
"name": "Special Offer",
"description": "This is a special offer",
"price": 38.6,
"items": [
{
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
"tags": ["foo"],
"images": [
{
"url": "http://example.com/image.png",
"name": "example image",
}
],
}
],
}
response = client.post(
"/offers/",
json=data,
)
assert response.status_code == 200, response.text
assert response.json() == data
def test_put_only_required(client: TestClient):
response = client.post(
"/offers/",
json={
"name": "Special Offer",
"price": 38.6,
"items": [
{
"name": "Foo",
"price": 35.4,
"images": [
{
"url": "http://example.com/image.png",
"name": "example image",
}
],
}
],
},
)
assert response.status_code == 200, response.text
assert response.json() == {
"name": "Special Offer",
"description": None,
"price": 38.6,
"items": [
{
"name": "Foo",
"description": None,
"price": 35.4,
"tax": None,
"tags": [],
"images": [
{
"url": "http://example.com/image.png",
"name": "example image",
}
],
}
],
}
def test_put_empty_body(client: TestClient):
response = client.post(
"/offers/",
json={},
)
assert response.status_code == 422, response.text
assert response.json() == {
"detail": [
{
"loc": ["body", "name"],
"input": {},
"msg": "Field required",
"type": "missing",
},
{
"loc": ["body", "price"],
"input": {},
"msg": "Field required",
"type": "missing",
},
{
"loc": ["body", "items"],
"input": {},
"msg": "Field required",
"type": "missing",
},
]
}
def test_put_missing_required_in_items(client: TestClient):
response = client.post(
"/offers/",
json={
"name": "Special Offer",
"price": 38.6,
"items": [{}],
},
)
assert response.status_code == 422, response.text
assert response.json() == {
"detail": [
{
"loc": ["body", "items", 0, "name"],
"input": {},
"msg": "Field required",
"type": "missing",
},
{
"loc": ["body", "items", 0, "price"],
"input": {},
"msg": "Field required",
"type": "missing",
},
]
}
def test_put_missing_required_in_images(client: TestClient):
response = client.post(
"/offers/",
json={
"name": "Special Offer",
"price": 38.6,
"items": [
{"name": "Foo", "price": 35.4, "images": [{}]},
],
},
)
assert response.status_code == 422, response.text
assert response.json() == {
"detail": [
{
"loc": ["body", "items", 0, "images", 0, "url"],
"input": {},
"msg": "Field required",
"type": "missing",
},
{
"loc": ["body", "items", 0, "images", 0, "name"],
"input": {},
"msg": "Field required",
"type": "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": {
"/offers/": {
"post": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Create Offer",
"operationId": "create_offer_offers__post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Offer",
}
}
},
"required": True,
},
}
}
},
"components": {
"schemas": {
"Image": {
"properties": {
"url": {
"title": "Url",
"type": "string",
"format": "uri",
"maxLength": 2083,
"minLength": 1,
},
"name": {
"title": "Name",
"type": "string",
},
},
"required": ["url", "name"],
"title": "Image",
"type": "object",
},
"Item": {
"properties": {
"name": {
"title": "Name",
"type": "string",
},
"description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
"price": {
"title": "Price",
"type": "number",
},
"tax": {
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
},
"tags": {
"title": "Tags",
"default": [],
"type": "array",
"items": {"type": "string"},
"uniqueItems": True,
},
"images": {
"anyOf": [
{
"items": {
"$ref": "#/components/schemas/Image",
},
"type": "array",
},
{
"type": "null",
},
],
"title": "Images",
},
},
"required": [
"name",
"price",
],
"title": "Item",
"type": "object",
},
"Offer": {
"properties": {
"name": {
"title": "Name",
"type": "string",
},
"description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
"price": {
"title": "Price",
"type": "number",
},
"items": {
"title": "Items",
"type": "array",
"items": {"$ref": "#/components/schemas/Item"},
},
},
"required": ["name", "price", "items"],
"title": "Offer",
"type": "object",
},
"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"},
}
},
},
}
},
}

View File

@ -0,0 +1,157 @@
import importlib
import pytest
from fastapi.testclient import TestClient
@pytest.fixture(
name="client",
params=[
pytest.param("tutorial008_py39"),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.body_nested_models.{request.param}")
client = TestClient(mod.app)
return client
def test_post_body(client: TestClient):
data = [
{"url": "http://example.com/", "name": "Example"},
{"url": "http://fastapi.tiangolo.com/", "name": "FastAPI"},
]
response = client.post("/images/multiple", json=data)
assert response.status_code == 200, response.text
assert response.json() == data
def test_post_invalid_list_item(client: TestClient):
data = [{"url": "not a valid url", "name": "Example"}]
response = client.post("/images/multiple", json=data)
assert response.status_code == 422, response.text
assert response.json() == {
"detail": [
{
"loc": ["body", 0, "url"],
"input": "not a valid url",
"msg": "Input should be a valid URL, relative URL without a base",
"type": "url_parsing",
"ctx": {"error": "relative URL without a base"},
},
]
}
def test_post_not_a_list(client: TestClient):
data = {"url": "http://example.com/", "name": "Example"}
response = client.post("/images/multiple", json=data)
assert response.status_code == 422, response.text
assert response.json() == {
"detail": [
{
"loc": ["body"],
"input": {
"name": "Example",
"url": "http://example.com/",
},
"msg": "Input should be a valid list",
"type": "list_type",
}
]
}
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": {
"/images/multiple/": {
"post": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Create Multiple Images",
"operationId": "create_multiple_images_images_multiple__post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"title": "Images",
"type": "array",
"items": {"$ref": "#/components/schemas/Image"},
}
}
},
"required": True,
},
}
}
},
"components": {
"schemas": {
"Image": {
"properties": {
"url": {
"title": "Url",
"type": "string",
"format": "uri",
"maxLength": 2083,
"minLength": 1,
},
"name": {
"title": "Name",
"type": "string",
},
},
"required": ["url", "name"],
"title": "Image",
"type": "object",
},
"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"},
}
},
},
}
},
}

Some files were not shown because too many files have changed in this diff Show More