From 998a92af97e760627580caae3db69059dcd30de9 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Wed, 26 Nov 2025 22:22:08 +0100 Subject: [PATCH] Add variants for `dataclasses/tutorial003` --- docs/en/docs/advanced/dataclasses.md | 2 +- docs_src/dataclasses/tutorial003_py310.py | 54 ++++++++++++++++++ docs_src/dataclasses/tutorial003_py39.py | 55 +++++++++++++++++++ .../test_dataclasses/test_tutorial003.py | 33 ++++++++--- 4 files changed, 134 insertions(+), 10 deletions(-) create mode 100644 docs_src/dataclasses/tutorial003_py310.py create mode 100644 docs_src/dataclasses/tutorial003_py39.py diff --git a/docs/en/docs/advanced/dataclasses.md b/docs/en/docs/advanced/dataclasses.md index 962701548..574beb65f 100644 --- a/docs/en/docs/advanced/dataclasses.md +++ b/docs/en/docs/advanced/dataclasses.md @@ -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.py hl[1,5,8:11,14:17,23:25,28] *} +{* ../../docs_src/dataclasses/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *} 1. We still import `field` from standard `dataclasses`. diff --git a/docs_src/dataclasses/tutorial003_py310.py b/docs_src/dataclasses/tutorial003_py310.py new file mode 100644 index 000000000..9b9a3fd63 --- /dev/null +++ b/docs_src/dataclasses/tutorial003_py310.py @@ -0,0 +1,54 @@ +from dataclasses import field # (1) + +from fastapi import FastAPI +from pydantic.dataclasses import dataclass # (2) + + +@dataclass +class Item: + name: str + description: str | None = None + + +@dataclass +class Author: + name: str + items: list[Item] = field(default_factory=list) # (3) + + +app = FastAPI() + + +@app.post("/authors/{author_id}/items/", response_model=Author) # (4) +async def create_author_items(author_id: str, items: list[Item]): # (5) + return {"name": author_id, "items": items} # (6) + + +@app.get("/authors/", response_model=list[Author]) # (7) +def get_authors(): # (8) + return [ # (9) + { + "name": "Breaters", + "items": [ + { + "name": "Island In The Moon", + "description": "A place to be playin' and havin' fun", + }, + {"name": "Holy Buddies"}, + ], + }, + { + "name": "System of an Up", + "items": [ + { + "name": "Salt", + "description": "The kombucha mushroom people's favorite", + }, + {"name": "Pad Thai"}, + { + "name": "Lonely Night", + "description": "The mostests lonliest nightiest of allest", + }, + ], + }, + ] diff --git a/docs_src/dataclasses/tutorial003_py39.py b/docs_src/dataclasses/tutorial003_py39.py new file mode 100644 index 000000000..991708c00 --- /dev/null +++ b/docs_src/dataclasses/tutorial003_py39.py @@ -0,0 +1,55 @@ +from dataclasses import field # (1) +from typing import Union + +from fastapi import FastAPI +from pydantic.dataclasses import dataclass # (2) + + +@dataclass +class Item: + name: str + description: Union[str, None] = None + + +@dataclass +class Author: + name: str + items: list[Item] = field(default_factory=list) # (3) + + +app = FastAPI() + + +@app.post("/authors/{author_id}/items/", response_model=Author) # (4) +async def create_author_items(author_id: str, items: list[Item]): # (5) + return {"name": author_id, "items": items} # (6) + + +@app.get("/authors/", response_model=list[Author]) # (7) +def get_authors(): # (8) + return [ # (9) + { + "name": "Breaters", + "items": [ + { + "name": "Island In The Moon", + "description": "A place to be playin' and havin' fun", + }, + {"name": "Holy Buddies"}, + ], + }, + { + "name": "System of an Up", + "items": [ + { + "name": "Salt", + "description": "The kombucha mushroom people's favorite", + }, + {"name": "Pad Thai"}, + { + "name": "Lonely Night", + "description": "The mostests lonliest nightiest of allest", + }, + ], + }, + ] diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial003.py b/tests/test_tutorial/test_dataclasses/test_tutorial003.py index e1fa45201..5728d2b6b 100644 --- a/tests/test_tutorial/test_dataclasses/test_tutorial003.py +++ b/tests/test_tutorial/test_dataclasses/test_tutorial003.py @@ -1,13 +1,28 @@ +import importlib + +import pytest from fastapi.testclient import TestClient -from docs_src.dataclasses.tutorial003 import app - -from ...utils import needs_pydanticv1, needs_pydanticv2 - -client = TestClient(app) +from ...utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2 -def test_post_authors_item(): +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial003"), + pytest.param("tutorial003_py39", marks=needs_py39), + pytest.param("tutorial003_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.dataclasses.{request.param}") + + client = TestClient(mod.app) + client.headers.clear() + return client + + +def test_post_authors_item(client: TestClient): response = client.post( "/authors/foo/items/", json=[{"name": "Bar"}, {"name": "Baz", "description": "Drop the Baz"}], @@ -22,7 +37,7 @@ def test_post_authors_item(): } -def test_get_authors(): +def test_get_authors(client: TestClient): response = client.get("/authors/") assert response.status_code == 200 assert response.json() == [ @@ -54,7 +69,7 @@ def test_get_authors(): @needs_pydanticv2 -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { @@ -191,7 +206,7 @@ def test_openapi_schema(): # TODO: remove when deprecating Pydantic v1 @needs_pydanticv1 -def test_openapi_schema_pv1(): +def test_openapi_schema_pv1(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == {