mirror of https://github.com/tiangolo/fastapi.git
✅ 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:
parent
5eb8d6ed8a
commit
3063ada72f
|
|
@ -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`.
|
||||
|
||||
|
|
|
|||
|
|
@ -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>.
|
||||
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
||||
|
|
|
|||
|
|
@ -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>.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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>.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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>.
|
||||
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
||||
|
|
|
|||
|
|
@ -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>.
|
||||
|
||||
|
|
|
|||
|
|
@ -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`;
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -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"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -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"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -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",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -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"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -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",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -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"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -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"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -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"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -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"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -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"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -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
Loading…
Reference in New Issue