diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml
index 73e1c6b67a..cd27179f57 100644
--- a/.github/workflows/build-docs.yml
+++ b/.github/workflows/build-docs.yml
@@ -60,8 +60,6 @@ jobs:
pyproject.toml
- name: Install docs extras
run: uv pip install -r requirements-docs.txt
- - name: Verify Docs
- run: python ./scripts/docs.py verify-docs
- name: Export Language Codes
id: show-langs
run: |
diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
index fa0574d7d1..b397912e67 100644
--- a/.github/workflows/pre-commit.yml
+++ b/.github/workflows/pre-commit.yml
@@ -7,7 +7,8 @@ on:
- synchronize
env:
- IS_FORK: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
+ # Forks and Dependabot don't have access to secrets
+ HAS_SECRETS: ${{ secrets.PRE_COMMIT != '' }}
jobs:
pre-commit:
@@ -19,16 +20,23 @@ jobs:
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v5
name: Checkout PR for own repo
- if: env.IS_FORK == 'false'
+ if: env.HAS_SECRETS == 'true'
with:
- # To be able to commit it needs more than the last commit
+ # To be able to commit it needs to fetch the head of the branch, not the
+ # merge commit
ref: ${{ github.head_ref }}
+ # And it needs the full history to be able to compute diffs
+ fetch-depth: 0
# A token other than the default GITHUB_TOKEN is needed to be able to trigger CI
token: ${{ secrets.PRE_COMMIT }}
# pre-commit lite ci needs the default checkout configs to work
- uses: actions/checkout@v5
name: Checkout PR for fork
- if: env.IS_FORK == 'true'
+ if: env.HAS_SECRETS == 'false'
+ with:
+ # To be able to commit it needs the head branch of the PR, the remote one
+ ref: ${{ github.event.pull_request.head.sha }}
+ fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v6
with:
@@ -44,15 +52,12 @@ jobs:
run: |
uv venv
uv pip install -r requirements.txt
- - name: Run pre-commit
+ - name: Run prek - pre-commit
id: precommit
- run: |
- # Fetch the base branch for comparison
- git fetch origin ${{ github.base_ref }}
- uvx pre-commit run --from-ref origin/${{ github.base_ref }} --to-ref HEAD --show-diff-on-failure
+ run: uvx prek run --from-ref origin/${GITHUB_BASE_REF} --to-ref HEAD --show-diff-on-failure
continue-on-error: true
- name: Commit and push changes
- if: env.IS_FORK == 'false'
+ if: env.HAS_SECRETS == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
@@ -64,7 +69,7 @@ jobs:
git push
fi
- uses: pre-commit-ci/lite-action@v1.1.0
- if: env.IS_FORK == 'true'
+ if: env.HAS_SECRETS == 'false'
with:
msg: 🎨 Auto format
- name: Error out on pre-commit errors
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 8a839a928a..3ad630d94b 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -16,59 +16,31 @@ env:
UV_SYSTEM_PYTHON: 1
jobs:
- lint:
- runs-on: ubuntu-latest
- steps:
- - name: Dump GitHub context
- env:
- GITHUB_CONTEXT: ${{ toJson(github) }}
- run: echo "$GITHUB_CONTEXT"
- - uses: actions/checkout@v6
- - name: Set up Python
- uses: actions/setup-python@v6
- with:
- python-version: "3.11"
- - name: Setup uv
- uses: astral-sh/setup-uv@v7
- with:
- cache-dependency-glob: |
- requirements**.txt
- pyproject.toml
- - name: Install Dependencies
- run: uv pip install -r requirements-tests.txt
- - name: Lint
- run: bash scripts/lint.sh
-
test:
strategy:
matrix:
os: [ windows-latest, macos-latest ]
python-version: [ "3.14" ]
- pydantic-version: [ "pydantic>=2.0.2,<3.0.0" ]
include:
- os: ubuntu-latest
python-version: "3.9"
- pydantic-version: "pydantic>=1.10.0,<2.0.0"
coverage: coverage
- os: macos-latest
python-version: "3.10"
- pydantic-version: "pydantic>=2.0.2,<3.0.0"
+ coverage: coverage
- os: windows-latest
- python-version: "3.11"
- pydantic-version: "pydantic>=1.10.0,<2.0.0"
- - os: ubuntu-latest
python-version: "3.12"
- pydantic-version: "pydantic>=2.0.2,<3.0.0"
- - os: macos-latest
- python-version: "3.13"
- pydantic-version: "pydantic>=1.10.0,<2.0.0"
- - os: windows-latest
- python-version: "3.13"
- pydantic-version: "pydantic>=2.0.2,<3.0.0"
coverage: coverage
+ - os: ubuntu-latest
+ python-version: "3.13"
+ coverage: coverage
+ # Ubuntu with 3.13 needs coverage for CodSpeed benchmarks
+ - os: ubuntu-latest
+ python-version: "3.13"
+ coverage: coverage
+ codspeed: codspeed
- os: ubuntu-latest
python-version: "3.14"
- pydantic-version: "pydantic>=2.0.2,<3.0.0"
coverage: coverage
fail-fast: false
runs-on: ${{ matrix.os }}
@@ -92,14 +64,22 @@ jobs:
pyproject.toml
- name: Install Dependencies
run: uv pip install -r requirements-tests.txt
- - name: Install Pydantic
- run: uv pip install "${{ matrix.pydantic-version }}"
- run: mkdir coverage
- name: Test
+ if: matrix.codspeed != 'codspeed'
run: bash scripts/test.sh
env:
COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}
CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}
+ - name: CodSpeed benchmarks
+ if: matrix.codspeed == 'codspeed'
+ uses: CodSpeedHQ/action@v4
+ env:
+ COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}
+ CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}
+ with:
+ mode: simulation
+ run: coverage run -m pytest tests/ --codspeed
# Do not store coverage for all possible combinations to avoid file size max errors in Smokeshow
- name: Store coverage files
if: matrix.coverage == 'coverage'
diff --git a/.github/workflows/translate.yml b/.github/workflows/translate.yml
index e681762ca3..f1267d21f5 100644
--- a/.github/workflows/translate.yml
+++ b/.github/workflows/translate.yml
@@ -1,8 +1,8 @@
name: Translate
on:
- schedule:
- - cron: "0 5 15 * *" # Run at 05:00 on the 15 of every month
+ # schedule:
+ # - cron: "0 5 15 * *" # Run at 05:00 on the 15 of every month
workflow_dispatch:
inputs:
diff --git a/.gitignore b/.gitignore
index 6016ffa598..3dc12ca951 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,3 +31,5 @@ archive.zip
# Ignore while the setup still depends on requirements.txt files
uv.lock
+
+.codspeed
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 8e6d93fb7d..10a0949e48 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -5,25 +5,55 @@ repos:
rev: v6.0.0
hooks:
- id: check-added-large-files
+ args: ['--maxkb=750']
- id: check-toml
- id: check-yaml
args:
- --unsafe
- id: end-of-file-fixer
- id: trailing-whitespace
- - repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.14.3
- hooks:
- - id: ruff
- args:
- - --fix
- - id: ruff-format
+
- repo: local
hooks:
- - id: local-script
+ - id: local-ruff-check
+ name: ruff check
+ entry: uv run ruff check --force-exclude --fix --exit-non-zero-on-fix
+ require_serial: true
language: unsupported
- name: local script
+ types: [python]
+
+ - id: local-ruff-format
+ name: ruff format
+ entry: uv run ruff format --force-exclude --exit-non-zero-on-format
+ require_serial: true
+ language: unsupported
+ types: [python]
+
+ - id: add-permalinks-pages
+ language: unsupported
+ name: add-permalinks-pages
entry: uv run ./scripts/docs.py add-permalinks-pages
args:
- --update-existing
files: ^docs/en/docs/.*\.md$
+
+ - id: generate-readme
+ language: unsupported
+ name: generate README.md from index.md
+ entry: uv run ./scripts/docs.py generate-readme
+ files: ^docs/en/docs/index\.md|docs/en/data/sponsors\.yml|scripts/docs\.py$
+ pass_filenames: false
+
+ - id: update-languages
+ language: unsupported
+ name: update languages
+ entry: uv run ./scripts/docs.py update-languages
+ files: ^docs/.*|scripts/docs\.py$
+ pass_filenames: false
+
+ - id: ensure-non-translated
+ language: unsupported
+ name: ensure non-translated files are not modified
+ entry: uv run ./scripts/docs.py ensure-non-translated
+ files: ^docs/(?!en/).*|^scripts/docs\.py$
+ pass_filenames: false
diff --git a/README.md b/README.md
index a42cedae69..1057b86942 100644
--- a/README.md
+++ b/README.md
@@ -120,6 +120,12 @@ The key features are:
---
+## FastAPI mini documentary
+
+There's a FastAPI mini documentary released at the end of 2025, you can watch it online:
+
+
+
## **Typer**, the FastAPI of CLIs
diff --git a/docs/de/docs/advanced/dataclasses.md b/docs/de/docs/advanced/dataclasses.md
index e2d59c776e..52b9634aea 100644
--- a/docs/de/docs/advanced/dataclasses.md
+++ b/docs/de/docs/advanced/dataclasses.md
@@ -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 `dataclasses`:
-{* ../../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 `dataclasses` intern unterstützt.
@@ -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`.
diff --git a/docs/de/docs/advanced/path-operation-advanced-configuration.md b/docs/de/docs/advanced/path-operation-advanced-configuration.md
index e722526002..c7ac1cf61b 100644
--- a/docs/de/docs/advanced/path-operation-advanced-configuration.md
+++ b/docs/de/docs/advanced/path-operation-advanced-configuration.md
@@ -48,7 +48,7 @@ Sie können die verwendeten Zeilen aus dem Docstring einer *Pfadoperation-Funkti
Das Hinzufügen eines `\f` (ein maskiertes „Form Feed“-Zeichen) führt dazu, dass **FastAPI** die für OpenAPI verwendete Ausgabe an dieser Stelle abschneidet.
-Sie wird nicht in der Dokumentation angezeigt, aber andere Tools (z. B. Sphinx) können den Rest verwenden.
+Sie wird nicht in der Dokumentation angezeigt, aber andere Tools (wie z. B. Sphinx) können den Rest verwenden.
{* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *}
@@ -153,48 +153,16 @@ Und Sie könnten dies auch tun, wenn der Datentyp im Request nicht JSON ist.
In der folgenden Anwendung verwenden wir beispielsweise weder die integrierte Funktionalität von FastAPI zum Extrahieren des JSON-Schemas aus Pydantic-Modellen noch die automatische Validierung für JSON. Tatsächlich deklarieren wir den Request-Content-Type als YAML und nicht als JSON:
-//// tab | Pydantic v2
-
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *}
-////
-
-//// tab | Pydantic v1
-
-{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[15:20, 22] *}
-
-////
-
-/// info | Info
-
-In Pydantic Version 1 hieß die Methode zum Abrufen des JSON-Schemas für ein Modell `Item.schema()`, in Pydantic Version 2 heißt die Methode `Item.model_json_schema()`.
-
-///
-
Obwohl wir nicht die standardmäßig integrierte Funktionalität verwenden, verwenden wir dennoch ein Pydantic-Modell, um das JSON-Schema für die Daten, die wir in YAML empfangen möchten, manuell zu generieren.
-Dann verwenden wir den Request direkt und extrahieren den Body als `bytes`. Das bedeutet, dass FastAPI nicht einmal versucht, den Request-Payload als JSON zu parsen.
+Dann verwenden wir den Request direkt und extrahieren den Body als `bytes`. Das bedeutet, dass FastAPI nicht einmal versucht, die Request-Payload als JSON zu parsen.
Und dann parsen wir in unserem Code diesen YAML-Inhalt direkt und verwenden dann wieder dasselbe Pydantic-Modell, um den YAML-Inhalt zu validieren:
-//// tab | Pydantic v2
-
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *}
-////
-
-//// tab | Pydantic v1
-
-{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[24:31] *}
-
-////
-
-/// info | Info
-
-In Pydantic Version 1 war die Methode zum Parsen und Validieren eines Objekts `Item.parse_obj()`, in Pydantic Version 2 heißt die Methode `Item.model_validate()`.
-
-///
-
/// tip | Tipp
Hier verwenden wir dasselbe Pydantic-Modell wieder.
diff --git a/docs/de/docs/advanced/settings.md b/docs/de/docs/advanced/settings.md
index ebacf76f42..ea4540e10e 100644
--- a/docs/de/docs/advanced/settings.md
+++ b/docs/de/docs/advanced/settings.md
@@ -60,24 +60,8 @@ Auf die gleiche Weise wie bei Pydantic-Modellen deklarieren Sie Klassenattribute
Sie können dieselben Validierungs-Funktionen und -Tools verwenden, die Sie für Pydantic-Modelle verwenden, z. B. verschiedene Datentypen und zusätzliche Validierungen mit `Field()`.
-//// tab | Pydantic v2
-
{* ../../docs_src/settings/tutorial001_py39.py hl[2,5:8,11] *}
-////
-
-//// tab | Pydantic v1
-
-/// info | Info
-
-In Pydantic v1 würden Sie `BaseSettings` direkt von `pydantic` statt von `pydantic_settings` importieren.
-
-///
-
-{* ../../docs_src/settings/tutorial001_pv1_py39.py hl[2,5:8,11] *}
-
-////
-
/// tip | Tipp
Für ein schnelles Copy-and-paste verwenden Sie nicht dieses Beispiel, sondern das letzte unten.
@@ -215,8 +199,6 @@ APP_NAME="ChimichangApp"
Und dann aktualisieren Sie Ihre `config.py` mit:
-//// tab | Pydantic v2
-
{* ../../docs_src/settings/app03_an_py39/config.py hl[9] *}
/// tip | Tipp
@@ -225,26 +207,6 @@ Das Attribut `model_config` wird nur für die Pydantic-Konfiguration verwendet.
///
-////
-
-//// tab | Pydantic v1
-
-{* ../../docs_src/settings/app03_an_py39/config_pv1.py hl[9:10] *}
-
-/// tip | Tipp
-
-Die Klasse `Config` wird nur für die Pydantic-Konfiguration verwendet. Weitere Informationen finden Sie unter Pydantic Model Config.
-
-///
-
-////
-
-/// info | Info
-
-In Pydantic Version 1 erfolgte die Konfiguration in einer internen Klasse `Config`, in Pydantic Version 2 erfolgt sie in einem Attribut `model_config`. Dieses Attribut akzeptiert ein `dict`. Um automatische Codevervollständigung und Inline-Fehlerberichte zu erhalten, können Sie `SettingsConfigDict` importieren und verwenden, um dieses `dict` zu definieren.
-
-///
-
Hier definieren wir die Konfiguration `env_file` innerhalb Ihrer Pydantic-`Settings`-Klasse und setzen den Wert auf den Dateinamen mit der dotenv-Datei, die wir verwenden möchten.
### Die `Settings` nur einmal laden mittels `lru_cache` { #creating-the-settings-only-once-with-lru-cache }
diff --git a/docs/de/docs/how-to/graphql.md b/docs/de/docs/how-to/graphql.md
index 0583faf4a3..5c908cec4a 100644
--- a/docs/de/docs/how-to/graphql.md
+++ b/docs/de/docs/how-to/graphql.md
@@ -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 Strawberry-Dokumentation.
diff --git a/docs/de/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md b/docs/de/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md
index 7f60492ee9..a8eff3b2b0 100644
--- a/docs/de/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md
+++ b/docs/de/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md
@@ -2,21 +2,23 @@
Wenn Sie eine ältere FastAPI-App haben, nutzen Sie möglicherweise Pydantic Version 1.
-FastAPI unterstützt seit Version 0.100.0 sowohl Pydantic v1 als auch v2.
+FastAPI Version 0.100.0 unterstützte sowohl Pydantic v1 als auch v2. Es verwendete, was auch immer Sie installiert hatten.
-Wenn Sie Pydantic v2 installiert hatten, wurde dieses verwendet. Wenn stattdessen Pydantic v1 installiert war, wurde jenes verwendet.
+FastAPI Version 0.119.0 führte eine teilweise Unterstützung für Pydantic v1 innerhalb von Pydantic v2 (als `pydantic.v1`) ein, um die Migration zu v2 zu erleichtern.
-Pydantic v1 ist jetzt deprecatet und die Unterstützung dafür wird in den nächsten Versionen von FastAPI entfernt, Sie sollten also zu **Pydantic v2 migrieren**. Auf diese Weise erhalten Sie die neuesten Features, Verbesserungen und Fixes.
+FastAPI 0.126.0 entfernte die Unterstützung für Pydantic v1, während `pydantic.v1` noch eine Weile unterstützt wurde.
/// warning | Achtung
-Außerdem hat das Pydantic-Team die Unterstützung für Pydantic v1 in den neuesten Python-Versionen eingestellt, beginnend mit **Python 3.14**.
+Das Pydantic-Team hat die Unterstützung für Pydantic v1 in den neuesten Python-Versionen eingestellt, beginnend mit **Python 3.14**.
+
+Dies schließt `pydantic.v1` ein, das unter Python 3.14 und höher nicht mehr unterstützt wird.
Wenn Sie die neuesten Features von Python nutzen möchten, müssen Sie sicherstellen, dass Sie Pydantic v2 verwenden.
///
-Wenn Sie eine ältere FastAPI-App mit Pydantic v1 haben, zeige ich Ihnen hier, wie Sie sie zu Pydantic v2 migrieren, und die **neuen Features in FastAPI 0.119.0**, die Ihnen bei einer schrittweisen Migration helfen.
+Wenn Sie eine ältere FastAPI-App mit Pydantic v1 haben, zeige ich Ihnen hier, wie Sie sie zu Pydantic v2 migrieren, und die **Features in FastAPI 0.119.0**, die Ihnen bei einer schrittweisen Migration helfen.
## Offizieller Leitfaden { #official-guide }
@@ -44,7 +46,7 @@ Danach können Sie die Tests ausführen und prüfen, ob alles funktioniert. Fall
## Pydantic v1 in v2 { #pydantic-v1-in-v2 }
-Pydantic v2 enthält alles aus Pydantic v1 als Untermodul `pydantic.v1`.
+Pydantic v2 enthält alles aus Pydantic v1 als Untermodul `pydantic.v1`. Dies wird aber in Versionen oberhalb von Python 3.13 nicht mehr unterstützt.
Das bedeutet, Sie können die neueste Version von Pydantic v2 installieren und die alten Pydantic‑v1‑Komponenten aus diesem Untermodul importieren und verwenden, als hätten Sie das alte Pydantic v1 installiert.
diff --git a/docs/de/docs/how-to/separate-openapi-schemas.md b/docs/de/docs/how-to/separate-openapi-schemas.md
index 31653590b5..16f9c8a144 100644
--- a/docs/de/docs/how-to/separate-openapi-schemas.md
+++ b/docs/de/docs/how-to/separate-openapi-schemas.md
@@ -1,6 +1,6 @@
# Separate OpenAPI-Schemas für Eingabe und Ausgabe oder nicht { #separate-openapi-schemas-for-input-and-output-or-not }
-Bei Verwendung von **Pydantic v2** ist die generierte OpenAPI etwas genauer und **korrekter** als zuvor. 😎
+Seit der Veröffentlichung von **Pydantic v2** ist die generierte OpenAPI etwas genauer und **korrekter** als zuvor. 😎
Tatsächlich gibt es in einigen Fällen sogar **zwei JSON-Schemas** in OpenAPI für dasselbe Pydantic-Modell, für Eingabe und Ausgabe, je nachdem, ob sie **Defaultwerte** haben.
@@ -100,5 +100,3 @@ Und jetzt wird es ein einziges Schema für die Eingabe und Ausgabe des Modells g
+
## **Typer**, das FastAPI der CLIs { #typer-the-fastapi-of-clis }
fastapi dev main.py macht ...fastapi dev main.py ...
+
## **Typer**, the FastAPI of CLIs { #typer-the-fastapi-of-clis }
text or `text` or "text", ignore that further markup when deciding if the text is an abbreviation), and if the description (the text inside the title attribute) contains the full phrase for this abbreviation, then append a dash (-) to the full phrase, followed by the translation of the full phrase.
+
+Conversion scheme:
+
+Source (English):
+
+```
+{abbreviation}
+```
+
+Result:
+
+```
+{abbreviation}
+```
+
+Examples:
+
+Source (English):
+
+```
+IoT
+CPU
+TL;DR:
+```
+
+Result (German):
+
+```
+IoT
+CPU
+TL;DR:
+```
+
+- If the language to which you translate mostly uses the letters of the ASCII char set (for example Spanish, French, German, but not Russian, Chinese) and if the translation of the full phrase is identical to, or starts with the same letters as the original full phrase, then only give the translation of the full phrase.
+
+Conversion scheme:
+
+Source (English):
+
+```
+{abbreviation}
+```
+
+Result:
+
+```
+{abbreviation}
+```
+
+Examples:
+
+Source (English):
+
+```
+JWT
+Enum
+ASGI
+```
+
+Result (German):
+
+```
+JWT
+Enum
+ASGI
+```
+
+- If the description is not a full phrase for an abbreviation which the abbr element surrounds, but some other information, then just translate the description.
+
+Conversion scheme:
+
+Source (English):
+
+```
+{text}
+```
+
+Result:
+
+```
+{translation of text}
+```
+
+Examples:
+
+ Source (English):
+
+```
+path
+linter
+parsing
+0.95.0
+at the time of writing this
+```
+
+Result (German):
+
+```
+Pfad
+Linter
+Parsen
+0.95.0
+zum Zeitpunkt als das hier geschrieben wurde
+```
+
+- If the text surrounded by the abbr element is an abbreviation and the description contains both the full phrase for that abbreviation, and other information, separated by a colon (`:`), then append a dash (`-`) and the translation of the full phrase to the original full phrase and translate the other information.
+
+Conversion scheme:
+
+Source (English):
+
+```
+{abbreviation}
+```
+
+Result:
+
+```
+{abbreviation}
+```
+
+Examples:
+
+Source (English):
+
+```
+I/O
+CDN
+IDE
+```
+
+Result (German):
+
+```
+I/O
+CDN
+IDE
+```
+
+- You can leave the original full phrase away, if the translated full phrase is identical or starts with the same letters as the original full phrase.
+
+Conversion scheme:
+
+Source (English):
+
+```
+{abbreviation}
+```
+
+Result:
+
+```
+{abbreviation}
+```
+
+Example:
+
+Source (English):
+
+```
+ORM
+```
+
+Result (German):
+
+```
+ORM
+```
+
+- If there is an existing translation, and it has ADDITIONAL abbr elements in a sentence, and these additional abbr elements do not exist in the related sentence in the English text, then KEEP those additional abbr elements in the translation. Do not remove them. Except when you remove the whole sentence from the translation, because the whole sentence was removed from the English text, then also remove the abbr element. The reasoning for this rule is, that such additional abbr elements are manually added by the human editor of the translation, in order to translate or explain an English word to the human readers of the translation. These additional abbr elements would not make sense in the English text, but they do make sense in the translation. So keep them in the translation, even though they are not part of the English text. This rule only applies to abbr elements.
+
+- Apply above rules also when there is an existing translation! Make sure that all title attributes in abbr elements get properly translated or updated, using the schemes given above. However, leave the ADDITIONAL abbr's described above alone. Do not change their formatting or content.
diff --git a/scripts/translate.py b/scripts/translate.py
index 764bc704ea..ffecfde07b 100644
--- a/scripts/translate.py
+++ b/scripts/translate.py
@@ -25,650 +25,8 @@ non_translated_sections = (
"contributing.md",
)
-
-general_prompt = """
-### About literal text in this prompt
-
-1) In the following instructions (after I say: `The above rules are in effect now`) the two characters `«` and `»` will be used to surround LITERAL TEXT, which is text or characters you shall interpret literally. The `«` and the `»` are not part of the literal text, they are the meta characters denoting it.
-
-2) Furthermore, text surrounded by `«««` and `»»»` is a BLOCK OF LITERAL TEXT which spans multiple lines. To get its content, dedent all lines of the block until the `«««` and `»»»` are at column zero, then remove the newline (`\n`) after the `«««` and the newline before the `»»»`. The `«««` and the `»»»` are not part of the literal text block, they are the meta characters denoting it.
-
-3) If you see backticks or any other quotes inside literal text – inside `«` and `»` – or inside blocks of literal text – inside `«««` and `»»»` – then interpret them as literal characters, do NOT interpret them as meta characters.
-
-The above rules are in effect now.
-
-
-### Definitions of terms used in this prompt
-
-"backtick"
-
- The character «`»
- Unicode U+0060 (GRAVE ACCENT)
-
-"single backtick"
-
- A single backtick – «`»
-
-"triple backticks"
-
- Three backticks in a row – «```»
-
-"neutral double quote"
-
- The character «"»
- Unicode U+0022 (QUOTATION MARK)
-
-"neutral single quote"
-
- The character «'»
- Unicode U+0027 (APOSTROPHE)
-
-"English double typographic quotes"
-
- The characters «“» and «”»
- Unicode U+201C (LEFT DOUBLE QUOTATION MARK) and Unicode U+201D (RIGHT DOUBLE QUOTATION MARK)
-
-"English single typographic quotes"
-
- The characters «‘» and «’»
- Unicode U+2018 (LEFT SINGLE QUOTATION MARK) and Unicode U+2019 (RIGHT SINGLE QUOTATION MARK)
-
-"code snippet"
-
- Also called "inline code". Text in a Markdown document which is surrounded by single backticks. A paragraph in a Markdown document can have a more than one code snippet.
-
- Example:
-
- «««
- `i am a code snippet`
- »»»
-
- Example:
-
- «««
- `first code snippet` `second code snippet` `third code snippet`
- »»»
-
-"code block"
-
- Text in a Markdown document which is surrounded by triple backticks. Spreads multiple lines.
-
- Example:
-
- «««
- ```
- Hello
- World
- ```
- »»»
-
- Example:
-
- «««
- ```python
- print("hello World")
- ```
- »»»
-
-"HTML element"
-
- a HTML opening tag – e.g. «text» or «`text`» or «"text"», ignore that further markup when deciding if the text is an abbreviation), and if the description (the text inside the title attribute) contains the full phrase for this abbreviation, then append a dash («–») to the full phrase, followed by the translation of the full phrase.
-
-Conversion scheme:
-
- Source (English):
-
- {abbreviation}
-
- Result:
-
- {abbreviation}
-
-Examples:
-
- Source (English):
-
- «««
- IoT
- CPU
- TL;DR:
- »»»
-
- Result (German):
-
- «««
- IoT
- CPU
- TL;DR:
- »»»
-
-1.1) If the language to which you translate mostly uses the letters of the ASCII char set (for example Spanish, French, German, but not Russian, Chinese) and if the translation of the full phrase is identical to, or starts with the same letters as the original full phrase, then only give the translation of the full phrase.
-
-Conversion scheme:
-
- Source (English):
-
- {abbreviation}
-
- Result:
-
- {abbreviation}
-
-Examples:
-
- Source (English):
-
- «««
- JWT
- Enum
- ASGI
- »»»
-
- Result (German):
-
- «««
- JWT
- Enum
- ASGI
- »»»
-
-2) If the description is not a full phrase for an abbreviation which the abbr element surrounds, but some other information, then just translate the description.
-
-Conversion scheme:
-
- Source (English):
-
- {text}
-
- Result:
-
- {translation of text}
-
-Examples:
-
- Source (English):
-
- «««
- path
- linter
- parsing
- 0.95.0
- at the time of writing this
- »»»
-
- Result (German):
-
- «««
- Pfad
- Linter
- Parsen
- 0.95.0
- zum Zeitpunkt als das hier geschrieben wurde
- »»»
-
-
-3) If the text surrounded by the abbr element is an abbreviation and the description contains both the full phrase for that abbreviation, and other information, separated by a colon («:»), then append a dash («–») and the translation of the full phrase to the original full phrase and translate the other information.
-
-Conversion scheme:
-
- Source (English):
-
- {abbreviation}
-
- Result:
-
- {abbreviation}
-
-Examples:
-
- Source (English):
-
- «««
- I/O
- CDN
- IDE
- »»»
-
- Result (German):
-
- «««
- I/O
- CDN
- IDE
- »»»
-
-3.1) Like in rule 2.1, you can leave the original full phrase away, if the translated full phrase is identical or starts with the same letters as the original full phrase.
-
-Conversion scheme:
-
- Source (English):
-
- {abbreviation}
-
- Result:
-
- {abbreviation}
-
-Example:
-
- Source (English):
-
- «««
- ORM
- »»»
-
- Result (German):
-
- «««
- ORM
- »»»
-
-4) If there is an existing translation, and it has ADDITIONAL abbr elements in a sentence, and these additional abbr elements do not exist in the related sentence in the English text, then KEEP those additional abbr elements in the translation. Do not remove them. Except when you remove the whole sentence from the translation, because the whole sentence was removed from the English text, then also remove the abbr element. The reasoning for this rule is, that such additional abbr elements are manually added by the human editor of the translation, in order to translate or explain an English word to the human readers of the translation. These additional abbr elements would not make sense in the English text, but they do make sense in the translation. So keep them in the translation, even though they are not part of the English text. This rule only applies to abbr elements.
-
-5) Apply above rules also when there is an existing translation! Make sure that all title attributes in abbr elements get properly translated or updated, using the schemes given above. However, leave the ADDITIONAL abbr's from rule 4 alone. Do not change their formatting or content.
-
-"""
+general_prompt_path = Path(__file__).absolute().parent / "llm-general-prompt.md"
+general_prompt = general_prompt_path.read_text(encoding="utf-8")
app = typer.Typer()
@@ -727,7 +85,7 @@ def translate_page(
print(f"Found existing translation: {out_path}")
old_translation = out_path.read_text(encoding="utf-8")
print(f"Translating {en_path} to {language} ({language_name})")
- agent = Agent("openai:gpt-5")
+ agent = Agent("openai:gpt-5.2")
prompt_segments = [
general_prompt,
@@ -1036,9 +394,13 @@ def make_pr(
print("Creating PR")
g = Github(github_token)
gh_repo = g.get_repo(github_repository)
- pr = gh_repo.create_pull(
- title=message, body=message, base="master", head=branch_name
+ body = (
+ message
+ + "\n\nThis PR was created automatically using LLMs."
+ + f"\n\nIt uses the prompt file https://github.com/fastapi/fastapi/blob/master/docs/{language}/llm-prompt.md."
+ + "\n\nIn most cases, it's better to make PRs updating that file so that the LLM can do a better job generating the translations than suggesting changes in this PR."
)
+ pr = gh_repo.create_pull(title=message, body=body, base="master", head=branch_name)
print(f"Created PR: {pr.number}")
print("Finished")
diff --git a/tests/benchmarks/__init__.py b/tests/benchmarks/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/benchmarks/test_general_performance.py b/tests/benchmarks/test_general_performance.py
new file mode 100644
index 0000000000..87add6d174
--- /dev/null
+++ b/tests/benchmarks/test_general_performance.py
@@ -0,0 +1,399 @@
+import json
+import sys
+from collections.abc import Iterator
+from typing import Annotated, Any
+
+import pytest
+from fastapi import Depends, FastAPI
+from fastapi.testclient import TestClient
+from pydantic import BaseModel
+
+if "--codspeed" not in sys.argv:
+ pytest.skip(
+ "Benchmark tests are skipped by default; run with --codspeed.",
+ allow_module_level=True,
+ )
+
+LARGE_ITEMS: list[dict[str, Any]] = [
+ {
+ "id": i,
+ "name": f"item-{i}",
+ "values": list(range(25)),
+ "meta": {
+ "active": True,
+ "group": i % 10,
+ "tag": f"t{i % 5}",
+ },
+ }
+ for i in range(300)
+]
+
+LARGE_METADATA: dict[str, Any] = {
+ "source": "benchmark",
+ "version": 1,
+ "flags": {"a": True, "b": False, "c": True},
+ "notes": ["x" * 50, "y" * 50, "z" * 50],
+}
+
+LARGE_PAYLOAD: dict[str, Any] = {"items": LARGE_ITEMS, "metadata": LARGE_METADATA}
+
+
+def dep_a():
+ return 40
+
+
+def dep_b(a: Annotated[int, Depends(dep_a)]):
+ return a + 2
+
+
+class ItemIn(BaseModel):
+ name: str
+ value: int
+
+
+class ItemOut(BaseModel):
+ name: str
+ value: int
+ dep: int
+
+
+class LargeIn(BaseModel):
+ items: list[dict[str, Any]]
+ metadata: dict[str, Any]
+
+
+class LargeOut(BaseModel):
+ items: list[dict[str, Any]]
+ metadata: dict[str, Any]
+
+
+app = FastAPI()
+
+
+@app.post("/sync/validated", response_model=ItemOut)
+def sync_validated(item: ItemIn, dep: Annotated[int, Depends(dep_b)]):
+ return ItemOut(name=item.name, value=item.value, dep=dep)
+
+
+@app.get("/sync/dict-no-response-model")
+def sync_dict_no_response_model():
+ return {"name": "foo", "value": 123}
+
+
+@app.get("/sync/dict-with-response-model", response_model=ItemOut)
+def sync_dict_with_response_model(
+ dep: Annotated[int, Depends(dep_b)],
+):
+ return {"name": "foo", "value": 123, "dep": dep}
+
+
+@app.get("/sync/model-no-response-model")
+def sync_model_no_response_model(dep: Annotated[int, Depends(dep_b)]):
+ return ItemOut(name="foo", value=123, dep=dep)
+
+
+@app.get("/sync/model-with-response-model", response_model=ItemOut)
+def sync_model_with_response_model(dep: Annotated[int, Depends(dep_b)]):
+ return ItemOut(name="foo", value=123, dep=dep)
+
+
+@app.post("/async/validated", response_model=ItemOut)
+async def async_validated(
+ item: ItemIn,
+ dep: Annotated[int, Depends(dep_b)],
+):
+ return ItemOut(name=item.name, value=item.value, dep=dep)
+
+
+@app.post("/sync/large-receive")
+def sync_large_receive(payload: LargeIn):
+ return {"received": len(payload.items)}
+
+
+@app.post("/async/large-receive")
+async def async_large_receive(payload: LargeIn):
+ return {"received": len(payload.items)}
+
+
+@app.get("/sync/large-dict-no-response-model")
+def sync_large_dict_no_response_model():
+ return LARGE_PAYLOAD
+
+
+@app.get("/sync/large-dict-with-response-model", response_model=LargeOut)
+def sync_large_dict_with_response_model():
+ return LARGE_PAYLOAD
+
+
+@app.get("/sync/large-model-no-response-model")
+def sync_large_model_no_response_model():
+ return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA)
+
+
+@app.get("/sync/large-model-with-response-model", response_model=LargeOut)
+def sync_large_model_with_response_model():
+ return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA)
+
+
+@app.get("/async/large-dict-no-response-model")
+async def async_large_dict_no_response_model():
+ return LARGE_PAYLOAD
+
+
+@app.get("/async/large-dict-with-response-model", response_model=LargeOut)
+async def async_large_dict_with_response_model():
+ return LARGE_PAYLOAD
+
+
+@app.get("/async/large-model-no-response-model")
+async def async_large_model_no_response_model():
+ return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA)
+
+
+@app.get("/async/large-model-with-response-model", response_model=LargeOut)
+async def async_large_model_with_response_model():
+ return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA)
+
+
+@app.get("/async/dict-no-response-model")
+async def async_dict_no_response_model():
+ return {"name": "foo", "value": 123}
+
+
+@app.get("/async/dict-with-response-model", response_model=ItemOut)
+async def async_dict_with_response_model(
+ dep: Annotated[int, Depends(dep_b)],
+):
+ return {"name": "foo", "value": 123, "dep": dep}
+
+
+@app.get("/async/model-no-response-model")
+async def async_model_no_response_model(
+ dep: Annotated[int, Depends(dep_b)],
+):
+ return ItemOut(name="foo", value=123, dep=dep)
+
+
+@app.get("/async/model-with-response-model", response_model=ItemOut)
+async def async_model_with_response_model(
+ dep: Annotated[int, Depends(dep_b)],
+):
+ return ItemOut(name="foo", value=123, dep=dep)
+
+
+@pytest.fixture(scope="module")
+def client() -> Iterator[TestClient]:
+ with TestClient(app) as client:
+ yield client
+
+
+def _bench_get(benchmark, client: TestClient, path: str) -> tuple[int, bytes]:
+ warmup = client.get(path)
+ assert warmup.status_code == 200
+
+ def do_request() -> tuple[int, bytes]:
+ response = client.get(path)
+ return response.status_code, response.content
+
+ return benchmark(do_request)
+
+
+def _bench_post_json(
+ benchmark, client: TestClient, path: str, json: dict[str, Any]
+) -> tuple[int, bytes]:
+ warmup = client.post(path, json=json)
+ assert warmup.status_code == 200
+
+ def do_request() -> tuple[int, bytes]:
+ response = client.post(path, json=json)
+ return response.status_code, response.content
+
+ return benchmark(do_request)
+
+
+def test_sync_receiving_validated_pydantic_model(benchmark, client: TestClient) -> None:
+ status_code, body = _bench_post_json(
+ benchmark,
+ client,
+ "/sync/validated",
+ json={"name": "foo", "value": 123},
+ )
+ assert status_code == 200
+ assert body == b'{"name":"foo","value":123,"dep":42}'
+
+
+def test_sync_return_dict_without_response_model(benchmark, client: TestClient) -> None:
+ status_code, body = _bench_get(benchmark, client, "/sync/dict-no-response-model")
+ assert status_code == 200
+ assert body == b'{"name":"foo","value":123}'
+
+
+def test_sync_return_dict_with_response_model(benchmark, client: TestClient) -> None:
+ status_code, body = _bench_get(benchmark, client, "/sync/dict-with-response-model")
+ assert status_code == 200
+ assert body == b'{"name":"foo","value":123,"dep":42}'
+
+
+def test_sync_return_model_without_response_model(
+ benchmark, client: TestClient
+) -> None:
+ status_code, body = _bench_get(benchmark, client, "/sync/model-no-response-model")
+ assert status_code == 200
+ assert body == b'{"name":"foo","value":123,"dep":42}'
+
+
+def test_sync_return_model_with_response_model(benchmark, client: TestClient) -> None:
+ status_code, body = _bench_get(benchmark, client, "/sync/model-with-response-model")
+ assert status_code == 200
+ assert body == b'{"name":"foo","value":123,"dep":42}'
+
+
+def test_async_receiving_validated_pydantic_model(
+ benchmark, client: TestClient
+) -> None:
+ status_code, body = _bench_post_json(
+ benchmark, client, "/async/validated", json={"name": "foo", "value": 123}
+ )
+ assert status_code == 200
+ assert body == b'{"name":"foo","value":123,"dep":42}'
+
+
+def test_async_return_dict_without_response_model(
+ benchmark, client: TestClient
+) -> None:
+ status_code, body = _bench_get(benchmark, client, "/async/dict-no-response-model")
+ assert status_code == 200
+ assert body == b'{"name":"foo","value":123}'
+
+
+def test_async_return_dict_with_response_model(benchmark, client: TestClient) -> None:
+ status_code, body = _bench_get(benchmark, client, "/async/dict-with-response-model")
+ assert status_code == 200
+ assert body == b'{"name":"foo","value":123,"dep":42}'
+
+
+def test_async_return_model_without_response_model(
+ benchmark, client: TestClient
+) -> None:
+ status_code, body = _bench_get(benchmark, client, "/async/model-no-response-model")
+ assert status_code == 200
+ assert body == b'{"name":"foo","value":123,"dep":42}'
+
+
+def test_async_return_model_with_response_model(benchmark, client: TestClient) -> None:
+ status_code, body = _bench_get(
+ benchmark, client, "/async/model-with-response-model"
+ )
+ assert status_code == 200
+ assert body == b'{"name":"foo","value":123,"dep":42}'
+
+
+def test_sync_receiving_large_payload(benchmark, client: TestClient) -> None:
+ status_code, body = _bench_post_json(
+ benchmark,
+ client,
+ "/sync/large-receive",
+ json=LARGE_PAYLOAD,
+ )
+ assert status_code == 200
+ assert body == b'{"received":300}'
+
+
+def test_async_receiving_large_payload(benchmark, client: TestClient) -> None:
+ status_code, body = _bench_post_json(
+ benchmark,
+ client,
+ "/async/large-receive",
+ json=LARGE_PAYLOAD,
+ )
+ assert status_code == 200
+ assert body == b'{"received":300}'
+
+
+def _expected_large_payload_json_bytes() -> bytes:
+ return json.dumps(
+ LARGE_PAYLOAD,
+ ensure_ascii=False,
+ allow_nan=False,
+ separators=(",", ":"),
+ ).encode("utf-8")
+
+
+def test_sync_return_large_dict_without_response_model(
+ benchmark, client: TestClient
+) -> None:
+ status_code, body = _bench_get(
+ benchmark, client, "/sync/large-dict-no-response-model"
+ )
+ assert status_code == 200
+ assert body == _expected_large_payload_json_bytes()
+
+
+def test_sync_return_large_dict_with_response_model(
+ benchmark, client: TestClient
+) -> None:
+ status_code, body = _bench_get(
+ benchmark, client, "/sync/large-dict-with-response-model"
+ )
+ assert status_code == 200
+ assert body == _expected_large_payload_json_bytes()
+
+
+def test_sync_return_large_model_without_response_model(
+ benchmark, client: TestClient
+) -> None:
+ status_code, body = _bench_get(
+ benchmark, client, "/sync/large-model-no-response-model"
+ )
+ assert status_code == 200
+ assert body == _expected_large_payload_json_bytes()
+
+
+def test_sync_return_large_model_with_response_model(
+ benchmark, client: TestClient
+) -> None:
+ status_code, body = _bench_get(
+ benchmark, client, "/sync/large-model-with-response-model"
+ )
+ assert status_code == 200
+ assert body == _expected_large_payload_json_bytes()
+
+
+def test_async_return_large_dict_without_response_model(
+ benchmark, client: TestClient
+) -> None:
+ status_code, body = _bench_get(
+ benchmark, client, "/async/large-dict-no-response-model"
+ )
+ assert status_code == 200
+ assert body == _expected_large_payload_json_bytes()
+
+
+def test_async_return_large_dict_with_response_model(
+ benchmark, client: TestClient
+) -> None:
+ status_code, body = _bench_get(
+ benchmark, client, "/async/large-dict-with-response-model"
+ )
+ assert status_code == 200
+ assert body == _expected_large_payload_json_bytes()
+
+
+def test_async_return_large_model_without_response_model(
+ benchmark, client: TestClient
+) -> None:
+ status_code, body = _bench_get(
+ benchmark, client, "/async/large-model-no-response-model"
+ )
+ assert status_code == 200
+ assert body == _expected_large_payload_json_bytes()
+
+
+def test_async_return_large_model_with_response_model(
+ benchmark, client: TestClient
+) -> None:
+ status_code, body = _bench_get(
+ benchmark, client, "/async/large-model-with-response-model"
+ )
+ assert status_code == 200
+ assert body == _expected_large_payload_json_bytes()
diff --git a/tests/test_additional_properties_bool.py b/tests/test_additional_properties_bool.py
index de59e48ce7..063297a3f2 100644
--- a/tests/test_additional_properties_bool.py
+++ b/tests/test_additional_properties_bool.py
@@ -1,19 +1,12 @@
from typing import Union
-from dirty_equals import IsDict
from fastapi import FastAPI
-from fastapi._compat import PYDANTIC_V2
from fastapi.testclient import TestClient
from pydantic import BaseModel, ConfigDict
class FooBaseModel(BaseModel):
- if PYDANTIC_V2:
- model_config = ConfigDict(extra="forbid")
- else:
-
- class Config:
- extra = "forbid"
+ model_config = ConfigDict(extra="forbid")
class Foo(FooBaseModel):
@@ -58,19 +51,13 @@ def test_openapi_schema():
"requestBody": {
"content": {
"application/json": {
- "schema": IsDict(
- {
- "anyOf": [
- {"$ref": "#/components/schemas/Foo"},
- {"type": "null"},
- ],
- "title": "Foo",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"$ref": "#/components/schemas/Foo"}
- )
+ "schema": {
+ "anyOf": [
+ {"$ref": "#/components/schemas/Foo"},
+ {"type": "null"},
+ ],
+ "title": "Foo",
+ }
}
}
},
diff --git a/tests/test_additional_responses_custom_model_in_callback.py b/tests/test_additional_responses_custom_model_in_callback.py
index 2ad5754551..376d7714ed 100644
--- a/tests/test_additional_responses_custom_model_in_callback.py
+++ b/tests/test_additional_responses_custom_model_in_callback.py
@@ -1,6 +1,6 @@
-from dirty_equals import IsDict
from fastapi import APIRouter, FastAPI
from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
from pydantic import BaseModel, HttpUrl
from starlette.responses import JSONResponse
@@ -32,121 +32,114 @@ client = TestClient(app)
def test_openapi_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": {
- "/": {
- "post": {
- "summary": "Main Route",
- "operationId": "main_route__post",
- "parameters": [
- {
- "required": True,
- "schema": IsDict(
- {
- "title": "Callback Url",
- "minLength": 1,
- "type": "string",
- "format": "uri",
- }
- )
- # TODO: remove when deprecating Pydantic v1
- | IsDict(
- {
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/": {
+ "post": {
+ "summary": "Main Route",
+ "operationId": "main_route__post",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {
"title": "Callback Url",
"maxLength": 2083,
"minLength": 1,
"type": "string",
"format": "uri",
- }
- ),
- "name": "callback_url",
- "in": "query",
- }
- ],
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
+ },
+ "name": "callback_url",
+ "in": "query",
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
},
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
+ "callbacks": {
+ "callback_route": {
+ "{$callback_url}/callback/": {
+ "get": {
+ "summary": "Callback Route",
+ "operationId": "callback_route__callback_url__callback__get",
+ "responses": {
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CustomModel"
+ }
+ }
+ },
+ "description": "Bad Request",
+ },
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {"schema": {}}
+ },
+ },
+ },
}
}
- },
+ }
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "CustomModel": {
+ "title": "CustomModel",
+ "required": ["a"],
+ "type": "object",
+ "properties": {"a": {"title": "A", "type": "integer"}},
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
+ }
},
},
- "callbacks": {
- "callback_route": {
- "{$callback_url}/callback/": {
- "get": {
- "summary": "Callback Route",
- "operationId": "callback_route__callback_url__callback__get",
- "responses": {
- "400": {
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/CustomModel"
- }
- }
- },
- "description": "Bad Request",
- },
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {"schema": {}}
- },
- },
- },
- }
- }
- }
+ "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"},
+ },
},
}
- }
- },
- "components": {
- "schemas": {
- "CustomModel": {
- "title": "CustomModel",
- "required": ["a"],
- "type": "object",
- "properties": {"a": {"title": "A", "type": "integer"}},
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- "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"},
- },
- },
- }
- },
- }
+ },
+ }
+ )
diff --git a/tests/test_ambiguous_params.py b/tests/test_ambiguous_params.py
index 5e48782f8d..44d49b7818 100644
--- a/tests/test_ambiguous_params.py
+++ b/tests/test_ambiguous_params.py
@@ -4,7 +4,6 @@ import pytest
from fastapi import Depends, FastAPI, Path
from fastapi.param_functions import Query
from fastapi.testclient import TestClient
-from fastapi.utils import PYDANTIC_V2
app = FastAPI()
@@ -71,6 +70,5 @@ def test_multiple_annotations():
response = client.get("/multi-query", params={"foo": "123"})
assert response.status_code == 422
- if PYDANTIC_V2:
- response = client.get("/multi-query", params={"foo": "1"})
- assert response.status_code == 422
+ response = client.get("/multi-query", params={"foo": "1"})
+ assert response.status_code == 422
diff --git a/tests/test_annotated.py b/tests/test_annotated.py
index a9e7c78c99..39f6f83b29 100644
--- a/tests/test_annotated.py
+++ b/tests/test_annotated.py
@@ -1,7 +1,6 @@
from typing import Annotated
import pytest
-from dirty_equals import IsDict
from fastapi import APIRouter, FastAPI, Query
from fastapi.testclient import TestClient
@@ -32,44 +31,23 @@ client = TestClient(app)
foo_is_missing = {
"detail": [
- IsDict(
- {
- "loc": ["query", "foo"],
- "msg": "Field required",
- "type": "missing",
- "input": None,
- }
- )
- # TODO: remove when deprecating Pydantic v1
- | IsDict(
- {
- "loc": ["query", "foo"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- )
+ {
+ "loc": ["query", "foo"],
+ "msg": "Field required",
+ "type": "missing",
+ "input": None,
+ }
]
}
foo_is_short = {
"detail": [
- IsDict(
- {
- "ctx": {"min_length": 1},
- "loc": ["query", "foo"],
- "msg": "String should have at least 1 character",
- "type": "string_too_short",
- "input": "",
- }
- )
- # TODO: remove when deprecating Pydantic v1
- | IsDict(
- {
- "ctx": {"limit_value": 1},
- "loc": ["query", "foo"],
- "msg": "ensure this value has at least 1 characters",
- "type": "value_error.any_str.min_length",
- }
- )
+ {
+ "ctx": {"min_length": 1},
+ "loc": ["query", "foo"],
+ "msg": "String should have at least 1 character",
+ "type": "string_too_short",
+ "input": "",
+ }
]
}
diff --git a/tests/test_application.py b/tests/test_application.py
index 8f1b0a18d3..001586ff78 100644
--- a/tests/test_application.py
+++ b/tests/test_application.py
@@ -1,5 +1,4 @@
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
from .main import app
@@ -274,14 +273,10 @@ def test_openapi_schema():
"name": "item_id",
"in": "path",
"required": True,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Item Id",
- }
- )
- # TODO: remove when deprecating Pydantic v1
- | IsDict({"title": "Item Id", "type": "string"}),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Item Id",
+ },
}
],
}
@@ -984,14 +979,10 @@ def test_openapi_schema():
"name": "query",
"in": "query",
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "integer"}, {"type": "null"}],
- "title": "Query",
- }
- )
- # TODO: remove when deprecating Pydantic v1
- | IsDict({"title": "Query", "type": "integer"}),
+ "schema": {
+ "anyOf": [{"type": "integer"}, {"type": "null"}],
+ "title": "Query",
+ },
}
],
}
diff --git a/tests/test_arbitrary_types.py b/tests/test_arbitrary_types.py
index 8b8417bae0..481acc3bfc 100644
--- a/tests/test_arbitrary_types.py
+++ b/tests/test_arbitrary_types.py
@@ -5,8 +5,6 @@ from fastapi import FastAPI
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
-from .utils import needs_pydanticv2
-
@pytest.fixture(name="client")
def get_client():
@@ -42,13 +40,11 @@ def get_client():
return client
-@needs_pydanticv2
def test_get(client: TestClient):
response = client.get("/")
assert response.json() == {"custom_field": [1.0, 2.0, 3.0]}
-@needs_pydanticv2
def test_typeadapter():
# This test is only to confirm that Pydantic alone is working as expected
from pydantic import (
@@ -93,7 +89,6 @@ def test_typeadapter():
)
-@needs_pydanticv2
def test_openapi_schema(client: TestClient):
response = client.get("openapi.json")
assert response.json() == snapshot(
diff --git a/tests/test_compat.py b/tests/test_compat.py
index 3f9d84d894..0b5600f8f5 100644
--- a/tests/test_compat.py
+++ b/tests/test_compat.py
@@ -1,23 +1,18 @@
-from typing import Any, Union
+from typing import Union
from fastapi import FastAPI, UploadFile
from fastapi._compat import (
Undefined,
- _get_model_config,
- get_cached_model_fields,
- is_scalar_field,
is_uploadfile_sequence_annotation,
- may_v1,
)
from fastapi._compat.shared import is_bytes_sequence_annotation
from fastapi.testclient import TestClient
from pydantic import BaseModel, ConfigDict
from pydantic.fields import FieldInfo
-from .utils import needs_py310, needs_py_lt_314, needs_pydanticv2
+from .utils import needs_py310
-@needs_pydanticv2
def test_model_field_default_required():
from fastapi._compat import v2
@@ -27,36 +22,6 @@ def test_model_field_default_required():
assert field.default is Undefined
-@needs_py_lt_314
-def test_v1_plain_validator_function():
- from fastapi._compat import v1
-
- # For coverage
- def func(v): # pragma: no cover
- return v
-
- result = v1.with_info_plain_validator_function(func)
- assert result == {}
-
-
-def test_is_model_field():
- # For coverage
- from fastapi._compat import _is_model_field
-
- assert not _is_model_field(str)
-
-
-@needs_pydanticv2
-def test_get_model_config():
- # For coverage in Pydantic v2
- class Foo(BaseModel):
- model_config = ConfigDict(from_attributes=True)
-
- foo = Foo()
- config = _get_model_config(foo)
- assert config == {"from_attributes": True}
-
-
def test_complex():
app = FastAPI()
@@ -75,7 +40,6 @@ def test_complex():
assert response2.json() == [1, 2]
-@needs_pydanticv2
def test_propagates_pydantic2_model_config():
app = FastAPI()
@@ -136,7 +100,6 @@ def test_is_uploadfile_sequence_annotation():
assert is_uploadfile_sequence_annotation(Union[list[str], list[UploadFile]])
-@needs_pydanticv2
def test_serialize_sequence_value_with_optional_list():
"""Test that serialize_sequence_value handles optional lists correctly."""
from fastapi._compat import v2
@@ -148,7 +111,6 @@ def test_serialize_sequence_value_with_optional_list():
assert isinstance(result, list)
-@needs_pydanticv2
@needs_py310
def test_serialize_sequence_value_with_optional_list_pipe_union():
"""Test that serialize_sequence_value handles optional lists correctly (with new syntax)."""
@@ -161,7 +123,6 @@ def test_serialize_sequence_value_with_optional_list_pipe_union():
assert isinstance(result, list)
-@needs_pydanticv2
def test_serialize_sequence_value_with_none_first_in_union():
"""Test that serialize_sequence_value handles Union[None, List[...]] correctly."""
from fastapi._compat import v2
@@ -171,33 +132,3 @@ def test_serialize_sequence_value_with_none_first_in_union():
result = v2.serialize_sequence_value(field=field, value=["x", "y"])
assert result == ["x", "y"]
assert isinstance(result, list)
-
-
-@needs_py_lt_314
-def test_is_pv1_scalar_field():
- from fastapi._compat import v1
-
- # For coverage
- class Model(v1.BaseModel):
- foo: Union[str, dict[str, Any]]
-
- fields = v1.get_model_fields(Model)
- assert not is_scalar_field(fields[0])
-
-
-@needs_py_lt_314
-def test_get_model_fields_cached():
- from fastapi._compat import v1
-
- class Model(may_v1.BaseModel):
- foo: str
-
- non_cached_fields = v1.get_model_fields(Model)
- non_cached_fields2 = v1.get_model_fields(Model)
- cached_fields = get_cached_model_fields(Model)
- cached_fields2 = get_cached_model_fields(Model)
- for f1, f2 in zip(cached_fields, cached_fields2):
- assert f1 is f2
-
- assert non_cached_fields is not non_cached_fields2
- assert cached_fields is cached_fields2
diff --git a/tests/test_compat_params_v1.py b/tests/test_compat_params_v1.py
deleted file mode 100644
index 8829a0a611..0000000000
--- a/tests/test_compat_params_v1.py
+++ /dev/null
@@ -1,1123 +0,0 @@
-import sys
-from typing import Optional
-
-import pytest
-
-from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314
-
-if sys.version_info >= (3, 14):
- skip_module_if_py_gte_314()
-
-from typing import Annotated
-
-from fastapi import FastAPI
-from fastapi._compat.v1 import BaseModel
-from fastapi.temp_pydantic_v1_params import (
- Body,
- Cookie,
- File,
- Form,
- Header,
- Path,
- Query,
-)
-from fastapi.testclient import TestClient
-from inline_snapshot import snapshot
-
-
-class Item(BaseModel):
- name: str
- price: float
- description: Optional[str] = None
-
-
-app = FastAPI()
-
-
-@app.get("/items/{item_id}")
-def get_item_with_path(
- item_id: Annotated[int, Path(title="The ID of the item", ge=1, le=1000)],
-):
- return {"item_id": item_id}
-
-
-@app.get("/items/")
-def get_items_with_query(
- q: Annotated[
- Optional[str], Query(min_length=3, max_length=50, pattern="^[a-zA-Z0-9 ]+$")
- ] = None,
- skip: Annotated[int, Query(ge=0)] = 0,
- limit: Annotated[int, Query(ge=1, le=100, examples=[5])] = 10,
-):
- return {"q": q, "skip": skip, "limit": limit}
-
-
-@app.get("/users/")
-def get_user_with_header(
- x_custom: Annotated[Optional[str], Header()] = None,
- x_token: Annotated[Optional[str], Header(convert_underscores=True)] = None,
-):
- return {"x_custom": x_custom, "x_token": x_token}
-
-
-@app.get("/cookies/")
-def get_cookies(
- session_id: Annotated[Optional[str], Cookie()] = None,
- tracking_id: Annotated[Optional[str], Cookie(min_length=10)] = None,
-):
- return {"session_id": session_id, "tracking_id": tracking_id}
-
-
-@app.post("/items/")
-def create_item(
- item: Annotated[
- Item,
- Body(examples=[{"name": "Foo", "price": 35.4, "description": "The Foo item"}]),
- ],
-):
- return {"item": item}
-
-
-@app.post("/items-embed/")
-def create_item_embed(
- item: Annotated[Item, Body(embed=True)],
-):
- return {"item": item}
-
-
-@app.put("/items/{item_id}")
-def update_item(
- item_id: Annotated[int, Path(ge=1)],
- item: Annotated[Item, Body()],
- importance: Annotated[int, Body(gt=0, le=10)],
-):
- return {"item": item, "importance": importance}
-
-
-@app.post("/form-data/")
-def submit_form(
- username: Annotated[str, Form(min_length=3, max_length=50)],
- password: Annotated[str, Form(min_length=8)],
- email: Annotated[Optional[str], Form()] = None,
-):
- return {"username": username, "password": password, "email": email}
-
-
-@app.post("/upload/")
-def upload_file(
- file: Annotated[bytes, File()],
- description: Annotated[Optional[str], Form()] = None,
-):
- return {"file_size": len(file), "description": description}
-
-
-@app.post("/upload-multiple/")
-def upload_multiple_files(
- files: Annotated[list[bytes], File()],
- note: Annotated[str, Form()] = "",
-):
- return {
- "file_count": len(files),
- "total_size": sum(len(f) for f in files),
- "note": note,
- }
-
-
-client = TestClient(app)
-
-
-# Path parameter tests
-def test_path_param_valid():
- response = client.get("/items/50")
- assert response.status_code == 200
- assert response.json() == {"item_id": 50}
-
-
-def test_path_param_too_large():
- response = client.get("/items/1001")
- assert response.status_code == 422
- error = response.json()["detail"][0]
- assert error["loc"] == ["path", "item_id"]
-
-
-def test_path_param_too_small():
- response = client.get("/items/0")
- assert response.status_code == 422
- error = response.json()["detail"][0]
- assert error["loc"] == ["path", "item_id"]
-
-
-# Query parameter tests
-def test_query_params_valid():
- response = client.get("/items/?q=test search&skip=5&limit=20")
- assert response.status_code == 200
- assert response.json() == {"q": "test search", "skip": 5, "limit": 20}
-
-
-def test_query_params_defaults():
- response = client.get("/items/")
- assert response.status_code == 200
- assert response.json() == {"q": None, "skip": 0, "limit": 10}
-
-
-def test_query_param_too_short():
- response = client.get("/items/?q=ab")
- assert response.status_code == 422
- error = response.json()["detail"][0]
- assert error["loc"] == ["query", "q"]
-
-
-def test_query_param_invalid_pattern():
- response = client.get("/items/?q=test@#$")
- assert response.status_code == 422
- error = response.json()["detail"][0]
- assert error["loc"] == ["query", "q"]
-
-
-def test_query_param_limit_too_large():
- response = client.get("/items/?limit=101")
- assert response.status_code == 422
- error = response.json()["detail"][0]
- assert error["loc"] == ["query", "limit"]
-
-
-# Header parameter tests
-def test_header_params():
- response = client.get(
- "/users/",
- headers={"X-Custom": "Plumbus", "X-Token": "secret-token"},
- )
- assert response.status_code == 200
- assert response.json() == {
- "x_custom": "Plumbus",
- "x_token": "secret-token",
- }
-
-
-def test_header_underscore_conversion():
- response = client.get(
- "/users/",
- headers={"x-token": "secret-token-with-dash"},
- )
- assert response.status_code == 200
- assert response.json()["x_token"] == "secret-token-with-dash"
-
-
-def test_header_params_none():
- response = client.get("/users/")
- assert response.status_code == 200
- assert response.json() == {"x_custom": None, "x_token": None}
-
-
-# Cookie parameter tests
-def test_cookie_params():
- with TestClient(app) as client:
- client.cookies.set("session_id", "abc123")
- client.cookies.set("tracking_id", "1234567890abcdef")
- response = client.get("/cookies/")
- assert response.status_code == 200
- assert response.json() == {
- "session_id": "abc123",
- "tracking_id": "1234567890abcdef",
- }
-
-
-def test_cookie_tracking_id_too_short():
- with TestClient(app) as client:
- client.cookies.set("tracking_id", "short")
- response = client.get("/cookies/")
- assert response.status_code == 422
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["cookie", "tracking_id"],
- "msg": "ensure this value has at least 10 characters",
- "type": "value_error.any_str.min_length",
- "ctx": {"limit_value": 10},
- }
- ]
- }
- )
-
-
-def test_cookie_params_none():
- response = client.get("/cookies/")
- assert response.status_code == 200
- assert response.json() == {"session_id": None, "tracking_id": None}
-
-
-# Body parameter tests
-def test_body_param():
- response = client.post(
- "/items/",
- json={"name": "Test Item", "price": 29.99, "description": "A test item"},
- )
- assert response.status_code == 200
- assert response.json() == {
- "item": {
- "name": "Test Item",
- "price": 29.99,
- "description": "A test item",
- }
- }
-
-
-def test_body_param_minimal():
- response = client.post(
- "/items/",
- json={"name": "Minimal", "price": 9.99},
- )
- assert response.status_code == 200
- assert response.json() == {
- "item": {"name": "Minimal", "price": 9.99, "description": None}
- }
-
-
-def test_body_param_missing_required():
- response = client.post(
- "/items/",
- json={"name": "Incomplete"},
- )
- assert response.status_code == 422
- error = response.json()["detail"][0]
- assert error["loc"] == ["body", "price"]
-
-
-def test_body_embed():
- response = client.post(
- "/items-embed/",
- json={"item": {"name": "Embedded", "price": 15.0}},
- )
- assert response.status_code == 200
- assert response.json() == {
- "item": {"name": "Embedded", "price": 15.0, "description": None}
- }
-
-
-def test_body_embed_wrong_structure():
- response = client.post(
- "/items-embed/",
- json={"name": "Not Embedded", "price": 15.0},
- )
- assert response.status_code == 422
-
-
-# Multiple body parameters test
-def test_multiple_body_params():
- response = client.put(
- "/items/5",
- json={
- "item": {"name": "Updated Item", "price": 49.99},
- "importance": 8,
- },
- )
- assert response.status_code == 200
- assert response.json() == snapshot(
- {
- "item": {"name": "Updated Item", "price": 49.99, "description": None},
- "importance": 8,
- }
- )
-
-
-def test_multiple_body_params_importance_too_large():
- response = client.put(
- "/items/5",
- json={
- "item": {"name": "Item", "price": 10.0},
- "importance": 11,
- },
- )
- assert response.status_code == 422
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", "importance"],
- "msg": "ensure this value is less than or equal to 10",
- "type": "value_error.number.not_le",
- "ctx": {"limit_value": 10},
- }
- ]
- }
- )
-
-
-def test_multiple_body_params_importance_too_small():
- response = client.put(
- "/items/5",
- json={
- "item": {"name": "Item", "price": 10.0},
- "importance": 0,
- },
- )
- assert response.status_code == 422
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", "importance"],
- "msg": "ensure this value is greater than 0",
- "type": "value_error.number.not_gt",
- "ctx": {"limit_value": 0},
- }
- ]
- }
- )
-
-
-# Form parameter tests
-def test_form_data_valid():
- response = client.post(
- "/form-data/",
- data={
- "username": "testuser",
- "password": "password123",
- "email": "test@example.com",
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "username": "testuser",
- "password": "password123",
- "email": "test@example.com",
- }
-
-
-def test_form_data_optional_field():
- response = client.post(
- "/form-data/",
- data={"username": "testuser", "password": "password123"},
- )
- assert response.status_code == 200
- assert response.json() == {
- "username": "testuser",
- "password": "password123",
- "email": None,
- }
-
-
-def test_form_data_username_too_short():
- response = client.post(
- "/form-data/",
- data={"username": "ab", "password": "password123"},
- )
- assert response.status_code == 422
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", "username"],
- "msg": "ensure this value has at least 3 characters",
- "type": "value_error.any_str.min_length",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
-
-
-def test_form_data_password_too_short():
- response = client.post(
- "/form-data/",
- data={"username": "testuser", "password": "short"},
- )
- assert response.status_code == 422
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", "password"],
- "msg": "ensure this value has at least 8 characters",
- "type": "value_error.any_str.min_length",
- "ctx": {"limit_value": 8},
- }
- ]
- }
- )
-
-
-# File upload tests
-def test_upload_file():
- response = client.post(
- "/upload/",
- files={"file": ("test.txt", b"Hello, World!", "text/plain")},
- data={"description": "A test file"},
- )
- assert response.status_code == 200
- assert response.json() == {
- "file_size": 13,
- "description": "A test file",
- }
-
-
-def test_upload_file_without_description():
- response = client.post(
- "/upload/",
- files={"file": ("test.txt", b"Hello!", "text/plain")},
- )
- assert response.status_code == 200
- assert response.json() == {
- "file_size": 6,
- "description": None,
- }
-
-
-def test_upload_multiple_files():
- response = client.post(
- "/upload-multiple/",
- files=[
- ("files", ("file1.txt", b"Content 1", "text/plain")),
- ("files", ("file2.txt", b"Content 2", "text/plain")),
- ("files", ("file3.txt", b"Content 3", "text/plain")),
- ],
- data={"note": "Multiple files uploaded"},
- )
- assert response.status_code == 200
- assert response.json() == {
- "file_count": 3,
- "total_size": 27,
- "note": "Multiple files uploaded",
- }
-
-
-def test_upload_multiple_files_empty_note():
- response = client.post(
- "/upload-multiple/",
- files=[
- ("files", ("file1.txt", b"Test", "text/plain")),
- ],
- )
- assert response.status_code == 200
- assert response.json()["file_count"] == 1
- assert response.json()["note"] == ""
-
-
-# __repr__ tests
-def test_query_repr():
- query_param = Query(default=None, min_length=3)
- assert repr(query_param) == "Query(None)"
-
-
-def test_body_repr():
- body_param = Body(default=None)
- assert repr(body_param) == "Body(None)"
-
-
-# Deprecation warning tests for regex parameter
-def test_query_regex_deprecation_warning():
- with pytest.warns(DeprecationWarning, match="`regex` has been deprecated"):
- Query(regex="^test$")
-
-
-def test_body_regex_deprecation_warning():
- with pytest.warns(DeprecationWarning, match="`regex` has been deprecated"):
- Body(regex="^test$")
-
-
-# Deprecation warning tests for example parameter
-def test_query_example_deprecation_warning():
- with pytest.warns(DeprecationWarning, match="`example` has been deprecated"):
- Query(example="test example")
-
-
-def test_body_example_deprecation_warning():
- with pytest.warns(DeprecationWarning, match="`example` has been deprecated"):
- Body(example={"test": "example"})
-
-
-def test_openapi_schema():
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == snapshot(
- {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/{item_id}": {
- "get": {
- "summary": "Get Item With Path",
- "operationId": "get_item_with_path_items__item_id__get",
- "parameters": [
- {
- "name": "item_id",
- "in": "path",
- "required": True,
- "schema": {
- "title": "The ID of the item",
- "minimum": 1,
- "maximum": 1000,
- "type": "integer",
- },
- }
- ],
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- },
- "put": {
- "summary": "Update Item",
- "operationId": "update_item_items__item_id__put",
- "parameters": [
- {
- "name": "item_id",
- "in": "path",
- "required": True,
- "schema": {
- "title": "Item Id",
- "minimum": 1,
- "type": "integer",
- },
- }
- ],
- "requestBody": {
- "required": True,
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v1=snapshot(
- {
- "$ref": "#/components/schemas/Body_update_item_items__item_id__put"
- }
- ),
- v2=snapshot(
- {
- "title": "Body",
- "allOf": [
- {
- "$ref": "#/components/schemas/Body_update_item_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"
- }
- }
- },
- },
- },
- },
- },
- "/items/": {
- "get": {
- "summary": "Get Items With Query",
- "operationId": "get_items_with_query_items__get",
- "parameters": [
- {
- "name": "q",
- "in": "query",
- "required": False,
- "schema": {
- "title": "Q",
- "maxLength": 50,
- "minLength": 3,
- "pattern": "^[a-zA-Z0-9 ]+$",
- "type": "string",
- },
- },
- {
- "name": "skip",
- "in": "query",
- "required": False,
- "schema": {
- "title": "Skip",
- "default": 0,
- "minimum": 0,
- "type": "integer",
- },
- },
- {
- "name": "limit",
- "in": "query",
- "required": False,
- "schema": {
- "title": "Limit",
- "default": 10,
- "minimum": 1,
- "maximum": 100,
- "examples": [5],
- "type": "integer",
- },
- },
- ],
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- },
- "post": {
- "summary": "Create Item",
- "operationId": "create_item_items__post",
- "requestBody": {
- "required": True,
- "content": {
- "application/json": {
- "schema": {
- "title": "Item",
- "examples": [
- {
- "name": "Foo",
- "price": 35.4,
- "description": "The Foo item",
- }
- ],
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- }
- }
- },
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- },
- },
- "/users/": {
- "get": {
- "summary": "Get User With Header",
- "operationId": "get_user_with_header_users__get",
- "parameters": [
- {
- "name": "x-custom",
- "in": "header",
- "required": False,
- "schema": {"title": "X-Custom", "type": "string"},
- },
- {
- "name": "x-token",
- "in": "header",
- "required": False,
- "schema": {"title": "X-Token", "type": "string"},
- },
- ],
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/cookies/": {
- "get": {
- "summary": "Get Cookies",
- "operationId": "get_cookies_cookies__get",
- "parameters": [
- {
- "name": "session_id",
- "in": "cookie",
- "required": False,
- "schema": {"title": "Session Id", "type": "string"},
- },
- {
- "name": "tracking_id",
- "in": "cookie",
- "required": False,
- "schema": {
- "title": "Tracking Id",
- "minLength": 10,
- "type": "string",
- },
- },
- ],
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/items-embed/": {
- "post": {
- "summary": "Create Item Embed",
- "operationId": "create_item_embed_items_embed__post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v1=snapshot(
- {
- "$ref": "#/components/schemas/Body_create_item_embed_items_embed__post"
- }
- ),
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Body_create_item_embed_items_embed__post"
- }
- ],
- "title": "Body",
- }
- ),
- ),
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/form-data/": {
- "post": {
- "summary": "Submit Form",
- "operationId": "submit_form_form_data__post",
- "requestBody": {
- "content": {
- "application/x-www-form-urlencoded": {
- "schema": pydantic_snapshot(
- v1=snapshot(
- {
- "$ref": "#/components/schemas/Body_submit_form_form_data__post"
- }
- ),
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Body_submit_form_form_data__post"
- }
- ],
- "title": "Body",
- }
- ),
- ),
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/upload/": {
- "post": {
- "summary": "Upload File",
- "operationId": "upload_file_upload__post",
- "requestBody": {
- "content": {
- "multipart/form-data": {
- "schema": pydantic_snapshot(
- v1=snapshot(
- {
- "$ref": "#/components/schemas/Body_upload_file_upload__post"
- }
- ),
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Body_upload_file_upload__post"
- }
- ],
- "title": "Body",
- }
- ),
- ),
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/upload-multiple/": {
- "post": {
- "summary": "Upload Multiple Files",
- "operationId": "upload_multiple_files_upload_multiple__post",
- "requestBody": {
- "content": {
- "multipart/form-data": {
- "schema": pydantic_snapshot(
- v1=snapshot(
- {
- "$ref": "#/components/schemas/Body_upload_multiple_files_upload_multiple__post"
- }
- ),
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Body_upload_multiple_files_upload_multiple__post"
- }
- ],
- "title": "Body",
- }
- ),
- ),
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- },
- "components": {
- "schemas": {
- "Body_create_item_embed_items_embed__post": {
- "properties": pydantic_snapshot(
- v1=snapshot(
- {"item": {"$ref": "#/components/schemas/Item"}}
- ),
- v2=snapshot(
- {
- "item": {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Item",
- }
- }
- ),
- ),
- "type": "object",
- "required": ["item"],
- "title": "Body_create_item_embed_items_embed__post",
- },
- "Body_submit_form_form_data__post": {
- "properties": {
- "username": {
- "type": "string",
- "maxLength": 50,
- "minLength": 3,
- "title": "Username",
- },
- "password": {
- "type": "string",
- "minLength": 8,
- "title": "Password",
- },
- "email": {"type": "string", "title": "Email"},
- },
- "type": "object",
- "required": ["username", "password"],
- "title": "Body_submit_form_form_data__post",
- },
- "Body_update_item_items__item_id__put": {
- "properties": {
- "item": pydantic_snapshot(
- v1=snapshot({"$ref": "#/components/schemas/Item"}),
- v2=snapshot(
- {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Item",
- }
- ),
- ),
- "importance": {
- "type": "integer",
- "maximum": 10.0,
- "exclusiveMinimum": 0.0,
- "title": "Importance",
- },
- },
- "type": "object",
- "required": ["item", "importance"],
- "title": "Body_update_item_items__item_id__put",
- },
- "Body_upload_file_upload__post": {
- "properties": {
- "file": {
- "type": "string",
- "format": "binary",
- "title": "File",
- },
- "description": {"type": "string", "title": "Description"},
- },
- "type": "object",
- "required": ["file"],
- "title": "Body_upload_file_upload__post",
- },
- "Body_upload_multiple_files_upload_multiple__post": {
- "properties": {
- "files": {
- "items": {"type": "string", "format": "binary"},
- "type": "array",
- "title": "Files",
- },
- "note": {"type": "string", "title": "Note", "default": ""},
- },
- "type": "object",
- "required": ["files"],
- "title": "Body_upload_multiple_files_upload_multiple__post",
- },
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {
- "$ref": "#/components/schemas/ValidationError"
- },
- "type": "array",
- "title": "Detail",
- }
- },
- "type": "object",
- "title": "HTTPValidationError",
- },
- "Item": {
- "properties": {
- "name": {"type": "string", "title": "Name"},
- "price": {"type": "number", "title": "Price"},
- "description": {"type": "string", "title": "Description"},
- },
- "type": "object",
- "required": ["name", "price"],
- "title": "Item",
- },
- "ValidationError": {
- "properties": {
- "loc": {
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- "type": "array",
- "title": "Location",
- },
- "msg": {"type": "string", "title": "Message"},
- "type": {"type": "string", "title": "Error Type"},
- },
- "type": "object",
- "required": ["loc", "msg", "type"],
- "title": "ValidationError",
- },
- }
- },
- }
- )
diff --git a/tests/test_computed_fields.py b/tests/test_computed_fields.py
index f2e42999b3..e7f969f7cf 100644
--- a/tests/test_computed_fields.py
+++ b/tests/test_computed_fields.py
@@ -2,8 +2,6 @@ import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
-from .utils import needs_pydanticv2
-
@pytest.fixture(name="client")
def get_client(request):
@@ -35,7 +33,6 @@ def get_client(request):
@pytest.mark.parametrize("client", [True, False], indirect=True)
@pytest.mark.parametrize("path", ["/", "/responses"])
-@needs_pydanticv2
def test_get(client: TestClient, path: str):
response = client.get(path)
assert response.status_code == 200, response.text
@@ -43,7 +40,6 @@ def test_get(client: TestClient, path: str):
@pytest.mark.parametrize("client", [True, False], indirect=True)
-@needs_pydanticv2
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
diff --git a/tests/test_custom_schema_fields.py b/tests/test_custom_schema_fields.py
index bc3e1c5ec3..60b795e9ba 100644
--- a/tests/test_custom_schema_fields.py
+++ b/tests/test_custom_schema_fields.py
@@ -1,12 +1,8 @@
from typing import Annotated, Optional
from fastapi import FastAPI
-from fastapi._compat import PYDANTIC_V2
from fastapi.testclient import TestClient
-from pydantic import BaseModel
-
-if PYDANTIC_V2:
- from pydantic import WithJsonSchema
+from pydantic import BaseModel, WithJsonSchema
app = FastAPI()
@@ -14,23 +10,15 @@ app = FastAPI()
class Item(BaseModel):
name: str
- if PYDANTIC_V2:
- description: Annotated[
- Optional[str], WithJsonSchema({"type": ["string", "null"]})
- ] = None
+ description: Annotated[
+ Optional[str], WithJsonSchema({"type": ["string", "null"]})
+ ] = None
- model_config = {
- "json_schema_extra": {
- "x-something-internal": {"level": 4},
- }
+ model_config = {
+ "json_schema_extra": {
+ "x-something-internal": {"level": 4},
}
- else:
- description: Optional[str] = None # type: ignore[no-redef]
-
- class Config:
- schema_extra = {
- "x-something-internal": {"level": 4},
- }
+ }
@app.get("/foo", response_model=Item)
@@ -55,7 +43,7 @@ item_schema = {
},
"description": {
"title": "Description",
- "type": ["string", "null"] if PYDANTIC_V2 else "string",
+ "type": ["string", "null"],
},
},
}
diff --git a/tests/test_datastructures.py b/tests/test_datastructures.py
index c175147bc3..29a70cae0c 100644
--- a/tests/test_datastructures.py
+++ b/tests/test_datastructures.py
@@ -7,12 +7,6 @@ from fastapi.datastructures import Default
from fastapi.testclient import TestClient
-# TODO: remove when deprecating Pydantic v1
-def test_upload_file_invalid():
- with pytest.raises(ValueError):
- UploadFile.validate("not a Starlette UploadFile")
-
-
def test_upload_file_invalid_pydantic_v2():
with pytest.raises(ValueError):
UploadFile._validate("not a Starlette UploadFile", {})
diff --git a/tests/test_datetime_custom_encoder.py b/tests/test_datetime_custom_encoder.py
index 3aa77c0b1d..f154ede029 100644
--- a/tests/test_datetime_custom_encoder.py
+++ b/tests/test_datetime_custom_encoder.py
@@ -4,10 +4,7 @@ from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
-from .utils import needs_pydanticv1, needs_pydanticv2
-
-@needs_pydanticv2
def test_pydanticv2():
from pydantic import field_serializer
@@ -29,29 +26,3 @@ def test_pydanticv2():
with client:
response = client.get("/model")
assert response.json() == {"dt_field": "2019-01-01T08:00:00+00:00"}
-
-
-# TODO: remove when deprecating Pydantic v1
-@needs_pydanticv1
-def test_pydanticv1():
- class ModelWithDatetimeField(BaseModel):
- dt_field: datetime
-
- class Config:
- json_encoders = {
- datetime: lambda dt: dt.replace(
- microsecond=0, tzinfo=timezone.utc
- ).isoformat()
- }
-
- app = FastAPI()
- model = ModelWithDatetimeField(dt_field=datetime(2019, 1, 1, 8))
-
- @app.get("/model", response_model=ModelWithDatetimeField)
- def get_model():
- return model
-
- client = TestClient(app)
- with client:
- response = client.get("/model")
- assert response.json() == {"dt_field": "2019-01-01T08:00:00+00:00"}
diff --git a/tests/test_dependency_duplicates.py b/tests/test_dependency_duplicates.py
index 7c6717e2aa..a8658e03bb 100644
--- a/tests/test_dependency_duplicates.py
+++ b/tests/test_dependency_duplicates.py
@@ -1,4 +1,3 @@
-from dirty_equals import IsDict
from fastapi import Depends, FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
@@ -46,29 +45,16 @@ async def no_duplicates_sub(
def test_no_duplicates_invalid():
response = client.post("/no-duplicates", json={"item": {"data": "myitem"}})
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "item2"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "item2"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "item2"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_no_duplicates():
diff --git a/tests/test_dependency_overrides.py b/tests/test_dependency_overrides.py
index 154937fa0b..e25db624d8 100644
--- a/tests/test_dependency_overrides.py
+++ b/tests/test_dependency_overrides.py
@@ -1,7 +1,6 @@
from typing import Optional
import pytest
-from dirty_equals import IsDict
from fastapi import APIRouter, Depends, FastAPI
from fastapi.testclient import TestClient
@@ -54,29 +53,16 @@ async def overrider_dependency_with_sub(msg: dict = Depends(overrider_sub_depend
def test_main_depends():
response = client.get("/main-depends/")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "q"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "q"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "q"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_main_depends_q_foo():
@@ -100,29 +86,16 @@ def test_main_depends_q_foo_skip_100_limit_200():
def test_decorator_depends():
response = client.get("/decorator-depends/")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "q"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "q"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "q"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_decorator_depends_q_foo():
@@ -140,29 +113,16 @@ def test_decorator_depends_q_foo_skip_100_limit_200():
def test_router_depends():
response = client.get("/router-depends/")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "q"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "q"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "q"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_router_depends_q_foo():
@@ -186,29 +146,16 @@ def test_router_depends_q_foo_skip_100_limit_200():
def test_router_decorator_depends():
response = client.get("/router-decorator-depends/")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "q"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "q"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "q"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_router_decorator_depends_q_foo():
@@ -272,29 +219,17 @@ def test_override_with_sub_main_depends():
app.dependency_overrides[common_parameters] = overrider_dependency_with_sub
response = client.get("/main-depends/")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "k"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "k"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "k"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
app.dependency_overrides = {}
@@ -302,29 +237,17 @@ def test_override_with_sub__main_depends_q_foo():
app.dependency_overrides[common_parameters] = overrider_dependency_with_sub
response = client.get("/main-depends/?q=foo")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "k"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "k"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "k"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
app.dependency_overrides = {}
@@ -340,29 +263,17 @@ def test_override_with_sub_decorator_depends():
app.dependency_overrides[common_parameters] = overrider_dependency_with_sub
response = client.get("/decorator-depends/")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "k"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "k"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "k"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
app.dependency_overrides = {}
@@ -370,29 +281,17 @@ def test_override_with_sub_decorator_depends_q_foo():
app.dependency_overrides[common_parameters] = overrider_dependency_with_sub
response = client.get("/decorator-depends/?q=foo")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "k"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "k"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "k"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
app.dependency_overrides = {}
@@ -408,29 +307,17 @@ def test_override_with_sub_router_depends():
app.dependency_overrides[common_parameters] = overrider_dependency_with_sub
response = client.get("/router-depends/")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "k"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "k"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "k"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
app.dependency_overrides = {}
@@ -438,29 +325,17 @@ def test_override_with_sub_router_depends_q_foo():
app.dependency_overrides[common_parameters] = overrider_dependency_with_sub
response = client.get("/router-depends/?q=foo")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "k"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "k"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "k"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
app.dependency_overrides = {}
@@ -476,29 +351,17 @@ def test_override_with_sub_router_decorator_depends():
app.dependency_overrides[common_parameters] = overrider_dependency_with_sub
response = client.get("/router-decorator-depends/")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "k"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "k"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "k"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
app.dependency_overrides = {}
@@ -506,29 +369,17 @@ def test_override_with_sub_router_decorator_depends_q_foo():
app.dependency_overrides[common_parameters] = overrider_dependency_with_sub
response = client.get("/router-decorator-depends/?q=foo")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "k"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "k"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "k"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
app.dependency_overrides = {}
diff --git a/tests/test_extra_routes.py b/tests/test_extra_routes.py
index bd16fe9254..45734ec28a 100644
--- a/tests/test_extra_routes.py
+++ b/tests/test_extra_routes.py
@@ -1,6 +1,5 @@
from typing import Optional
-from dirty_equals import IsDict
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from fastapi.testclient import TestClient
@@ -328,14 +327,10 @@ def test_openapi_schema():
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
- "price": IsDict(
- {
- "title": "Price",
- "anyOf": [{"type": "number"}, {"type": "null"}],
- }
- )
- # TODO: remove when deprecating Pydantic v1
- | IsDict({"title": "Price", "type": "number"}),
+ "price": {
+ "title": "Price",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ },
},
},
"ValidationError": {
diff --git a/tests/test_filter_pydantic_sub_model/app_pv1.py b/tests/test_filter_pydantic_sub_model/app_pv1.py
deleted file mode 100644
index 657e8c5d12..0000000000
--- a/tests/test_filter_pydantic_sub_model/app_pv1.py
+++ /dev/null
@@ -1,35 +0,0 @@
-from typing import Optional
-
-from fastapi import Depends, FastAPI
-from pydantic import BaseModel, validator
-
-app = FastAPI()
-
-
-class ModelB(BaseModel):
- username: str
-
-
-class ModelC(ModelB):
- password: str
-
-
-class ModelA(BaseModel):
- name: str
- description: Optional[str] = None
- model_b: ModelB
-
- @validator("name")
- def lower_username(cls, name: str, values):
- if not name.endswith("A"):
- raise ValueError("name must end in A")
- return name
-
-
-async def get_model_c() -> ModelC:
- return ModelC(username="test-user", password="test-password")
-
-
-@app.get("/model/{name}", response_model=ModelA)
-async def get_model_a(name: str, model_c=Depends(get_model_c)):
- return {"name": name, "description": "model-a-desc", "model_b": model_c}
diff --git a/tests/test_filter_pydantic_sub_model_pv2.py b/tests/test_filter_pydantic_sub_model_pv2.py
index 2e2c26ddcb..fc5876410d 100644
--- a/tests/test_filter_pydantic_sub_model_pv2.py
+++ b/tests/test_filter_pydantic_sub_model_pv2.py
@@ -1,12 +1,11 @@
from typing import Optional
import pytest
-from dirty_equals import HasRepr, IsDict, IsOneOf
+from dirty_equals import HasRepr
from fastapi import Depends, FastAPI
from fastapi.exceptions import ResponseValidationError
from fastapi.testclient import TestClient
-
-from .utils import needs_pydanticv2
+from inline_snapshot import snapshot
@pytest.fixture(name="client")
@@ -25,6 +24,7 @@ def get_client():
name: str
description: Optional[str] = None
foo: ModelB
+ tags: dict[str, str] = {}
@field_validator("name")
def lower_username(cls, name: str, info: ValidationInfo):
@@ -37,13 +37,17 @@ def get_client():
@app.get("/model/{name}", response_model=ModelA)
async def get_model_a(name: str, model_c=Depends(get_model_c)):
- return {"name": name, "description": "model-a-desc", "foo": model_c}
+ return {
+ "name": name,
+ "description": "model-a-desc",
+ "foo": model_c,
+ "tags": {"key1": "value1", "key2": "value2"},
+ }
client = TestClient(app)
return client
-@needs_pydanticv2
def test_filter_sub_model(client: TestClient):
response = client.get("/model/modelA")
assert response.status_code == 200, response.text
@@ -51,134 +55,128 @@ def test_filter_sub_model(client: TestClient):
"name": "modelA",
"description": "model-a-desc",
"foo": {"username": "test-user"},
+ "tags": {"key1": "value1", "key2": "value2"},
}
-@needs_pydanticv2
def test_validator_is_cloned(client: TestClient):
with pytest.raises(ResponseValidationError) as err:
client.get("/model/modelX")
assert err.value.errors() == [
- IsDict(
- {
- "type": "value_error",
- "loc": ("response", "name"),
- "msg": "Value error, name must end in A",
- "input": "modelX",
- "ctx": {"error": HasRepr("ValueError('name must end in A')")},
- }
- )
- | IsDict(
- # TODO remove when deprecating Pydantic v1
- {
- "loc": ("response", "name"),
- "msg": "name must end in A",
- "type": "value_error",
- }
- )
+ {
+ "type": "value_error",
+ "loc": ("response", "name"),
+ "msg": "Value error, name must end in A",
+ "input": "modelX",
+ "ctx": {"error": HasRepr("ValueError('name must end in A')")},
+ }
]
-@needs_pydanticv2
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": {
- "/model/{name}": {
- "get": {
- "summary": "Get Model A",
- "operationId": "get_model_a_model__name__get",
- "parameters": [
- {
- "required": True,
- "schema": {"title": "Name", "type": "string"},
- "name": "name",
- "in": "path",
- }
- ],
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/ModelA"}
- }
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/model/{name}": {
+ "get": {
+ "summary": "Get Model A",
+ "operationId": "get_model_a_model__name__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Name", "type": "string"},
+ "name": "name",
+ "in": "path",
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ModelA"
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
},
},
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
+ }
+ },
+ },
+ "ModelA": {
+ "title": "ModelA",
+ "required": ["name", "foo"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "description": {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
},
+ "foo": {"$ref": "#/components/schemas/ModelB"},
+ "tags": {
+ "additionalProperties": {"type": "string"},
+ "type": "object",
+ "title": "Tags",
+ "default": {},
+ },
+ },
+ },
+ "ModelB": {
+ "title": "ModelB",
+ "required": ["username"],
+ "type": "object",
+ "properties": {
+ "username": {"title": "Username", "type": "string"}
+ },
+ },
+ "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"},
},
},
}
- }
- },
- "components": {
- "schemas": {
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- "ModelA": {
- "title": "ModelA",
- "required": IsOneOf(
- ["name", "description", "foo"],
- # TODO remove when deprecating Pydantic v1
- ["name", "foo"],
- ),
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "description": IsDict(
- {
- "title": "Description",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- |
- # TODO remove when deprecating Pydantic v1
- IsDict({"title": "Description", "type": "string"}),
- "foo": {"$ref": "#/components/schemas/ModelB"},
- },
- },
- "ModelB": {
- "title": "ModelB",
- "required": ["username"],
- "type": "object",
- "properties": {"username": {"title": "Username", "type": "string"}},
- },
- "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"},
- },
- },
- }
- },
- }
+ },
+ }
+ )
diff --git a/tests/test_forms_single_model.py b/tests/test_forms_single_model.py
index b149b76539..7d03d29572 100644
--- a/tests/test_forms_single_model.py
+++ b/tests/test_forms_single_model.py
@@ -1,8 +1,6 @@
from typing import Annotated, Optional
-from dirty_equals import IsDict
from fastapi import FastAPI, Form
-from fastapi._compat import PYDANTIC_V2
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
@@ -20,12 +18,7 @@ class FormModel(BaseModel):
class FormModelExtraAllow(BaseModel):
param: str
- if PYDANTIC_V2:
- model_config = {"extra": "allow"}
- else:
-
- class Config:
- extra = "allow"
+ model_config = {"extra": "allow"}
@app.post("/form/")
@@ -85,68 +78,37 @@ def test_invalid_data():
},
)
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["body", "age"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "seventy",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "age"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["body", "age"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "seventy",
+ }
+ ]
+ }
def test_no_data():
response = client.post("/form/")
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "username"],
- "msg": "Field required",
- "input": {"tags": ["foo", "bar"], "with": "nothing"},
- },
- {
- "type": "missing",
- "loc": ["body", "lastname"],
- "msg": "Field required",
- "input": {"tags": ["foo", "bar"], "with": "nothing"},
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "username"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "lastname"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": {"tags": ["foo", "bar"], "with": "nothing"},
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "lastname"],
+ "msg": "Field required",
+ "input": {"tags": ["foo", "bar"], "with": "nothing"},
+ },
+ ]
+ }
def test_extra_param_single():
diff --git a/tests/test_get_model_definitions_formfeed_escape.py b/tests/test_get_model_definitions_formfeed_escape.py
index 215d06a072..eb7939b69a 100644
--- a/tests/test_get_model_definitions_formfeed_escape.py
+++ b/tests/test_get_model_definitions_formfeed_escape.py
@@ -1,181 +1,158 @@
-from collections.abc import Iterator
-from typing import Any
-
-import fastapi._compat
-import fastapi.openapi.utils
-import pydantic.schema
import pytest
from fastapi import FastAPI
-from pydantic import BaseModel
-from starlette.testclient import TestClient
-
-from .utils import needs_pydanticv1
+from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
-class Address(BaseModel):
- """
- This is a public description of an Address
- \f
- You can't see this part of the docstring, it's private!
- """
+@pytest.fixture(name="client")
+def client_fixture() -> TestClient:
+ from pydantic import BaseModel
- line_1: str
- city: str
- state_province: str
+ class Address(BaseModel):
+ """
+ This is a public description of an Address
+ \f
+ You can't see this part of the docstring, it's private!
+ """
+
+ line_1: str
+ city: str
+ state_province: str
+
+ class Facility(BaseModel):
+ id: str
+ address: Address
+
+ app = FastAPI()
+
+ @app.get("/facilities/{facility_id}")
+ def get_facility(facility_id: str) -> Facility:
+ return Facility(
+ id=facility_id,
+ address=Address(line_1="123 Main St", city="Anytown", state_province="CA"),
+ )
+
+ client = TestClient(app)
+ return client
-class Facility(BaseModel):
- id: str
- address: Address
+def test_get(client: TestClient):
+ response = client.get("/facilities/42")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "id": "42",
+ "address": {
+ "line_1": "123 Main St",
+ "city": "Anytown",
+ "state_province": "CA",
+ },
+ }
-app = FastAPI()
-
-client = TestClient(app)
-
-
-@app.get("/facilities/{facility_id}")
-def get_facility(facility_id: str) -> Facility: ...
-
-
-openapi_schema = {
- "components": {
- "schemas": {
- "Address": {
- # NOTE: the description of this model shows only the public-facing text, before the `\f` in docstring
- "description": "This is a public description of an Address\n",
- "properties": {
- "city": {"title": "City", "type": "string"},
- "line_1": {"title": "Line 1", "type": "string"},
- "state_province": {"title": "State Province", "type": "string"},
- },
- "required": ["line_1", "city", "state_province"],
- "title": "Address",
- "type": "object",
- },
- "Facility": {
- "properties": {
- "address": {"$ref": "#/components/schemas/Address"},
- "id": {"title": "Id", "type": "string"},
- },
- "required": ["id", "address"],
- "title": "Facility",
- "type": "object",
- },
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {"$ref": "#/components/schemas/ValidationError"},
- "title": "Detail",
- "type": "array",
- }
- },
- "title": "HTTPValidationError",
- "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",
- },
- }
- },
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "openapi": "3.1.0",
- "paths": {
- "/facilities/{facility_id}": {
- "get": {
- "operationId": "get_facility_facilities__facility_id__get",
- "parameters": [
- {
- "in": "path",
- "name": "facility_id",
- "required": True,
- "schema": {"title": "Facility Id", "type": "string"},
- }
- ],
- "responses": {
- "200": {
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Facility"}
- }
- },
- "description": "Successful Response",
- },
- "422": {
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- "description": "Validation Error",
- },
- },
- "summary": "Get Facility",
- }
- }
- },
-}
-
-
-def test_openapi_schema():
+def test_openapi_schema(client: TestClient):
"""
Sanity check to ensure our app's openapi schema renders as we expect
"""
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
- assert response.json() == openapi_schema
-
-
-class SortedTypeSet(set):
- """
- Set of Types whose `__iter__()` method yields results sorted by the type names
- """
-
- def __init__(self, seq: set[type[Any]], *, sort_reversed: bool):
- super().__init__(seq)
- self.sort_reversed = sort_reversed
-
- def __iter__(self) -> Iterator[type[Any]]:
- members_sorted = sorted(
- super().__iter__(),
- key=lambda type_: type_.__name__,
- reverse=self.sort_reversed,
- )
- yield from members_sorted
-
-
-@needs_pydanticv1
-@pytest.mark.parametrize("sort_reversed", [True, False])
-def test_model_description_escaped_with_formfeed(sort_reversed: bool):
- """
- Regression test for bug fixed by https://github.com/fastapi/fastapi/pull/6039.
-
- Test `get_model_definitions` with models passed in different order.
- """
- from fastapi._compat import v1
-
- all_fields = fastapi.openapi.utils.get_fields_from_routes(app.routes)
-
- flat_models = v1.get_flat_models_from_fields(all_fields, known_models=set())
- model_name_map = pydantic.schema.get_model_name_map(flat_models)
-
- expected_address_description = "This is a public description of an Address\n"
-
- models = v1.get_model_definitions(
- flat_models=SortedTypeSet(flat_models, sort_reversed=sort_reversed),
- model_name_map=model_name_map,
+ assert response.json() == snapshot(
+ {
+ "components": {
+ "schemas": {
+ "Address": {
+ # NOTE: the description of this model shows only the public-facing text, before the `\f` in docstring
+ "description": "This is a public description of an Address\n",
+ "properties": {
+ "city": {"title": "City", "type": "string"},
+ "line_1": {"title": "Line 1", "type": "string"},
+ "state_province": {
+ "title": "State Province",
+ "type": "string",
+ },
+ },
+ "required": ["line_1", "city", "state_province"],
+ "title": "Address",
+ "type": "object",
+ },
+ "Facility": {
+ "properties": {
+ "address": {"$ref": "#/components/schemas/Address"},
+ "id": {"title": "Id", "type": "string"},
+ },
+ "required": ["id", "address"],
+ "title": "Facility",
+ "type": "object",
+ },
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
+ "title": "Detail",
+ "type": "array",
+ }
+ },
+ "title": "HTTPValidationError",
+ "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",
+ },
+ }
+ },
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "openapi": "3.1.0",
+ "paths": {
+ "/facilities/{facility_id}": {
+ "get": {
+ "operationId": "get_facility_facilities__facility_id__get",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "facility_id",
+ "required": True,
+ "schema": {"title": "Facility Id", "type": "string"},
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Facility"
+ }
+ }
+ },
+ "description": "Successful Response",
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ "description": "Validation Error",
+ },
+ },
+ "summary": "Get Facility",
+ }
+ }
+ },
+ }
)
- assert models["Address"]["description"] == expected_address_description
diff --git a/tests/test_infer_param_optionality.py b/tests/test_infer_param_optionality.py
index e3d57bb428..147018996e 100644
--- a/tests/test_infer_param_optionality.py
+++ b/tests/test_infer_param_optionality.py
@@ -1,6 +1,5 @@
from typing import Optional
-from dirty_equals import IsDict
from fastapi import APIRouter, FastAPI
from fastapi.testclient import TestClient
@@ -163,16 +162,10 @@ def test_openapi_schema():
"required": False,
"name": "user_id",
"in": "query",
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "User Id",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "User Id", "type": "string"}
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "User Id",
+ },
}
],
"responses": {
@@ -208,16 +201,10 @@ def test_openapi_schema():
"required": False,
"name": "user_id",
"in": "query",
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "User Id",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "User Id", "type": "string"}
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "User Id",
+ },
},
],
"responses": {
@@ -247,16 +234,10 @@ def test_openapi_schema():
"required": True,
"name": "user_id",
"in": "path",
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "User Id",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "User Id", "type": "string"}
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "User Id",
+ },
}
],
"responses": {
@@ -292,16 +273,10 @@ def test_openapi_schema():
"required": True,
"name": "user_id",
"in": "path",
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "User Id",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "User Id", "type": "string"}
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "User Id",
+ },
},
],
"responses": {
diff --git a/tests/test_inherited_custom_class.py b/tests/test_inherited_custom_class.py
index fe9350f4ef..8cf8952f92 100644
--- a/tests/test_inherited_custom_class.py
+++ b/tests/test_inherited_custom_class.py
@@ -5,8 +5,6 @@ from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
-from .utils import needs_pydanticv1, needs_pydanticv2
-
class MyUuid:
def __init__(self, uuid_string: str):
@@ -26,7 +24,6 @@ class MyUuid:
raise TypeError("vars() argument must have __dict__ attribute")
-@needs_pydanticv2
def test_pydanticv2():
from pydantic import field_serializer
@@ -68,44 +65,3 @@ def test_pydanticv2():
assert response_pydantic.json() == {
"a_uuid": "b8799909-f914-42de-91bc-95c819218d01"
}
-
-
-# TODO: remove when deprecating Pydantic v1
-@needs_pydanticv1
-def test_pydanticv1():
- app = FastAPI()
-
- @app.get("/fast_uuid")
- def return_fast_uuid():
- asyncpg_uuid = MyUuid("a10ff360-3b1e-4984-a26f-d3ab460bdb51")
- assert isinstance(asyncpg_uuid, uuid.UUID)
- assert type(asyncpg_uuid) is not uuid.UUID
- with pytest.raises(TypeError):
- vars(asyncpg_uuid)
- return {"fast_uuid": asyncpg_uuid}
-
- class SomeCustomClass(BaseModel):
- class Config:
- arbitrary_types_allowed = True
- json_encoders = {uuid.UUID: str}
-
- a_uuid: MyUuid
-
- @app.get("/get_custom_class")
- def return_some_user():
- # Test that the fix also works for custom pydantic classes
- return SomeCustomClass(a_uuid=MyUuid("b8799909-f914-42de-91bc-95c819218d01"))
-
- client = TestClient(app)
-
- with client:
- response_simple = client.get("/fast_uuid")
- response_pydantic = client.get("/get_custom_class")
-
- assert response_simple.json() == {
- "fast_uuid": "a10ff360-3b1e-4984-a26f-d3ab460bdb51"
- }
-
- assert response_pydantic.json() == {
- "a_uuid": "b8799909-f914-42de-91bc-95c819218d01"
- }
diff --git a/tests/test_jsonable_encoder.py b/tests/test_jsonable_encoder.py
index 3b6513e27b..4528dff440 100644
--- a/tests/test_jsonable_encoder.py
+++ b/tests/test_jsonable_encoder.py
@@ -1,3 +1,4 @@
+import warnings
from collections import deque
from dataclasses import dataclass
from datetime import datetime, timezone
@@ -5,15 +6,14 @@ from decimal import Decimal
from enum import Enum
from math import isinf, isnan
from pathlib import PurePath, PurePosixPath, PureWindowsPath
-from typing import Optional
+from typing import Optional, TypedDict
import pytest
-from fastapi._compat import PYDANTIC_V2, Undefined
+from fastapi._compat import Undefined
from fastapi.encoders import jsonable_encoder
+from fastapi.exceptions import PydanticV1NotSupportedError
from pydantic import BaseModel, Field, ValidationError
-from .utils import needs_pydanticv1, needs_pydanticv2
-
class Person:
def __init__(self, name: str):
@@ -59,12 +59,7 @@ class RoleEnum(Enum):
class ModelWithConfig(BaseModel):
role: Optional[RoleEnum] = None
- if PYDANTIC_V2:
- model_config = {"use_enum_values": True}
- else:
-
- class Config:
- use_enum_values = True
+ model_config = {"use_enum_values": True}
class ModelWithAlias(BaseModel):
@@ -89,6 +84,18 @@ def test_encode_dict():
}
+def test_encode_dict_include_exclude_list():
+ pet = {"name": "Firulais", "owner": {"name": "Foo"}}
+ assert jsonable_encoder(pet) == {"name": "Firulais", "owner": {"name": "Foo"}}
+ assert jsonable_encoder(pet, include=["name"]) == {"name": "Firulais"}
+ assert jsonable_encoder(pet, exclude=["owner"]) == {"name": "Firulais"}
+ assert jsonable_encoder(pet, include=[]) == {}
+ assert jsonable_encoder(pet, exclude=[]) == {
+ "name": "Firulais",
+ "owner": {"name": "Foo"},
+ }
+
+
def test_encode_class():
person = Person(name="Foo")
pet = Pet(owner=person, name="Firulais")
@@ -130,7 +137,6 @@ def test_encode_unsupported():
jsonable_encoder(unserializable)
-@needs_pydanticv2
def test_encode_custom_json_encoders_model_pydanticv2():
from pydantic import field_serializer
@@ -150,27 +156,17 @@ def test_encode_custom_json_encoders_model_pydanticv2():
assert jsonable_encoder(subclass_model) == {"dt_field": "2019-01-01T08:00:00+00:00"}
-# TODO: remove when deprecating Pydantic v1
-@needs_pydanticv1
-def test_encode_custom_json_encoders_model_pydanticv1():
- class ModelWithCustomEncoder(BaseModel):
- dt_field: datetime
+def test_json_encoder_error_with_pydanticv1():
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", UserWarning)
+ from pydantic import v1
- class Config:
- json_encoders = {
- datetime: lambda dt: dt.replace(
- microsecond=0, tzinfo=timezone.utc
- ).isoformat()
- }
+ class ModelV1(v1.BaseModel):
+ name: str
- class ModelWithCustomEncoderSubclass(ModelWithCustomEncoder):
- class Config:
- pass
-
- model = ModelWithCustomEncoder(dt_field=datetime(2019, 1, 1, 8))
- assert jsonable_encoder(model) == {"dt_field": "2019-01-01T08:00:00+00:00"}
- subclass_model = ModelWithCustomEncoderSubclass(dt_field=datetime(2019, 1, 1, 8))
- assert jsonable_encoder(subclass_model) == {"dt_field": "2019-01-01T08:00:00+00:00"}
+ data = ModelV1(name="test")
+ with pytest.raises(PydanticV1NotSupportedError):
+ jsonable_encoder(data)
def test_encode_model_with_config():
@@ -206,23 +202,27 @@ def test_encode_model_with_default():
}
-@needs_pydanticv1
def test_custom_encoders():
class safe_datetime(datetime):
pass
- class MyModel(BaseModel):
+ class MyDict(TypedDict):
dt_field: safe_datetime
- instance = MyModel(dt_field=safe_datetime.now())
+ instance = MyDict(dt_field=safe_datetime.now())
encoded_instance = jsonable_encoder(
instance, custom_encoder={safe_datetime: lambda o: o.strftime("%H:%M:%S")}
)
- assert encoded_instance["dt_field"] == instance.dt_field.strftime("%H:%M:%S")
+ assert encoded_instance["dt_field"] == instance["dt_field"].strftime("%H:%M:%S")
+
+ encoded_instance = jsonable_encoder(
+ instance, custom_encoder={datetime: lambda o: o.strftime("%H:%M:%S")}
+ )
+ assert encoded_instance["dt_field"] == instance["dt_field"].strftime("%H:%M:%S")
encoded_instance2 = jsonable_encoder(instance)
- assert encoded_instance2["dt_field"] == instance.dt_field.isoformat()
+ assert encoded_instance2["dt_field"] == instance["dt_field"].isoformat()
def test_custom_enum_encoders():
@@ -244,12 +244,7 @@ def test_encode_model_with_pure_path():
class ModelWithPath(BaseModel):
path: PurePath
- if PYDANTIC_V2:
- model_config = {"arbitrary_types_allowed": True}
- else:
-
- class Config:
- arbitrary_types_allowed = True
+ model_config = {"arbitrary_types_allowed": True}
test_path = PurePath("/foo", "bar")
obj = ModelWithPath(path=test_path)
@@ -260,12 +255,7 @@ def test_encode_model_with_pure_posix_path():
class ModelWithPath(BaseModel):
path: PurePosixPath
- if PYDANTIC_V2:
- model_config = {"arbitrary_types_allowed": True}
- else:
-
- class Config:
- arbitrary_types_allowed = True
+ model_config = {"arbitrary_types_allowed": True}
obj = ModelWithPath(path=PurePosixPath("/foo", "bar"))
assert jsonable_encoder(obj) == {"path": "/foo/bar"}
@@ -275,45 +265,33 @@ def test_encode_model_with_pure_windows_path():
class ModelWithPath(BaseModel):
path: PureWindowsPath
- if PYDANTIC_V2:
- model_config = {"arbitrary_types_allowed": True}
- else:
-
- class Config:
- arbitrary_types_allowed = True
+ model_config = {"arbitrary_types_allowed": True}
obj = ModelWithPath(path=PureWindowsPath("/foo", "bar"))
assert jsonable_encoder(obj) == {"path": "\\foo\\bar"}
-@needs_pydanticv1
-def test_encode_root():
- class ModelWithRoot(BaseModel):
- __root__: str
+def test_encode_pure_path():
+ test_path = PurePath("/foo", "bar")
- model = ModelWithRoot(__root__="Foo")
- assert jsonable_encoder(model) == "Foo"
+ assert jsonable_encoder({"path": test_path}) == {"path": str(test_path)}
-@needs_pydanticv2
def test_decimal_encoder_float():
data = {"value": Decimal(1.23)}
assert jsonable_encoder(data) == {"value": 1.23}
-@needs_pydanticv2
def test_decimal_encoder_int():
data = {"value": Decimal(2)}
assert jsonable_encoder(data) == {"value": 2}
-@needs_pydanticv2
def test_decimal_encoder_nan():
data = {"value": Decimal("NaN")}
assert isnan(jsonable_encoder(data)["value"])
-@needs_pydanticv2
def test_decimal_encoder_infinity():
data = {"value": Decimal("Infinity")}
assert isinf(jsonable_encoder(data)["value"])
@@ -330,7 +308,6 @@ def test_encode_deque_encodes_child_models():
assert jsonable_encoder(dq)[0]["test"] == "test"
-@needs_pydanticv2
def test_encode_pydantic_undefined():
data = {"value": Undefined}
assert jsonable_encoder(data) == {"value": None}
diff --git a/tests/test_multi_body_errors.py b/tests/test_multi_body_errors.py
index 6ea405fe70..4418c77cb0 100644
--- a/tests/test_multi_body_errors.py
+++ b/tests/test_multi_body_errors.py
@@ -1,8 +1,9 @@
from decimal import Decimal
-from dirty_equals import IsDict, IsOneOf
+from dirty_equals import IsOneOf
from fastapi import FastAPI
from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
from pydantic import BaseModel, condecimal
app = FastAPI()
@@ -24,109 +25,65 @@ client = TestClient(app)
def test_put_correct_body():
response = client.post("/items/", json=[{"name": "Foo", "age": 5}])
assert response.status_code == 200, response.text
- assert response.json() == {
- "item": [
- {
- "name": "Foo",
- "age": IsOneOf(
- 5,
- # TODO: remove when deprecating Pydantic v1
- "5",
- ),
- }
- ]
- }
+ assert response.json() == snapshot(
+ {
+ "item": [
+ {
+ "name": "Foo",
+ "age": "5",
+ }
+ ]
+ }
+ )
def test_jsonable_encoder_requiring_error():
response = client.post("/items/", json=[{"name": "Foo", "age": -1.0}])
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "greater_than",
- "loc": ["body", 0, "age"],
- "msg": "Input should be greater than 0",
- "input": -1.0,
- "ctx": {"gt": 0},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "ctx": {"limit_value": 0.0},
- "loc": ["body", 0, "age"],
- "msg": "ensure this value is greater than 0",
- "type": "value_error.number.not_gt",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "greater_than",
+ "loc": ["body", 0, "age"],
+ "msg": "Input should be greater than 0",
+ "input": -1.0,
+ "ctx": {"gt": 0},
+ }
+ ]
+ }
def test_put_incorrect_body_multiple():
response = client.post("/items/", json=[{"age": "five"}, {"age": "six"}])
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", 0, "name"],
- "msg": "Field required",
- "input": {"age": "five"},
- },
- {
- "type": "decimal_parsing",
- "loc": ["body", 0, "age"],
- "msg": "Input should be a valid decimal",
- "input": "five",
- },
- {
- "type": "missing",
- "loc": ["body", 1, "name"],
- "msg": "Field required",
- "input": {"age": "six"},
- },
- {
- "type": "decimal_parsing",
- "loc": ["body", 1, "age"],
- "msg": "Input should be a valid decimal",
- "input": "six",
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", 0, "name"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", 0, "age"],
- "msg": "value is not a valid decimal",
- "type": "type_error.decimal",
- },
- {
- "loc": ["body", 1, "name"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", 1, "age"],
- "msg": "value is not a valid decimal",
- "type": "type_error.decimal",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", 0, "name"],
+ "msg": "Field required",
+ "input": {"age": "five"},
+ },
+ {
+ "type": "decimal_parsing",
+ "loc": ["body", 0, "age"],
+ "msg": "Input should be a valid decimal",
+ "input": "five",
+ },
+ {
+ "type": "missing",
+ "loc": ["body", 1, "name"],
+ "msg": "Field required",
+ "input": {"age": "six"},
+ },
+ {
+ "type": "decimal_parsing",
+ "loc": ["body", 1, "age"],
+ "msg": "Input should be a valid decimal",
+ "input": "six",
+ },
+ ]
+ }
def test_openapi_schema():
@@ -179,31 +136,21 @@ def test_openapi_schema():
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
- "age": IsDict(
- {
- "title": "Age",
- "anyOf": [
- {"exclusiveMinimum": 0.0, "type": "number"},
- IsOneOf(
- # pydantic < 2.12.0
- {"type": "string"},
- # pydantic >= 2.12.0
- {
- "type": "string",
- "pattern": r"^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$",
- },
- ),
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Age",
- "exclusiveMinimum": 0.0,
- "type": "number",
- }
- ),
+ "age": {
+ "title": "Age",
+ "anyOf": [
+ {"exclusiveMinimum": 0.0, "type": "number"},
+ IsOneOf(
+ # pydantic < 2.12.0
+ {"type": "string"},
+ # pydantic >= 2.12.0
+ {
+ "type": "string",
+ "pattern": r"^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$",
+ },
+ ),
+ ],
+ },
},
},
"ValidationError": {
diff --git a/tests/test_multi_query_errors.py b/tests/test_multi_query_errors.py
index 7387a81ddf..5df51ba185 100644
--- a/tests/test_multi_query_errors.py
+++ b/tests/test_multi_query_errors.py
@@ -1,4 +1,3 @@
-from dirty_equals import IsDict
from fastapi import FastAPI, Query
from fastapi.testclient import TestClient
@@ -22,40 +21,22 @@ def test_multi_query():
def test_multi_query_incorrect():
response = client.get("/items/?q=five&q=six")
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["query", "q", 0],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "five",
- },
- {
- "type": "int_parsing",
- "loc": ["query", "q", 1],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "six",
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "q", 0],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- },
- {
- "loc": ["query", "q", 1],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["query", "q", 0],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "five",
+ },
+ {
+ "type": "int_parsing",
+ "loc": ["query", "q", 1],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "six",
+ },
+ ]
+ }
def test_openapi_schema():
diff --git a/tests/test_no_schema_split.py b/tests/test_no_schema_split.py
index 6169867ba3..131a3755e7 100644
--- a/tests/test_no_schema_split.py
+++ b/tests/test_no_schema_split.py
@@ -9,8 +9,6 @@ from fastapi.testclient import TestClient
from inline_snapshot import snapshot
from pydantic import BaseModel, Field
-from tests.utils import pydantic_snapshot
-
class MessageEventType(str, Enum):
alpha = "alpha"
@@ -126,47 +124,21 @@ def test_openapi_schema():
},
"MessageEvent": {
"properties": {
- "event_type": pydantic_snapshot(
- v2=snapshot(
- {
- "$ref": "#/components/schemas/MessageEventType",
- "default": "alpha",
- }
- ),
- v1=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/MessageEventType"
- }
- ],
- "default": "alpha",
- }
- ),
- ),
+ "event_type": {
+ "$ref": "#/components/schemas/MessageEventType",
+ "default": "alpha",
+ },
"output": {"type": "string", "title": "Output"},
},
"type": "object",
"required": ["output"],
"title": "MessageEvent",
},
- "MessageEventType": pydantic_snapshot(
- v2=snapshot(
- {
- "type": "string",
- "enum": ["alpha", "beta"],
- "title": "MessageEventType",
- }
- ),
- v1=snapshot(
- {
- "type": "string",
- "enum": ["alpha", "beta"],
- "title": "MessageEventType",
- "description": "An enumeration.",
- }
- ),
- ),
+ "MessageEventType": {
+ "type": "string",
+ "enum": ["alpha", "beta"],
+ "title": "MessageEventType",
+ },
"MessageOutput": {
"properties": {
"body": {"type": "string", "title": "Body", "default": ""},
diff --git a/tests/test_openapi_examples.py b/tests/test_openapi_examples.py
index b3f83ae237..bd0d55452e 100644
--- a/tests/test_openapi_examples.py
+++ b/tests/test_openapi_examples.py
@@ -1,6 +1,5 @@
from typing import Union
-from dirty_equals import IsDict
from fastapi import Body, Cookie, FastAPI, Header, Path, Query
from fastapi.testclient import TestClient
from pydantic import BaseModel
@@ -155,26 +154,12 @@ def test_openapi_schema():
"requestBody": {
"content": {
"application/json": {
- "schema": IsDict(
- {
- "$ref": "#/components/schemas/Item",
- "examples": [
- {"data": "Data in Body examples, example1"}
- ],
- }
- )
- | IsDict(
- {
- # TODO: remove when deprecating Pydantic v1
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Item",
- "examples": [
- {"data": "Data in Body examples, example1"}
- ],
- }
- ),
+ "schema": {
+ "$ref": "#/components/schemas/Item",
+ "examples": [
+ {"data": "Data in Body examples, example1"}
+ ],
+ },
"examples": {
"Example One": {
"summary": "Example One Summary",
@@ -265,27 +250,14 @@ def test_openapi_schema():
"name": "data",
"in": "query",
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "examples": [
- "json_schema_query1",
- "json_schema_query2",
- ],
- "title": "Data",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "examples": [
- "json_schema_query1",
- "json_schema_query2",
- ],
- "type": "string",
- "title": "Data",
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "examples": [
+ "json_schema_query1",
+ "json_schema_query2",
+ ],
+ "title": "Data",
+ },
"examples": {
"Query One": {
"summary": "Query One Summary",
@@ -323,27 +295,14 @@ def test_openapi_schema():
"name": "data",
"in": "header",
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "examples": [
- "json_schema_header1",
- "json_schema_header2",
- ],
- "title": "Data",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "string",
- "examples": [
- "json_schema_header1",
- "json_schema_header2",
- ],
- "title": "Data",
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "examples": [
+ "json_schema_header1",
+ "json_schema_header2",
+ ],
+ "title": "Data",
+ },
"examples": {
"Header One": {
"summary": "Header One Summary",
@@ -381,27 +340,14 @@ def test_openapi_schema():
"name": "data",
"in": "cookie",
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "examples": [
- "json_schema_cookie1",
- "json_schema_cookie2",
- ],
- "title": "Data",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "string",
- "examples": [
- "json_schema_cookie1",
- "json_schema_cookie2",
- ],
- "title": "Data",
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "examples": [
+ "json_schema_cookie1",
+ "json_schema_cookie2",
+ ],
+ "title": "Data",
+ },
"examples": {
"Cookie One": {
"summary": "Cookie One Summary",
diff --git a/tests/test_openapi_query_parameter_extension.py b/tests/test_openapi_query_parameter_extension.py
index dc7147c712..084cb695d4 100644
--- a/tests/test_openapi_query_parameter_extension.py
+++ b/tests/test_openapi_query_parameter_extension.py
@@ -1,6 +1,5 @@
from typing import Optional
-from dirty_equals import IsDict
from fastapi import FastAPI
from fastapi.testclient import TestClient
@@ -53,21 +52,11 @@ def test_openapi():
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "integer"}, {"type": "null"}],
- "default": 50,
- "title": "Standard Query Param",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Standard Query Param",
- "type": "integer",
- "default": 50,
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "integer"}, {"type": "null"}],
+ "default": 50,
+ "title": "Standard Query Param",
+ },
"name": "standard_query_param",
"in": "query",
},
diff --git a/tests/test_openapi_separate_input_output_schemas.py b/tests/test_openapi_separate_input_output_schemas.py
index bfe5e9a712..1891f0bde0 100644
--- a/tests/test_openapi_separate_input_output_schemas.py
+++ b/tests/test_openapi_separate_input_output_schemas.py
@@ -3,37 +3,30 @@ from typing import Optional
from fastapi import FastAPI
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
-from pydantic import BaseModel
-
-from .utils import PYDANTIC_V2, needs_pydanticv2
+from pydantic import BaseModel, computed_field
class SubItem(BaseModel):
subname: str
sub_description: Optional[str] = None
tags: list[str] = []
- if PYDANTIC_V2:
- model_config = {"json_schema_serialization_defaults_required": True}
+ model_config = {"json_schema_serialization_defaults_required": True}
class Item(BaseModel):
name: str
description: Optional[str] = None
sub: Optional[SubItem] = None
- if PYDANTIC_V2:
- model_config = {"json_schema_serialization_defaults_required": True}
+ model_config = {"json_schema_serialization_defaults_required": True}
-if PYDANTIC_V2:
- from pydantic import computed_field
+class WithComputedField(BaseModel):
+ name: str
- class WithComputedField(BaseModel):
- name: str
-
- @computed_field
- @property
- def computed_field(self) -> str:
- return f"computed {self.name}"
+ @computed_field
+ @property
+ def computed_field(self) -> str:
+ return f"computed {self.name}"
def get_app_client(separate_input_output_schemas: bool = True) -> TestClient:
@@ -58,13 +51,11 @@ def get_app_client(separate_input_output_schemas: bool = True) -> TestClient:
Item(name="Plumbus"),
]
- if PYDANTIC_V2:
-
- @app.post("/with-computed-field/")
- def create_with_computed_field(
- with_computed_field: WithComputedField,
- ) -> WithComputedField:
- return with_computed_field
+ @app.post("/with-computed-field/")
+ def create_with_computed_field(
+ with_computed_field: WithComputedField,
+ ) -> WithComputedField:
+ return with_computed_field
client = TestClient(app)
return client
@@ -151,7 +142,6 @@ def test_read_items():
)
-@needs_pydanticv2
def test_with_computed_field():
client = get_app_client()
client_no = get_app_client(separate_input_output_schemas=False)
@@ -168,7 +158,6 @@ def test_with_computed_field():
)
-@needs_pydanticv2
def test_openapi_schema():
client = get_app_client()
response = client.get("/openapi.json")
@@ -449,7 +438,6 @@ def test_openapi_schema():
)
-@needs_pydanticv2
def test_openapi_schema_no_separate():
client = get_app_client(separate_input_output_schemas=False)
response = client.get("/openapi.json")
diff --git a/tests/test_openapi_servers.py b/tests/test_openapi_servers.py
index 8697c8438b..33079e4b1d 100644
--- a/tests/test_openapi_servers.py
+++ b/tests/test_openapi_servers.py
@@ -1,6 +1,6 @@
-from dirty_equals import IsOneOf
from fastapi import FastAPI
from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
app = FastAPI(
servers=[
@@ -30,39 +30,31 @@ def test_app():
def test_openapi_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"},
- "servers": [
- {"url": "/", "description": "Default, relative server"},
- {
- "url": IsOneOf(
- "http://staging.localhost.tiangolo.com:8000/",
- # TODO: remove when deprecating Pydantic v1
- "http://staging.localhost.tiangolo.com:8000",
- ),
- "description": "Staging but actually localhost still",
- },
- {
- "url": IsOneOf(
- "https://prod.example.com/",
- # TODO: remove when deprecating Pydantic v1
- "https://prod.example.com",
- )
- },
- ],
- "paths": {
- "/foo": {
- "get": {
- "summary": "Foo",
- "operationId": "foo_foo_get",
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "servers": [
+ {"url": "/", "description": "Default, relative server"},
+ {
+ "url": "http://staging.localhost.tiangolo.com:8000",
+ "description": "Staging but actually localhost still",
+ },
+ {"url": "https://prod.example.com"},
+ ],
+ "paths": {
+ "/foo": {
+ "get": {
+ "summary": "Foo",
+ "operationId": "foo_foo_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
}
- }
- },
- }
+ },
+ }
+ )
diff --git a/tests/test_params_repr.py b/tests/test_params_repr.py
index 19c2e8d696..670e4f5ddf 100644
--- a/tests/test_params_repr.py
+++ b/tests/test_params_repr.py
@@ -1,6 +1,5 @@
from typing import Any
-from dirty_equals import IsOneOf
from fastapi.params import Body, Cookie, Header, Param, Path, Query
test_data: list[Any] = ["teststr", None, ..., 1, []]
@@ -19,11 +18,7 @@ def test_param_repr_none():
def test_param_repr_ellipsis():
- assert repr(Param(...)) == IsOneOf(
- "Param(PydanticUndefined)",
- # TODO: remove when deprecating Pydantic v1
- "Param(Ellipsis)",
- )
+ assert repr(Param(...)) == "Param(PydanticUndefined)"
def test_param_repr_number():
@@ -35,16 +30,8 @@ def test_param_repr_list():
def test_path_repr():
- assert repr(Path()) == IsOneOf(
- "Path(PydanticUndefined)",
- # TODO: remove when deprecating Pydantic v1
- "Path(Ellipsis)",
- )
- assert repr(Path(...)) == IsOneOf(
- "Path(PydanticUndefined)",
- # TODO: remove when deprecating Pydantic v1
- "Path(Ellipsis)",
- )
+ assert repr(Path()) == "Path(PydanticUndefined)"
+ assert repr(Path(...)) == "Path(PydanticUndefined)"
def test_query_repr_str():
@@ -56,11 +43,7 @@ def test_query_repr_none():
def test_query_repr_ellipsis():
- assert repr(Query(...)) == IsOneOf(
- "Query(PydanticUndefined)",
- # TODO: remove when deprecating Pydantic v1
- "Query(Ellipsis)",
- )
+ assert repr(Query(...)) == "Query(PydanticUndefined)"
def test_query_repr_number():
@@ -80,11 +63,7 @@ def test_header_repr_none():
def test_header_repr_ellipsis():
- assert repr(Header(...)) == IsOneOf(
- "Header(PydanticUndefined)",
- # TODO: remove when deprecating Pydantic v1
- "Header(Ellipsis)",
- )
+ assert repr(Header(...)) == "Header(PydanticUndefined)"
def test_header_repr_number():
@@ -104,11 +83,7 @@ def test_cookie_repr_none():
def test_cookie_repr_ellipsis():
- assert repr(Cookie(...)) == IsOneOf(
- "Cookie(PydanticUndefined)",
- # TODO: remove when deprecating Pydantic v1
- "Cookie(Ellipsis)",
- )
+ assert repr(Cookie(...)) == "Cookie(PydanticUndefined)"
def test_cookie_repr_number():
@@ -128,11 +103,7 @@ def test_body_repr_none():
def test_body_repr_ellipsis():
- assert repr(Body(...)) == IsOneOf(
- "Body(PydanticUndefined)",
- # TODO: remove when deprecating Pydantic v1
- "Body(Ellipsis)",
- )
+ assert repr(Body(...)) == "Body(PydanticUndefined)"
def test_body_repr_number():
diff --git a/tests/test_path.py b/tests/test_path.py
index 09c1f13fb1..47051b927c 100644
--- a/tests/test_path.py
+++ b/tests/test_path.py
@@ -1,4 +1,3 @@
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
from .main import app
@@ -45,57 +44,31 @@ def test_path_str_True():
def test_path_int_foobar():
response = client.get("/path/int/foobar")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "foobar",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ 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": "foobar",
+ }
+ ]
+ }
def test_path_int_True():
response = client.get("/path/int/True")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "True",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ 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": "True",
+ }
+ ]
+ }
def test_path_int_42():
@@ -107,85 +80,46 @@ def test_path_int_42():
def test_path_int_42_5():
response = client.get("/path/int/42.5")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "42.5",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ 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": "42.5",
+ }
+ ]
+ }
def test_path_float_foobar():
response = client.get("/path/float/foobar")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "float_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid number, unable to parse string as a number",
- "input": "foobar",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value is not a valid float",
- "type": "type_error.float",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "float_parsing",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be a valid number, unable to parse string as a number",
+ "input": "foobar",
+ }
+ ]
+ }
def test_path_float_True():
response = client.get("/path/float/True")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "float_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid number, unable to parse string as a number",
- "input": "True",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value is not a valid float",
- "type": "type_error.float",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "float_parsing",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be a valid number, unable to parse string as a number",
+ "input": "True",
+ }
+ ]
+ }
def test_path_float_42():
@@ -203,29 +137,16 @@ def test_path_float_42_5():
def test_path_bool_foobar():
response = client.get("/path/bool/foobar")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "bool_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid boolean, unable to interpret input",
- "input": "foobar",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value could not be parsed to a boolean",
- "type": "type_error.bool",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "bool_parsing",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be a valid boolean, unable to interpret input",
+ "input": "foobar",
+ }
+ ]
+ }
def test_path_bool_True():
@@ -237,57 +158,31 @@ def test_path_bool_True():
def test_path_bool_42():
response = client.get("/path/bool/42")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "bool_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid boolean, unable to interpret input",
- "input": "42",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value could not be parsed to a boolean",
- "type": "type_error.bool",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "bool_parsing",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be a valid boolean, unable to interpret input",
+ "input": "42",
+ }
+ ]
+ }
def test_path_bool_42_5():
response = client.get("/path/bool/42.5")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "bool_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid boolean, unable to interpret input",
- "input": "42.5",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value could not be parsed to a boolean",
- "type": "type_error.bool",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "bool_parsing",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be a valid boolean, unable to interpret input",
+ "input": "42.5",
+ }
+ ]
+ }
def test_path_bool_1():
@@ -335,31 +230,17 @@ def test_path_param_minlength_foo():
def test_path_param_minlength_fo():
response = client.get("/path/param-minlength/fo")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "string_too_short",
- "loc": ["path", "item_id"],
- "msg": "String should have at least 3 characters",
- "input": "fo",
- "ctx": {"min_length": 3},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value has at least 3 characters",
- "type": "value_error.any_str.min_length",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_too_short",
+ "loc": ["path", "item_id"],
+ "msg": "String should have at least 3 characters",
+ "input": "fo",
+ "ctx": {"min_length": 3},
+ }
+ ]
+ }
def test_path_param_maxlength_foo():
@@ -371,31 +252,17 @@ def test_path_param_maxlength_foo():
def test_path_param_maxlength_foobar():
response = client.get("/path/param-maxlength/foobar")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "string_too_long",
- "loc": ["path", "item_id"],
- "msg": "String should have at most 3 characters",
- "input": "foobar",
- "ctx": {"max_length": 3},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value has at most 3 characters",
- "type": "value_error.any_str.max_length",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_too_long",
+ "loc": ["path", "item_id"],
+ "msg": "String should have at most 3 characters",
+ "input": "foobar",
+ "ctx": {"max_length": 3},
+ }
+ ]
+ }
def test_path_param_min_maxlength_foo():
@@ -407,60 +274,33 @@ def test_path_param_min_maxlength_foo():
def test_path_param_min_maxlength_foobar():
response = client.get("/path/param-min_maxlength/foobar")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "string_too_long",
- "loc": ["path", "item_id"],
- "msg": "String should have at most 3 characters",
- "input": "foobar",
- "ctx": {"max_length": 3},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value has at most 3 characters",
- "type": "value_error.any_str.max_length",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_too_long",
+ "loc": ["path", "item_id"],
+ "msg": "String should have at most 3 characters",
+ "input": "foobar",
+ "ctx": {"max_length": 3},
+ }
+ ]
+ }
def test_path_param_min_maxlength_f():
response = client.get("/path/param-min_maxlength/f")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "string_too_short",
- "loc": ["path", "item_id"],
- "msg": "String should have at least 2 characters",
- "input": "f",
- "ctx": {"min_length": 2},
- }
- ]
- }
- ) | IsDict(
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value has at least 2 characters",
- "type": "value_error.any_str.min_length",
- "ctx": {"limit_value": 2},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_too_short",
+ "loc": ["path", "item_id"],
+ "msg": "String should have at least 2 characters",
+ "input": "f",
+ "ctx": {"min_length": 2},
+ }
+ ]
+ }
def test_path_param_gt_42():
@@ -472,31 +312,17 @@ def test_path_param_gt_42():
def test_path_param_gt_2():
response = client.get("/path/param-gt/2")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "greater_than",
- "loc": ["path", "item_id"],
- "msg": "Input should be greater than 3",
- "input": "2",
- "ctx": {"gt": 3.0},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is greater than 3",
- "type": "value_error.number.not_gt",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "greater_than",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be greater than 3",
+ "input": "2",
+ "ctx": {"gt": 3.0},
+ }
+ ]
+ }
def test_path_param_gt0_0_05():
@@ -508,31 +334,17 @@ def test_path_param_gt0_0_05():
def test_path_param_gt0_0():
response = client.get("/path/param-gt0/0")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "greater_than",
- "loc": ["path", "item_id"],
- "msg": "Input should be greater than 0",
- "input": "0",
- "ctx": {"gt": 0.0},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is greater than 0",
- "type": "value_error.number.not_gt",
- "ctx": {"limit_value": 0},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "greater_than",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be greater than 0",
+ "input": "0",
+ "ctx": {"gt": 0.0},
+ }
+ ]
+ }
def test_path_param_ge_42():
@@ -550,61 +362,33 @@ def test_path_param_ge_3():
def test_path_param_ge_2():
response = client.get("/path/param-ge/2")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "greater_than_equal",
- "loc": ["path", "item_id"],
- "msg": "Input should be greater than or equal to 3",
- "input": "2",
- "ctx": {"ge": 3.0},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is greater than or equal to 3",
- "type": "value_error.number.not_ge",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "greater_than_equal",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be greater than or equal to 3",
+ "input": "2",
+ "ctx": {"ge": 3.0},
+ }
+ ]
+ }
def test_path_param_lt_42():
response = client.get("/path/param-lt/42")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "less_than",
- "loc": ["path", "item_id"],
- "msg": "Input should be less than 3",
- "input": "42",
- "ctx": {"lt": 3.0},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is less than 3",
- "type": "value_error.number.not_lt",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "less_than",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be less than 3",
+ "input": "42",
+ "ctx": {"lt": 3.0},
+ }
+ ]
+ }
def test_path_param_lt_2():
@@ -622,61 +406,33 @@ def test_path_param_lt0__1():
def test_path_param_lt0_0():
response = client.get("/path/param-lt0/0")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "less_than",
- "loc": ["path", "item_id"],
- "msg": "Input should be less than 0",
- "input": "0",
- "ctx": {"lt": 0.0},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is less than 0",
- "type": "value_error.number.not_lt",
- "ctx": {"limit_value": 0},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "less_than",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be less than 0",
+ "input": "0",
+ "ctx": {"lt": 0.0},
+ }
+ ]
+ }
def test_path_param_le_42():
response = client.get("/path/param-le/42")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "less_than_equal",
- "loc": ["path", "item_id"],
- "msg": "Input should be less than or equal to 3",
- "input": "42",
- "ctx": {"le": 3.0},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is less than or equal to 3",
- "type": "value_error.number.not_le",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "less_than_equal",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be less than or equal to 3",
+ "input": "42",
+ "ctx": {"le": 3.0},
+ }
+ ]
+ }
def test_path_param_le_3():
@@ -700,61 +456,33 @@ def test_path_param_lt_gt_2():
def test_path_param_lt_gt_4():
response = client.get("/path/param-lt-gt/4")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "less_than",
- "loc": ["path", "item_id"],
- "msg": "Input should be less than 3",
- "input": "4",
- "ctx": {"lt": 3.0},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is less than 3",
- "type": "value_error.number.not_lt",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "less_than",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be less than 3",
+ "input": "4",
+ "ctx": {"lt": 3.0},
+ }
+ ]
+ }
def test_path_param_lt_gt_0():
response = client.get("/path/param-lt-gt/0")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "greater_than",
- "loc": ["path", "item_id"],
- "msg": "Input should be greater than 1",
- "input": "0",
- "ctx": {"gt": 1.0},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is greater than 1",
- "type": "value_error.number.not_gt",
- "ctx": {"limit_value": 1},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "greater_than",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be greater than 1",
+ "input": "0",
+ "ctx": {"gt": 1.0},
+ }
+ ]
+ }
def test_path_param_le_ge_2():
@@ -777,31 +505,17 @@ def test_path_param_le_ge_3():
def test_path_param_le_ge_4():
response = client.get("/path/param-le-ge/4")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "less_than_equal",
- "loc": ["path", "item_id"],
- "msg": "Input should be less than or equal to 3",
- "input": "4",
- "ctx": {"le": 3.0},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is less than or equal to 3",
- "type": "value_error.number.not_le",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "less_than_equal",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be less than or equal to 3",
+ "input": "4",
+ "ctx": {"le": 3.0},
+ }
+ ]
+ }
def test_path_param_lt_int_2():
@@ -813,59 +527,32 @@ def test_path_param_lt_int_2():
def test_path_param_lt_int_42():
response = client.get("/path/param-lt-int/42")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "less_than",
- "loc": ["path", "item_id"],
- "msg": "Input should be less than 3",
- "input": "42",
- "ctx": {"lt": 3},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is less than 3",
- "type": "value_error.number.not_lt",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "less_than",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be less than 3",
+ "input": "42",
+ "ctx": {"lt": 3},
+ }
+ ]
+ }
def test_path_param_lt_int_2_7():
response = client.get("/path/param-lt-int/2.7")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "2.7",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ 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": "2.7",
+ }
+ ]
+ }
def test_path_param_gt_int_42():
@@ -877,89 +564,48 @@ def test_path_param_gt_int_42():
def test_path_param_gt_int_2():
response = client.get("/path/param-gt-int/2")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "greater_than",
- "loc": ["path", "item_id"],
- "msg": "Input should be greater than 3",
- "input": "2",
- "ctx": {"gt": 3},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is greater than 3",
- "type": "value_error.number.not_gt",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "greater_than",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be greater than 3",
+ "input": "2",
+ "ctx": {"gt": 3},
+ }
+ ]
+ }
def test_path_param_gt_int_2_7():
response = client.get("/path/param-gt-int/2.7")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "2.7",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ 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": "2.7",
+ }
+ ]
+ }
def test_path_param_le_int_42():
response = client.get("/path/param-le-int/42")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "less_than_equal",
- "loc": ["path", "item_id"],
- "msg": "Input should be less than or equal to 3",
- "input": "42",
- "ctx": {"le": 3},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is less than or equal to 3",
- "type": "value_error.number.not_le",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "less_than_equal",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be less than or equal to 3",
+ "input": "42",
+ "ctx": {"le": 3},
+ }
+ ]
+ }
def test_path_param_le_int_3():
@@ -977,29 +623,16 @@ def test_path_param_le_int_2():
def test_path_param_le_int_2_7():
response = client.get("/path/param-le-int/2.7")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "2.7",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ 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": "2.7",
+ }
+ ]
+ }
def test_path_param_ge_int_42():
@@ -1017,59 +650,32 @@ def test_path_param_ge_int_3():
def test_path_param_ge_int_2():
response = client.get("/path/param-ge-int/2")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "greater_than_equal",
- "loc": ["path", "item_id"],
- "msg": "Input should be greater than or equal to 3",
- "input": "2",
- "ctx": {"ge": 3},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is greater than or equal to 3",
- "type": "value_error.number.not_ge",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "greater_than_equal",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be greater than or equal to 3",
+ "input": "2",
+ "ctx": {"ge": 3},
+ }
+ ]
+ }
def test_path_param_ge_int_2_7():
response = client.get("/path/param-ge-int/2.7")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "2.7",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ 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": "2.7",
+ }
+ ]
+ }
def test_path_param_lt_gt_int_2():
@@ -1081,89 +687,48 @@ def test_path_param_lt_gt_int_2():
def test_path_param_lt_gt_int_4():
response = client.get("/path/param-lt-gt-int/4")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "less_than",
- "loc": ["path", "item_id"],
- "msg": "Input should be less than 3",
- "input": "4",
- "ctx": {"lt": 3},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is less than 3",
- "type": "value_error.number.not_lt",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "less_than",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be less than 3",
+ "input": "4",
+ "ctx": {"lt": 3},
+ }
+ ]
+ }
def test_path_param_lt_gt_int_0():
response = client.get("/path/param-lt-gt-int/0")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "greater_than",
- "loc": ["path", "item_id"],
- "msg": "Input should be greater than 1",
- "input": "0",
- "ctx": {"gt": 1},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is greater than 1",
- "type": "value_error.number.not_gt",
- "ctx": {"limit_value": 1},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "greater_than",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be greater than 1",
+ "input": "0",
+ "ctx": {"gt": 1},
+ }
+ ]
+ }
def test_path_param_lt_gt_int_2_7():
response = client.get("/path/param-lt-gt-int/2.7")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "2.7",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ 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": "2.7",
+ }
+ ]
+ }
def test_path_param_le_ge_int_2():
@@ -1187,56 +752,29 @@ def test_path_param_le_ge_int_3():
def test_path_param_le_ge_int_4():
response = client.get("/path/param-le-ge-int/4")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "less_than_equal",
- "loc": ["path", "item_id"],
- "msg": "Input should be less than or equal to 3",
- "input": "4",
- "ctx": {"le": 3},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is less than or equal to 3",
- "type": "value_error.number.not_le",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "less_than_equal",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be less than or equal to 3",
+ "input": "4",
+ "ctx": {"le": 3},
+ }
+ ]
+ }
def test_path_param_le_ge_int_2_7():
response = client.get("/path/param-le-ge-int/2.7")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "2.7",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ 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": "2.7",
+ }
+ ]
+ }
diff --git a/tests/test_pydantic_v1_error.py b/tests/test_pydantic_v1_error.py
new file mode 100644
index 0000000000..13229a3137
--- /dev/null
+++ b/tests/test_pydantic_v1_error.py
@@ -0,0 +1,97 @@
+import sys
+import warnings
+from typing import Union
+
+import pytest
+
+from tests.utils import skip_module_if_py_gte_314
+
+if sys.version_info >= (3, 14):
+ skip_module_if_py_gte_314()
+
+from fastapi import FastAPI
+from fastapi.exceptions import PydanticV1NotSupportedError
+
+with warnings.catch_warnings():
+ warnings.simplefilter("ignore", UserWarning)
+ from pydantic.v1 import BaseModel
+
+
+def test_raises_pydantic_v1_model_in_endpoint_param() -> None:
+ class ParamModelV1(BaseModel):
+ name: str
+
+ app = FastAPI()
+
+ with pytest.raises(PydanticV1NotSupportedError):
+
+ @app.post("/param")
+ def endpoint(data: ParamModelV1): # pragma: no cover
+ return data
+
+
+def test_raises_pydantic_v1_model_in_return_type() -> None:
+ class ReturnModelV1(BaseModel):
+ name: str
+
+ app = FastAPI()
+
+ with pytest.raises(PydanticV1NotSupportedError):
+
+ @app.get("/return")
+ def endpoint() -> ReturnModelV1: # pragma: no cover
+ return ReturnModelV1(name="test")
+
+
+def test_raises_pydantic_v1_model_in_response_model() -> None:
+ class ResponseModelV1(BaseModel):
+ name: str
+
+ app = FastAPI()
+
+ with pytest.raises(PydanticV1NotSupportedError):
+
+ @app.get("/response-model", response_model=ResponseModelV1)
+ def endpoint(): # pragma: no cover
+ return {"name": "test"}
+
+
+def test_raises_pydantic_v1_model_in_additional_responses_model() -> None:
+ class ErrorModelV1(BaseModel):
+ detail: str
+
+ app = FastAPI()
+
+ with pytest.raises(PydanticV1NotSupportedError):
+
+ @app.get(
+ "/responses", response_model=None, responses={400: {"model": ErrorModelV1}}
+ )
+ def endpoint(): # pragma: no cover
+ return {"ok": True}
+
+
+def test_raises_pydantic_v1_model_in_union() -> None:
+ class ModelV1A(BaseModel):
+ name: str
+
+ app = FastAPI()
+
+ with pytest.raises(PydanticV1NotSupportedError):
+
+ @app.post("/union")
+ def endpoint(data: Union[dict, ModelV1A]): # pragma: no cover
+ return data
+
+
+def test_raises_pydantic_v1_model_in_sequence() -> None:
+ class ModelV1A(BaseModel):
+ name: str
+
+ app = FastAPI()
+
+ with pytest.raises(PydanticV1NotSupportedError):
+
+ @app.post("/sequence")
+ def endpoint(data: list[ModelV1A]): # pragma: no cover
+ return data
diff --git a/tests/test_pydantic_v1_v2_01.py b/tests/test_pydantic_v1_v2_01.py
deleted file mode 100644
index ebf86b99f0..0000000000
--- a/tests/test_pydantic_v1_v2_01.py
+++ /dev/null
@@ -1,475 +0,0 @@
-import sys
-from typing import Any, Union
-
-from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314
-
-if sys.version_info >= (3, 14):
- skip_module_if_py_gte_314()
-
-from fastapi import FastAPI
-from fastapi._compat.v1 import BaseModel
-from fastapi.testclient import TestClient
-from inline_snapshot import snapshot
-
-
-class SubItem(BaseModel):
- name: str
-
-
-class Item(BaseModel):
- title: str
- size: int
- description: Union[str, None] = None
- sub: SubItem
- multi: list[SubItem] = []
-
-
-app = FastAPI()
-
-
-@app.post("/simple-model")
-def handle_simple_model(data: SubItem) -> SubItem:
- return data
-
-
-@app.post("/simple-model-filter", response_model=SubItem)
-def handle_simple_model_filter(data: SubItem) -> Any:
- extended_data = data.dict()
- extended_data.update({"secret_price": 42})
- return extended_data
-
-
-@app.post("/item")
-def handle_item(data: Item) -> Item:
- return data
-
-
-@app.post("/item-filter", response_model=Item)
-def handle_item_filter(data: Item) -> Any:
- extended_data = data.dict()
- extended_data.update({"secret_data": "classified", "internal_id": 12345})
- extended_data["sub"].update({"internal_id": 67890})
- return extended_data
-
-
-client = TestClient(app)
-
-
-def test_old_simple_model():
- response = client.post(
- "/simple-model",
- json={"name": "Foo"},
- )
- assert response.status_code == 200, response.text
- assert response.json() == {"name": "Foo"}
-
-
-def test_old_simple_model_validation_error():
- response = client.post(
- "/simple-model",
- json={"wrong_name": "Foo"},
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", "name"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
-
-
-def test_old_simple_model_filter():
- response = client.post(
- "/simple-model-filter",
- json={"name": "Foo"},
- )
- assert response.status_code == 200, response.text
- assert response.json() == {"name": "Foo"}
-
-
-def test_item_model():
- response = client.post(
- "/item",
- json={
- "title": "Test Item",
- "size": 100,
- "description": "This is a test item",
- "sub": {"name": "SubItem1"},
- "multi": [{"name": "Multi1"}, {"name": "Multi2"}],
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "title": "Test Item",
- "size": 100,
- "description": "This is a test item",
- "sub": {"name": "SubItem1"},
- "multi": [{"name": "Multi1"}, {"name": "Multi2"}],
- }
-
-
-def test_item_model_minimal():
- response = client.post(
- "/item",
- json={"title": "Minimal Item", "size": 50, "sub": {"name": "SubMin"}},
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "title": "Minimal Item",
- "size": 50,
- "description": None,
- "sub": {"name": "SubMin"},
- "multi": [],
- }
-
-
-def test_item_model_validation_errors():
- response = client.post(
- "/item",
- json={"title": "Missing fields"},
- )
- assert response.status_code == 422, response.text
- error_detail = response.json()["detail"]
- assert len(error_detail) == 2
- assert {
- "loc": ["body", "size"],
- "msg": "field required",
- "type": "value_error.missing",
- } in error_detail
- assert {
- "loc": ["body", "sub"],
- "msg": "field required",
- "type": "value_error.missing",
- } in error_detail
-
-
-def test_item_model_nested_validation_error():
- response = client.post(
- "/item",
- json={"title": "Test Item", "size": 100, "sub": {"wrong_field": "test"}},
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", "sub", "name"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
-
-
-def test_item_model_invalid_type():
- response = client.post(
- "/item",
- json={"title": "Test Item", "size": "not_a_number", "sub": {"name": "SubItem"}},
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", "size"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
-
-
-def test_item_filter():
- response = client.post(
- "/item-filter",
- json={
- "title": "Filtered Item",
- "size": 200,
- "description": "Test filtering",
- "sub": {"name": "SubFiltered"},
- "multi": [],
- },
- )
- assert response.status_code == 200, response.text
- result = response.json()
- assert result == {
- "title": "Filtered Item",
- "size": 200,
- "description": "Test filtering",
- "sub": {"name": "SubFiltered"},
- "multi": [],
- }
- assert "secret_data" not in result
- assert "internal_id" not in result
-
-
-def test_openapi_schema():
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == snapshot(
- {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/simple-model": {
- "post": {
- "summary": "Handle Simple Model",
- "operationId": "handle_simple_model_simple_model_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/SubItem"
- }
- ],
- "title": "Data",
- }
- ),
- v1=snapshot(
- {"$ref": "#/components/schemas/SubItem"}
- ),
- )
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/SubItem"
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/simple-model-filter": {
- "post": {
- "summary": "Handle Simple Model Filter",
- "operationId": "handle_simple_model_filter_simple_model_filter_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/SubItem"
- }
- ],
- "title": "Data",
- }
- ),
- v1=snapshot(
- {"$ref": "#/components/schemas/SubItem"}
- ),
- )
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/SubItem"
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/item": {
- "post": {
- "summary": "Handle Item",
- "operationId": "handle_item_item_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Item"
- }
- ],
- "title": "Data",
- }
- ),
- v1=snapshot(
- {"$ref": "#/components/schemas/Item"}
- ),
- )
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/item-filter": {
- "post": {
- "summary": "Handle Item Filter",
- "operationId": "handle_item_filter_item_filter_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Item"
- }
- ],
- "title": "Data",
- }
- ),
- v1=snapshot(
- {"$ref": "#/components/schemas/Item"}
- ),
- )
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- },
- "components": {
- "schemas": {
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {
- "$ref": "#/components/schemas/ValidationError"
- },
- "type": "array",
- "title": "Detail",
- }
- },
- "type": "object",
- "title": "HTTPValidationError",
- },
- "Item": {
- "properties": {
- "title": {"type": "string", "title": "Title"},
- "size": {"type": "integer", "title": "Size"},
- "description": {"type": "string", "title": "Description"},
- "sub": {"$ref": "#/components/schemas/SubItem"},
- "multi": {
- "items": {"$ref": "#/components/schemas/SubItem"},
- "type": "array",
- "title": "Multi",
- "default": [],
- },
- },
- "type": "object",
- "required": ["title", "size", "sub"],
- "title": "Item",
- },
- "SubItem": {
- "properties": {"name": {"type": "string", "title": "Name"}},
- "type": "object",
- "required": ["name"],
- "title": "SubItem",
- },
- "ValidationError": {
- "properties": {
- "loc": {
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- "type": "array",
- "title": "Location",
- },
- "msg": {"type": "string", "title": "Message"},
- "type": {"type": "string", "title": "Error Type"},
- },
- "type": "object",
- "required": ["loc", "msg", "type"],
- "title": "ValidationError",
- },
- }
- },
- }
- )
diff --git a/tests/test_pydantic_v1_v2_list.py b/tests/test_pydantic_v1_v2_list.py
deleted file mode 100644
index 8879b010de..0000000000
--- a/tests/test_pydantic_v1_v2_list.py
+++ /dev/null
@@ -1,701 +0,0 @@
-import sys
-from typing import Any, Union
-
-from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314
-
-if sys.version_info >= (3, 14):
- skip_module_if_py_gte_314()
-
-from fastapi import FastAPI
-from fastapi._compat.v1 import BaseModel
-from fastapi.testclient import TestClient
-from inline_snapshot import snapshot
-
-
-class SubItem(BaseModel):
- name: str
-
-
-class Item(BaseModel):
- title: str
- size: int
- description: Union[str, None] = None
- sub: SubItem
- multi: list[SubItem] = []
-
-
-app = FastAPI()
-
-
-@app.post("/item")
-def handle_item(data: Item) -> list[Item]:
- return [data, data]
-
-
-@app.post("/item-filter", response_model=list[Item])
-def handle_item_filter(data: Item) -> Any:
- extended_data = data.dict()
- extended_data.update({"secret_data": "classified", "internal_id": 12345})
- extended_data["sub"].update({"internal_id": 67890})
- return [extended_data, extended_data]
-
-
-@app.post("/item-list")
-def handle_item_list(data: list[Item]) -> Item:
- if data:
- return data[0]
- return Item(title="", size=0, sub=SubItem(name=""))
-
-
-@app.post("/item-list-filter", response_model=Item)
-def handle_item_list_filter(data: list[Item]) -> Any:
- if data:
- extended_data = data[0].dict()
- extended_data.update({"secret_data": "classified", "internal_id": 12345})
- extended_data["sub"].update({"internal_id": 67890})
- return extended_data
- return Item(title="", size=0, sub=SubItem(name=""))
-
-
-@app.post("/item-list-to-list")
-def handle_item_list_to_list(data: list[Item]) -> list[Item]:
- return data
-
-
-@app.post("/item-list-to-list-filter", response_model=list[Item])
-def handle_item_list_to_list_filter(data: list[Item]) -> Any:
- if data:
- extended_data = data[0].dict()
- extended_data.update({"secret_data": "classified", "internal_id": 12345})
- extended_data["sub"].update({"internal_id": 67890})
- return [extended_data, extended_data]
- return []
-
-
-client = TestClient(app)
-
-
-def test_item_to_list():
- response = client.post(
- "/item",
- json={
- "title": "Test Item",
- "size": 100,
- "description": "This is a test item",
- "sub": {"name": "SubItem1"},
- "multi": [{"name": "Multi1"}, {"name": "Multi2"}],
- },
- )
- assert response.status_code == 200, response.text
- result = response.json()
- assert isinstance(result, list)
- assert len(result) == 2
- for item in result:
- assert item == {
- "title": "Test Item",
- "size": 100,
- "description": "This is a test item",
- "sub": {"name": "SubItem1"},
- "multi": [{"name": "Multi1"}, {"name": "Multi2"}],
- }
-
-
-def test_item_to_list_filter():
- response = client.post(
- "/item-filter",
- json={
- "title": "Filtered Item",
- "size": 200,
- "description": "Test filtering",
- "sub": {"name": "SubFiltered"},
- "multi": [],
- },
- )
- assert response.status_code == 200, response.text
- result = response.json()
- assert isinstance(result, list)
- assert len(result) == 2
- for item in result:
- assert item == {
- "title": "Filtered Item",
- "size": 200,
- "description": "Test filtering",
- "sub": {"name": "SubFiltered"},
- "multi": [],
- }
- # Verify secret fields are filtered out
- assert "secret_data" not in item
- assert "internal_id" not in item
- assert "internal_id" not in item["sub"]
-
-
-def test_list_to_item():
- response = client.post(
- "/item-list",
- json=[
- {"title": "First Item", "size": 50, "sub": {"name": "First Sub"}},
- {"title": "Second Item", "size": 75, "sub": {"name": "Second Sub"}},
- ],
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "title": "First Item",
- "size": 50,
- "description": None,
- "sub": {"name": "First Sub"},
- "multi": [],
- }
-
-
-def test_list_to_item_empty():
- response = client.post(
- "/item-list",
- json=[],
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "title": "",
- "size": 0,
- "description": None,
- "sub": {"name": ""},
- "multi": [],
- }
-
-
-def test_list_to_item_filter():
- response = client.post(
- "/item-list-filter",
- json=[
- {
- "title": "First Item",
- "size": 100,
- "sub": {"name": "First Sub"},
- "multi": [{"name": "Multi1"}],
- },
- {"title": "Second Item", "size": 200, "sub": {"name": "Second Sub"}},
- ],
- )
- assert response.status_code == 200, response.text
- result = response.json()
- assert result == {
- "title": "First Item",
- "size": 100,
- "description": None,
- "sub": {"name": "First Sub"},
- "multi": [{"name": "Multi1"}],
- }
- # Verify secret fields are filtered out
- assert "secret_data" not in result
- assert "internal_id" not in result
-
-
-def test_list_to_item_filter_no_data():
- response = client.post("/item-list-filter", json=[])
- assert response.status_code == 200, response.text
- assert response.json() == {
- "title": "",
- "size": 0,
- "description": None,
- "sub": {"name": ""},
- "multi": [],
- }
-
-
-def test_list_to_list():
- input_items = [
- {"title": "Item 1", "size": 10, "sub": {"name": "Sub1"}},
- {
- "title": "Item 2",
- "size": 20,
- "description": "Second item",
- "sub": {"name": "Sub2"},
- "multi": [{"name": "M1"}, {"name": "M2"}],
- },
- {"title": "Item 3", "size": 30, "sub": {"name": "Sub3"}},
- ]
- response = client.post(
- "/item-list-to-list",
- json=input_items,
- )
- assert response.status_code == 200, response.text
- result = response.json()
- assert isinstance(result, list)
- assert len(result) == 3
- assert result[0] == {
- "title": "Item 1",
- "size": 10,
- "description": None,
- "sub": {"name": "Sub1"},
- "multi": [],
- }
- assert result[1] == {
- "title": "Item 2",
- "size": 20,
- "description": "Second item",
- "sub": {"name": "Sub2"},
- "multi": [{"name": "M1"}, {"name": "M2"}],
- }
- assert result[2] == {
- "title": "Item 3",
- "size": 30,
- "description": None,
- "sub": {"name": "Sub3"},
- "multi": [],
- }
-
-
-def test_list_to_list_filter():
- response = client.post(
- "/item-list-to-list-filter",
- json=[{"title": "Item 1", "size": 100, "sub": {"name": "Sub1"}}],
- )
- assert response.status_code == 200, response.text
- result = response.json()
- assert isinstance(result, list)
- assert len(result) == 2
- for item in result:
- assert item == {
- "title": "Item 1",
- "size": 100,
- "description": None,
- "sub": {"name": "Sub1"},
- "multi": [],
- }
- # Verify secret fields are filtered out
- assert "secret_data" not in item
- assert "internal_id" not in item
-
-
-def test_list_to_list_filter_no_data():
- response = client.post(
- "/item-list-to-list-filter",
- json=[],
- )
- assert response.status_code == 200, response.text
- assert response.json() == []
-
-
-def test_list_validation_error():
- response = client.post(
- "/item-list",
- json=[
- {"title": "Valid Item", "size": 100, "sub": {"name": "Sub1"}},
- {
- "title": "Invalid Item"
- # Missing required fields: size and sub
- },
- ],
- )
- assert response.status_code == 422, response.text
- error_detail = response.json()["detail"]
- assert len(error_detail) == 2
- assert {
- "loc": ["body", 1, "size"],
- "msg": "field required",
- "type": "value_error.missing",
- } in error_detail
- assert {
- "loc": ["body", 1, "sub"],
- "msg": "field required",
- "type": "value_error.missing",
- } in error_detail
-
-
-def test_list_nested_validation_error():
- response = client.post(
- "/item-list",
- json=[
- {"title": "Item with bad sub", "size": 100, "sub": {"wrong_field": "value"}}
- ],
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", 0, "sub", "name"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
-
-
-def test_list_type_validation_error():
- response = client.post(
- "/item-list",
- json=[{"title": "Item", "size": "not_a_number", "sub": {"name": "Sub"}}],
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", 0, "size"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
-
-
-def test_invalid_list_structure():
- response = client.post(
- "/item-list",
- json={"title": "Not a list", "size": 100, "sub": {"name": "Sub"}},
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body"],
- "msg": "value is not a valid list",
- "type": "type_error.list",
- }
- ]
- }
- )
-
-
-def test_openapi_schema():
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == snapshot(
- {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/item": {
- "post": {
- "summary": "Handle Item",
- "operationId": "handle_item_item_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Item"
- }
- ],
- "title": "Data",
- }
- ),
- v1=snapshot(
- {"$ref": "#/components/schemas/Item"}
- ),
- )
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/Item"
- },
- "type": "array",
- "title": "Response Handle Item Item Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/item-filter": {
- "post": {
- "summary": "Handle Item Filter",
- "operationId": "handle_item_filter_item_filter_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Item"
- }
- ],
- "title": "Data",
- }
- ),
- v1=snapshot(
- {"$ref": "#/components/schemas/Item"}
- ),
- )
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/Item"
- },
- "type": "array",
- "title": "Response Handle Item Filter Item Filter Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/item-list": {
- "post": {
- "summary": "Handle Item List",
- "operationId": "handle_item_list_item_list_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": {"$ref": "#/components/schemas/Item"},
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/item-list-filter": {
- "post": {
- "summary": "Handle Item List Filter",
- "operationId": "handle_item_list_filter_item_list_filter_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": {"$ref": "#/components/schemas/Item"},
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/item-list-to-list": {
- "post": {
- "summary": "Handle Item List To List",
- "operationId": "handle_item_list_to_list_item_list_to_list_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": {"$ref": "#/components/schemas/Item"},
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/Item"
- },
- "type": "array",
- "title": "Response Handle Item List To List Item List To List Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/item-list-to-list-filter": {
- "post": {
- "summary": "Handle Item List To List Filter",
- "operationId": "handle_item_list_to_list_filter_item_list_to_list_filter_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": {"$ref": "#/components/schemas/Item"},
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/Item"
- },
- "type": "array",
- "title": "Response Handle Item List To List Filter Item List To List Filter Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- },
- "components": {
- "schemas": {
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {
- "$ref": "#/components/schemas/ValidationError"
- },
- "type": "array",
- "title": "Detail",
- }
- },
- "type": "object",
- "title": "HTTPValidationError",
- },
- "Item": {
- "properties": {
- "title": {"type": "string", "title": "Title"},
- "size": {"type": "integer", "title": "Size"},
- "description": {"type": "string", "title": "Description"},
- "sub": {"$ref": "#/components/schemas/SubItem"},
- "multi": {
- "items": {"$ref": "#/components/schemas/SubItem"},
- "type": "array",
- "title": "Multi",
- "default": [],
- },
- },
- "type": "object",
- "required": ["title", "size", "sub"],
- "title": "Item",
- },
- "SubItem": {
- "properties": {"name": {"type": "string", "title": "Name"}},
- "type": "object",
- "required": ["name"],
- "title": "SubItem",
- },
- "ValidationError": {
- "properties": {
- "loc": {
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- "type": "array",
- "title": "Location",
- },
- "msg": {"type": "string", "title": "Message"},
- "type": {"type": "string", "title": "Error Type"},
- },
- "type": "object",
- "required": ["loc", "msg", "type"],
- "title": "ValidationError",
- },
- }
- },
- }
- )
diff --git a/tests/test_pydantic_v1_v2_mixed.py b/tests/test_pydantic_v1_v2_mixed.py
deleted file mode 100644
index e66583cd54..0000000000
--- a/tests/test_pydantic_v1_v2_mixed.py
+++ /dev/null
@@ -1,1499 +0,0 @@
-import sys
-from typing import Any, Union
-
-from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314
-
-if sys.version_info >= (3, 14):
- skip_module_if_py_gte_314()
-
-from fastapi import FastAPI
-from fastapi._compat.v1 import BaseModel
-from fastapi.testclient import TestClient
-from inline_snapshot import snapshot
-from pydantic import BaseModel as NewBaseModel
-
-
-class SubItem(BaseModel):
- name: str
-
-
-class Item(BaseModel):
- title: str
- size: int
- description: Union[str, None] = None
- sub: SubItem
- multi: list[SubItem] = []
-
-
-class NewSubItem(NewBaseModel):
- new_sub_name: str
-
-
-class NewItem(NewBaseModel):
- new_title: str
- new_size: int
- new_description: Union[str, None] = None
- new_sub: NewSubItem
- new_multi: list[NewSubItem] = []
-
-
-app = FastAPI()
-
-
-@app.post("/v1-to-v2/item")
-def handle_v1_item_to_v2(data: Item) -> NewItem:
- return NewItem(
- new_title=data.title,
- new_size=data.size,
- new_description=data.description,
- new_sub=NewSubItem(new_sub_name=data.sub.name),
- new_multi=[NewSubItem(new_sub_name=s.name) for s in data.multi],
- )
-
-
-@app.post("/v1-to-v2/item-filter", response_model=NewItem)
-def handle_v1_item_to_v2_filter(data: Item) -> Any:
- result = {
- "new_title": data.title,
- "new_size": data.size,
- "new_description": data.description,
- "new_sub": {"new_sub_name": data.sub.name, "new_sub_secret": "sub_hidden"},
- "new_multi": [
- {"new_sub_name": s.name, "new_sub_secret": "sub_hidden"} for s in data.multi
- ],
- "secret": "hidden_v1_to_v2",
- }
- return result
-
-
-@app.post("/v2-to-v1/item")
-def handle_v2_item_to_v1(data: NewItem) -> Item:
- return Item(
- title=data.new_title,
- size=data.new_size,
- description=data.new_description,
- sub=SubItem(name=data.new_sub.new_sub_name),
- multi=[SubItem(name=s.new_sub_name) for s in data.new_multi],
- )
-
-
-@app.post("/v2-to-v1/item-filter", response_model=Item)
-def handle_v2_item_to_v1_filter(data: NewItem) -> Any:
- result = {
- "title": data.new_title,
- "size": data.new_size,
- "description": data.new_description,
- "sub": {"name": data.new_sub.new_sub_name, "sub_secret": "sub_hidden"},
- "multi": [
- {"name": s.new_sub_name, "sub_secret": "sub_hidden"} for s in data.new_multi
- ],
- "secret": "hidden_v2_to_v1",
- }
- return result
-
-
-@app.post("/v1-to-v2/item-to-list")
-def handle_v1_item_to_v2_list(data: Item) -> list[NewItem]:
- converted = NewItem(
- new_title=data.title,
- new_size=data.size,
- new_description=data.description,
- new_sub=NewSubItem(new_sub_name=data.sub.name),
- new_multi=[NewSubItem(new_sub_name=s.name) for s in data.multi],
- )
- return [converted, converted]
-
-
-@app.post("/v1-to-v2/list-to-list")
-def handle_v1_list_to_v2_list(data: list[Item]) -> list[NewItem]:
- result = []
- for item in data:
- result.append(
- NewItem(
- new_title=item.title,
- new_size=item.size,
- new_description=item.description,
- new_sub=NewSubItem(new_sub_name=item.sub.name),
- new_multi=[NewSubItem(new_sub_name=s.name) for s in item.multi],
- )
- )
- return result
-
-
-@app.post("/v1-to-v2/list-to-list-filter", response_model=list[NewItem])
-def handle_v1_list_to_v2_list_filter(data: list[Item]) -> Any:
- result = []
- for item in data:
- converted = {
- "new_title": item.title,
- "new_size": item.size,
- "new_description": item.description,
- "new_sub": {"new_sub_name": item.sub.name, "new_sub_secret": "sub_hidden"},
- "new_multi": [
- {"new_sub_name": s.name, "new_sub_secret": "sub_hidden"}
- for s in item.multi
- ],
- "secret": "hidden_v2_to_v1",
- }
- result.append(converted)
- return result
-
-
-@app.post("/v1-to-v2/list-to-item")
-def handle_v1_list_to_v2_item(data: list[Item]) -> NewItem:
- if data:
- item = data[0]
- return NewItem(
- new_title=item.title,
- new_size=item.size,
- new_description=item.description,
- new_sub=NewSubItem(new_sub_name=item.sub.name),
- new_multi=[NewSubItem(new_sub_name=s.name) for s in item.multi],
- )
- return NewItem(new_title="", new_size=0, new_sub=NewSubItem(new_sub_name=""))
-
-
-@app.post("/v2-to-v1/item-to-list")
-def handle_v2_item_to_v1_list(data: NewItem) -> list[Item]:
- converted = Item(
- title=data.new_title,
- size=data.new_size,
- description=data.new_description,
- sub=SubItem(name=data.new_sub.new_sub_name),
- multi=[SubItem(name=s.new_sub_name) for s in data.new_multi],
- )
- return [converted, converted]
-
-
-@app.post("/v2-to-v1/list-to-list")
-def handle_v2_list_to_v1_list(data: list[NewItem]) -> list[Item]:
- result = []
- for item in data:
- result.append(
- Item(
- title=item.new_title,
- size=item.new_size,
- description=item.new_description,
- sub=SubItem(name=item.new_sub.new_sub_name),
- multi=[SubItem(name=s.new_sub_name) for s in item.new_multi],
- )
- )
- return result
-
-
-@app.post("/v2-to-v1/list-to-list-filter", response_model=list[Item])
-def handle_v2_list_to_v1_list_filter(data: list[NewItem]) -> Any:
- result = []
- for item in data:
- converted = {
- "title": item.new_title,
- "size": item.new_size,
- "description": item.new_description,
- "sub": {"name": item.new_sub.new_sub_name, "sub_secret": "sub_hidden"},
- "multi": [
- {"name": s.new_sub_name, "sub_secret": "sub_hidden"}
- for s in item.new_multi
- ],
- "secret": "hidden_v2_to_v1",
- }
- result.append(converted)
- return result
-
-
-@app.post("/v2-to-v1/list-to-item")
-def handle_v2_list_to_v1_item(data: list[NewItem]) -> Item:
- if data:
- item = data[0]
- return Item(
- title=item.new_title,
- size=item.new_size,
- description=item.new_description,
- sub=SubItem(name=item.new_sub.new_sub_name),
- multi=[SubItem(name=s.new_sub_name) for s in item.new_multi],
- )
- return Item(title="", size=0, sub=SubItem(name=""))
-
-
-client = TestClient(app)
-
-
-def test_v1_to_v2_item():
- response = client.post(
- "/v1-to-v2/item",
- json={
- "title": "Old Item",
- "size": 100,
- "description": "V1 description",
- "sub": {"name": "V1 Sub"},
- "multi": [{"name": "M1"}, {"name": "M2"}],
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "new_title": "Old Item",
- "new_size": 100,
- "new_description": "V1 description",
- "new_sub": {"new_sub_name": "V1 Sub"},
- "new_multi": [{"new_sub_name": "M1"}, {"new_sub_name": "M2"}],
- }
-
-
-def test_v1_to_v2_item_minimal():
- response = client.post(
- "/v1-to-v2/item",
- json={"title": "Minimal", "size": 50, "sub": {"name": "MinSub"}},
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "new_title": "Minimal",
- "new_size": 50,
- "new_description": None,
- "new_sub": {"new_sub_name": "MinSub"},
- "new_multi": [],
- }
-
-
-def test_v1_to_v2_item_filter():
- response = client.post(
- "/v1-to-v2/item-filter",
- json={
- "title": "Filtered Item",
- "size": 50,
- "sub": {"name": "Sub"},
- "multi": [{"name": "Multi1"}],
- },
- )
- assert response.status_code == 200, response.text
- result = response.json()
- assert result == snapshot(
- {
- "new_title": "Filtered Item",
- "new_size": 50,
- "new_description": None,
- "new_sub": {"new_sub_name": "Sub"},
- "new_multi": [{"new_sub_name": "Multi1"}],
- }
- )
- # Verify secret fields are filtered out
- assert "secret" not in result
- assert "new_sub_secret" not in result["new_sub"]
- assert "new_sub_secret" not in result["new_multi"][0]
-
-
-def test_v2_to_v1_item():
- response = client.post(
- "/v2-to-v1/item",
- json={
- "new_title": "New Item",
- "new_size": 200,
- "new_description": "V2 description",
- "new_sub": {"new_sub_name": "V2 Sub"},
- "new_multi": [{"new_sub_name": "N1"}, {"new_sub_name": "N2"}],
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "title": "New Item",
- "size": 200,
- "description": "V2 description",
- "sub": {"name": "V2 Sub"},
- "multi": [{"name": "N1"}, {"name": "N2"}],
- }
-
-
-def test_v2_to_v1_item_minimal():
- response = client.post(
- "/v2-to-v1/item",
- json={
- "new_title": "MinimalNew",
- "new_size": 75,
- "new_sub": {"new_sub_name": "MinNewSub"},
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "title": "MinimalNew",
- "size": 75,
- "description": None,
- "sub": {"name": "MinNewSub"},
- "multi": [],
- }
-
-
-def test_v2_to_v1_item_filter():
- response = client.post(
- "/v2-to-v1/item-filter",
- json={
- "new_title": "Filtered New",
- "new_size": 75,
- "new_sub": {"new_sub_name": "NewSub"},
- "new_multi": [],
- },
- )
- assert response.status_code == 200, response.text
- result = response.json()
- assert result == snapshot(
- {
- "title": "Filtered New",
- "size": 75,
- "description": None,
- "sub": {"name": "NewSub"},
- "multi": [],
- }
- )
- # Verify secret fields are filtered out
- assert "secret" not in result
- assert "sub_secret" not in result["sub"]
-
-
-def test_v1_item_to_v2_list():
- response = client.post(
- "/v1-to-v2/item-to-list",
- json={
- "title": "Single to List",
- "size": 150,
- "description": "Convert to list",
- "sub": {"name": "Sub1"},
- "multi": [],
- },
- )
- assert response.status_code == 200, response.text
- result = response.json()
- assert result == [
- {
- "new_title": "Single to List",
- "new_size": 150,
- "new_description": "Convert to list",
- "new_sub": {"new_sub_name": "Sub1"},
- "new_multi": [],
- },
- {
- "new_title": "Single to List",
- "new_size": 150,
- "new_description": "Convert to list",
- "new_sub": {"new_sub_name": "Sub1"},
- "new_multi": [],
- },
- ]
-
-
-def test_v1_list_to_v2_list():
- response = client.post(
- "/v1-to-v2/list-to-list",
- json=[
- {"title": "Item1", "size": 10, "sub": {"name": "Sub1"}},
- {
- "title": "Item2",
- "size": 20,
- "description": "Second item",
- "sub": {"name": "Sub2"},
- "multi": [{"name": "M1"}, {"name": "M2"}],
- },
- {"title": "Item3", "size": 30, "sub": {"name": "Sub3"}},
- ],
- )
- assert response.status_code == 200, response.text
- assert response.json() == [
- {
- "new_title": "Item1",
- "new_size": 10,
- "new_description": None,
- "new_sub": {"new_sub_name": "Sub1"},
- "new_multi": [],
- },
- {
- "new_title": "Item2",
- "new_size": 20,
- "new_description": "Second item",
- "new_sub": {"new_sub_name": "Sub2"},
- "new_multi": [{"new_sub_name": "M1"}, {"new_sub_name": "M2"}],
- },
- {
- "new_title": "Item3",
- "new_size": 30,
- "new_description": None,
- "new_sub": {"new_sub_name": "Sub3"},
- "new_multi": [],
- },
- ]
-
-
-def test_v1_list_to_v2_list_filter():
- response = client.post(
- "/v1-to-v2/list-to-list-filter",
- json=[{"title": "FilterMe", "size": 30, "sub": {"name": "SubF"}}],
- )
- assert response.status_code == 200, response.text
- result = response.json()
- assert result == snapshot(
- [
- {
- "new_title": "FilterMe",
- "new_size": 30,
- "new_description": None,
- "new_sub": {"new_sub_name": "SubF"},
- "new_multi": [],
- }
- ]
- )
- # Verify secret fields are filtered out
- assert "secret" not in result[0]
- assert "new_sub_secret" not in result[0]["new_sub"]
-
-
-def test_v1_list_to_v2_item():
- response = client.post(
- "/v1-to-v2/list-to-item",
- json=[
- {"title": "First", "size": 100, "sub": {"name": "FirstSub"}},
- {"title": "Second", "size": 200, "sub": {"name": "SecondSub"}},
- ],
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "new_title": "First",
- "new_size": 100,
- "new_description": None,
- "new_sub": {"new_sub_name": "FirstSub"},
- "new_multi": [],
- }
-
-
-def test_v1_list_to_v2_item_empty():
- response = client.post("/v1-to-v2/list-to-item", json=[])
- assert response.status_code == 200, response.text
- assert response.json() == {
- "new_title": "",
- "new_size": 0,
- "new_description": None,
- "new_sub": {"new_sub_name": ""},
- "new_multi": [],
- }
-
-
-def test_v2_item_to_v1_list():
- response = client.post(
- "/v2-to-v1/item-to-list",
- json={
- "new_title": "Single New",
- "new_size": 250,
- "new_description": "New to list",
- "new_sub": {"new_sub_name": "NewSub"},
- "new_multi": [],
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() == [
- {
- "title": "Single New",
- "size": 250,
- "description": "New to list",
- "sub": {"name": "NewSub"},
- "multi": [],
- },
- {
- "title": "Single New",
- "size": 250,
- "description": "New to list",
- "sub": {"name": "NewSub"},
- "multi": [],
- },
- ]
-
-
-def test_v2_list_to_v1_list():
- response = client.post(
- "/v2-to-v1/list-to-list",
- json=[
- {"new_title": "New1", "new_size": 15, "new_sub": {"new_sub_name": "NS1"}},
- {
- "new_title": "New2",
- "new_size": 25,
- "new_description": "Second new",
- "new_sub": {"new_sub_name": "NS2"},
- "new_multi": [{"new_sub_name": "NM1"}],
- },
- ],
- )
- assert response.status_code == 200, response.text
- assert response.json() == [
- {
- "title": "New1",
- "size": 15,
- "description": None,
- "sub": {"name": "NS1"},
- "multi": [],
- },
- {
- "title": "New2",
- "size": 25,
- "description": "Second new",
- "sub": {"name": "NS2"},
- "multi": [{"name": "NM1"}],
- },
- ]
-
-
-def test_v2_list_to_v1_list_filter():
- response = client.post(
- "/v2-to-v1/list-to-list-filter",
- json=[
- {
- "new_title": "FilterNew",
- "new_size": 35,
- "new_sub": {"new_sub_name": "NSF"},
- }
- ],
- )
- assert response.status_code == 200, response.text
- result = response.json()
- assert result == snapshot(
- [
- {
- "title": "FilterNew",
- "size": 35,
- "description": None,
- "sub": {"name": "NSF"},
- "multi": [],
- }
- ]
- )
- # Verify secret fields are filtered out
- assert "secret" not in result[0]
- assert "sub_secret" not in result[0]["sub"]
-
-
-def test_v2_list_to_v1_item():
- response = client.post(
- "/v2-to-v1/list-to-item",
- json=[
- {
- "new_title": "FirstNew",
- "new_size": 300,
- "new_sub": {"new_sub_name": "FNS"},
- },
- {
- "new_title": "SecondNew",
- "new_size": 400,
- "new_sub": {"new_sub_name": "SNS"},
- },
- ],
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "title": "FirstNew",
- "size": 300,
- "description": None,
- "sub": {"name": "FNS"},
- "multi": [],
- }
-
-
-def test_v2_list_to_v1_item_empty():
- response = client.post("/v2-to-v1/list-to-item", json=[])
- assert response.status_code == 200, response.text
- assert response.json() == {
- "title": "",
- "size": 0,
- "description": None,
- "sub": {"name": ""},
- "multi": [],
- }
-
-
-def test_v1_to_v2_validation_error():
- response = client.post("/v1-to-v2/item", json={"title": "Missing fields"})
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", "size"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "sub"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
-
-
-def test_v1_to_v2_nested_validation_error():
- response = client.post(
- "/v1-to-v2/item",
- json={"title": "Bad sub", "size": 100, "sub": {"wrong_field": "value"}},
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", "sub", "name"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
-
-
-def test_v1_to_v2_type_validation_error():
- response = client.post(
- "/v1-to-v2/item",
- json={"title": "Bad type", "size": "not_a_number", "sub": {"name": "Sub"}},
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", "size"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
-
-
-def test_v2_to_v1_validation_error():
- response = client.post(
- "/v2-to-v1/item",
- json={"new_title": "Missing fields"},
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": pydantic_snapshot(
- v2=snapshot(
- [
- {
- "type": "missing",
- "loc": ["body", "new_size"],
- "msg": "Field required",
- "input": {"new_title": "Missing fields"},
- },
- {
- "type": "missing",
- "loc": ["body", "new_sub"],
- "msg": "Field required",
- "input": {"new_title": "Missing fields"},
- },
- ]
- ),
- v1=snapshot(
- [
- {
- "loc": ["body", "new_size"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "new_sub"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- ),
- )
- }
- )
-
-
-def test_v2_to_v1_nested_validation_error():
- response = client.post(
- "/v2-to-v1/item",
- json={
- "new_title": "Bad sub",
- "new_size": 200,
- "new_sub": {"wrong_field": "value"},
- },
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- pydantic_snapshot(
- v2=snapshot(
- {
- "type": "missing",
- "loc": ["body", "new_sub", "new_sub_name"],
- "msg": "Field required",
- "input": {"wrong_field": "value"},
- }
- ),
- v1=snapshot(
- {
- "loc": ["body", "new_sub", "new_sub_name"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ),
- )
- ]
- }
- )
-
-
-def test_v1_list_validation_error():
- response = client.post(
- "/v1-to-v2/list-to-list",
- json=[
- {"title": "Valid", "size": 10, "sub": {"name": "S"}},
- {"title": "Invalid"},
- ],
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", 1, "size"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", 1, "sub"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
-
-
-def test_v2_list_validation_error():
- response = client.post(
- "/v2-to-v1/list-to-list",
- json=[
- {"new_title": "Valid", "new_size": 10, "new_sub": {"new_sub_name": "NS"}},
- {"new_title": "Invalid"},
- ],
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": pydantic_snapshot(
- v2=snapshot(
- [
- {
- "type": "missing",
- "loc": ["body", 1, "new_size"],
- "msg": "Field required",
- "input": {"new_title": "Invalid"},
- },
- {
- "type": "missing",
- "loc": ["body", 1, "new_sub"],
- "msg": "Field required",
- "input": {"new_title": "Invalid"},
- },
- ]
- ),
- v1=snapshot(
- [
- {
- "loc": ["body", 1, "new_size"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", 1, "new_sub"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- ),
- )
- }
- )
-
-
-def test_invalid_list_structure_v1():
- response = client.post(
- "/v1-to-v2/list-to-list",
- json={"title": "Not a list", "size": 100, "sub": {"name": "Sub"}},
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body"],
- "msg": "value is not a valid list",
- "type": "type_error.list",
- }
- ]
- }
- )
-
-
-def test_invalid_list_structure_v2():
- response = client.post(
- "/v2-to-v1/list-to-list",
- json={
- "new_title": "Not a list",
- "new_size": 100,
- "new_sub": {"new_sub_name": "Sub"},
- },
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": pydantic_snapshot(
- v2=snapshot(
- [
- {
- "type": "list_type",
- "loc": ["body"],
- "msg": "Input should be a valid list",
- "input": {
- "new_title": "Not a list",
- "new_size": 100,
- "new_sub": {"new_sub_name": "Sub"},
- },
- }
- ]
- ),
- v1=snapshot(
- [
- {
- "loc": ["body"],
- "msg": "value is not a valid list",
- "type": "type_error.list",
- }
- ]
- ),
- )
- }
- )
-
-
-def test_openapi_schema():
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == snapshot(
- {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/v1-to-v2/item": {
- "post": {
- "summary": "Handle V1 Item To V2",
- "operationId": "handle_v1_item_to_v2_v1_to_v2_item_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Item"
- }
- ],
- "title": "Data",
- }
- ),
- v1=snapshot(
- {"$ref": "#/components/schemas/Item"}
- ),
- )
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/NewItem"
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v1-to-v2/item-filter": {
- "post": {
- "summary": "Handle V1 Item To V2 Filter",
- "operationId": "handle_v1_item_to_v2_filter_v1_to_v2_item_filter_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Item"
- }
- ],
- "title": "Data",
- }
- ),
- v1=snapshot(
- {"$ref": "#/components/schemas/Item"}
- ),
- )
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/NewItem"
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/item": {
- "post": {
- "summary": "Handle V2 Item To V1",
- "operationId": "handle_v2_item_to_v1_v2_to_v1_item_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/NewItem"}
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/item-filter": {
- "post": {
- "summary": "Handle V2 Item To V1 Filter",
- "operationId": "handle_v2_item_to_v1_filter_v2_to_v1_item_filter_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/NewItem"}
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v1-to-v2/item-to-list": {
- "post": {
- "summary": "Handle V1 Item To V2 List",
- "operationId": "handle_v1_item_to_v2_list_v1_to_v2_item_to_list_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Item"
- }
- ],
- "title": "Data",
- }
- ),
- v1=snapshot(
- {"$ref": "#/components/schemas/Item"}
- ),
- )
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/NewItem"
- },
- "type": "array",
- "title": "Response Handle V1 Item To V2 List V1 To V2 Item To List Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v1-to-v2/list-to-list": {
- "post": {
- "summary": "Handle V1 List To V2 List",
- "operationId": "handle_v1_list_to_v2_list_v1_to_v2_list_to_list_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": {"$ref": "#/components/schemas/Item"},
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/NewItem"
- },
- "type": "array",
- "title": "Response Handle V1 List To V2 List V1 To V2 List To List Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v1-to-v2/list-to-list-filter": {
- "post": {
- "summary": "Handle V1 List To V2 List Filter",
- "operationId": "handle_v1_list_to_v2_list_filter_v1_to_v2_list_to_list_filter_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": {"$ref": "#/components/schemas/Item"},
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/NewItem"
- },
- "type": "array",
- "title": "Response Handle V1 List To V2 List Filter V1 To V2 List To List Filter Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v1-to-v2/list-to-item": {
- "post": {
- "summary": "Handle V1 List To V2 Item",
- "operationId": "handle_v1_list_to_v2_item_v1_to_v2_list_to_item_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": {"$ref": "#/components/schemas/Item"},
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/NewItem"
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/item-to-list": {
- "post": {
- "summary": "Handle V2 Item To V1 List",
- "operationId": "handle_v2_item_to_v1_list_v2_to_v1_item_to_list_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/NewItem"}
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/Item"
- },
- "type": "array",
- "title": "Response Handle V2 Item To V1 List V2 To V1 Item To List Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/list-to-list": {
- "post": {
- "summary": "Handle V2 List To V1 List",
- "operationId": "handle_v2_list_to_v1_list_v2_to_v1_list_to_list_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/NewItem"
- },
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/Item"
- },
- "type": "array",
- "title": "Response Handle V2 List To V1 List V2 To V1 List To List Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/list-to-list-filter": {
- "post": {
- "summary": "Handle V2 List To V1 List Filter",
- "operationId": "handle_v2_list_to_v1_list_filter_v2_to_v1_list_to_list_filter_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/NewItem"
- },
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/Item"
- },
- "type": "array",
- "title": "Response Handle V2 List To V1 List Filter V2 To V1 List To List Filter Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/list-to-item": {
- "post": {
- "summary": "Handle V2 List To V1 Item",
- "operationId": "handle_v2_list_to_v1_item_v2_to_v1_list_to_item_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/NewItem"
- },
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- },
- "components": {
- "schemas": {
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {
- "$ref": "#/components/schemas/ValidationError"
- },
- "type": "array",
- "title": "Detail",
- }
- },
- "type": "object",
- "title": "HTTPValidationError",
- },
- "Item": {
- "properties": {
- "title": {"type": "string", "title": "Title"},
- "size": {"type": "integer", "title": "Size"},
- "description": {"type": "string", "title": "Description"},
- "sub": {"$ref": "#/components/schemas/SubItem"},
- "multi": {
- "items": {"$ref": "#/components/schemas/SubItem"},
- "type": "array",
- "title": "Multi",
- "default": [],
- },
- },
- "type": "object",
- "required": ["title", "size", "sub"],
- "title": "Item",
- },
- "NewItem": {
- "properties": {
- "new_title": {"type": "string", "title": "New Title"},
- "new_size": {"type": "integer", "title": "New Size"},
- "new_description": pydantic_snapshot(
- v2=snapshot(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "New Description",
- }
- ),
- v1=snapshot(
- {"type": "string", "title": "New Description"}
- ),
- ),
- "new_sub": {"$ref": "#/components/schemas/NewSubItem"},
- "new_multi": {
- "items": {"$ref": "#/components/schemas/NewSubItem"},
- "type": "array",
- "title": "New Multi",
- "default": [],
- },
- },
- "type": "object",
- "required": ["new_title", "new_size", "new_sub"],
- "title": "NewItem",
- },
- "NewSubItem": {
- "properties": {
- "new_sub_name": {"type": "string", "title": "New Sub Name"}
- },
- "type": "object",
- "required": ["new_sub_name"],
- "title": "NewSubItem",
- },
- "SubItem": {
- "properties": {"name": {"type": "string", "title": "Name"}},
- "type": "object",
- "required": ["name"],
- "title": "SubItem",
- },
- "ValidationError": {
- "properties": {
- "loc": {
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- "type": "array",
- "title": "Location",
- },
- "msg": {"type": "string", "title": "Message"},
- "type": {"type": "string", "title": "Error Type"},
- },
- "type": "object",
- "required": ["loc", "msg", "type"],
- "title": "ValidationError",
- },
- }
- },
- }
- )
diff --git a/tests/test_pydantic_v1_v2_multifile/main.py b/tests/test_pydantic_v1_v2_multifile/main.py
deleted file mode 100644
index 9397368abf..0000000000
--- a/tests/test_pydantic_v1_v2_multifile/main.py
+++ /dev/null
@@ -1,140 +0,0 @@
-from fastapi import FastAPI
-
-from . import modelsv1, modelsv2, modelsv2b
-
-app = FastAPI()
-
-
-@app.post("/v1-to-v2/item")
-def handle_v1_item_to_v2(data: modelsv1.Item) -> modelsv2.Item:
- return modelsv2.Item(
- new_title=data.title,
- new_size=data.size,
- new_description=data.description,
- new_sub=modelsv2.SubItem(new_sub_name=data.sub.name),
- new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in data.multi],
- )
-
-
-@app.post("/v2-to-v1/item")
-def handle_v2_item_to_v1(data: modelsv2.Item) -> modelsv1.Item:
- return modelsv1.Item(
- title=data.new_title,
- size=data.new_size,
- description=data.new_description,
- sub=modelsv1.SubItem(name=data.new_sub.new_sub_name),
- multi=[modelsv1.SubItem(name=s.new_sub_name) for s in data.new_multi],
- )
-
-
-@app.post("/v1-to-v2/item-to-list")
-def handle_v1_item_to_v2_list(data: modelsv1.Item) -> list[modelsv2.Item]:
- converted = modelsv2.Item(
- new_title=data.title,
- new_size=data.size,
- new_description=data.description,
- new_sub=modelsv2.SubItem(new_sub_name=data.sub.name),
- new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in data.multi],
- )
- return [converted, converted]
-
-
-@app.post("/v1-to-v2/list-to-list")
-def handle_v1_list_to_v2_list(data: list[modelsv1.Item]) -> list[modelsv2.Item]:
- result = []
- for item in data:
- result.append(
- modelsv2.Item(
- new_title=item.title,
- new_size=item.size,
- new_description=item.description,
- new_sub=modelsv2.SubItem(new_sub_name=item.sub.name),
- new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in item.multi],
- )
- )
- return result
-
-
-@app.post("/v1-to-v2/list-to-item")
-def handle_v1_list_to_v2_item(data: list[modelsv1.Item]) -> modelsv2.Item:
- if data:
- item = data[0]
- return modelsv2.Item(
- new_title=item.title,
- new_size=item.size,
- new_description=item.description,
- new_sub=modelsv2.SubItem(new_sub_name=item.sub.name),
- new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in item.multi],
- )
- return modelsv2.Item(
- new_title="", new_size=0, new_sub=modelsv2.SubItem(new_sub_name="")
- )
-
-
-@app.post("/v2-to-v1/item-to-list")
-def handle_v2_item_to_v1_list(data: modelsv2.Item) -> list[modelsv1.Item]:
- converted = modelsv1.Item(
- title=data.new_title,
- size=data.new_size,
- description=data.new_description,
- sub=modelsv1.SubItem(name=data.new_sub.new_sub_name),
- multi=[modelsv1.SubItem(name=s.new_sub_name) for s in data.new_multi],
- )
- return [converted, converted]
-
-
-@app.post("/v2-to-v1/list-to-list")
-def handle_v2_list_to_v1_list(data: list[modelsv2.Item]) -> list[modelsv1.Item]:
- result = []
- for item in data:
- result.append(
- modelsv1.Item(
- title=item.new_title,
- size=item.new_size,
- description=item.new_description,
- sub=modelsv1.SubItem(name=item.new_sub.new_sub_name),
- multi=[modelsv1.SubItem(name=s.new_sub_name) for s in item.new_multi],
- )
- )
- return result
-
-
-@app.post("/v2-to-v1/list-to-item")
-def handle_v2_list_to_v1_item(data: list[modelsv2.Item]) -> modelsv1.Item:
- if data:
- item = data[0]
- return modelsv1.Item(
- title=item.new_title,
- size=item.new_size,
- description=item.new_description,
- sub=modelsv1.SubItem(name=item.new_sub.new_sub_name),
- multi=[modelsv1.SubItem(name=s.new_sub_name) for s in item.new_multi],
- )
- return modelsv1.Item(title="", size=0, sub=modelsv1.SubItem(name=""))
-
-
-@app.post("/v2-to-v1/same-name")
-def handle_v2_same_name_to_v1(
- item1: modelsv2.Item, item2: modelsv2b.Item
-) -> modelsv1.Item:
- return modelsv1.Item(
- title=item1.new_title,
- size=item2.dup_size,
- description=item1.new_description,
- sub=modelsv1.SubItem(name=item1.new_sub.new_sub_name),
- multi=[modelsv1.SubItem(name=s.dup_sub_name) for s in item2.dup_multi],
- )
-
-
-@app.post("/v2-to-v1/list-of-items-to-list-of-items")
-def handle_v2_items_in_list_to_v1_item_in_list(
- data1: list[modelsv2.ItemInList], data2: list[modelsv2b.ItemInList]
-) -> list[modelsv1.ItemInList]:
- result = []
- item1 = data1[0]
- item2 = data2[0]
- result = [
- modelsv1.ItemInList(name1=item1.name2),
- modelsv1.ItemInList(name1=item2.dup_name2),
- ]
- return result
diff --git a/tests/test_pydantic_v1_v2_multifile/modelsv1.py b/tests/test_pydantic_v1_v2_multifile/modelsv1.py
deleted file mode 100644
index 0cc8de4559..0000000000
--- a/tests/test_pydantic_v1_v2_multifile/modelsv1.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from typing import Union
-
-from fastapi._compat.v1 import BaseModel
-
-
-class SubItem(BaseModel):
- name: str
-
-
-class Item(BaseModel):
- title: str
- size: int
- description: Union[str, None] = None
- sub: SubItem
- multi: list[SubItem] = []
-
-
-class ItemInList(BaseModel):
- name1: str
diff --git a/tests/test_pydantic_v1_v2_multifile/modelsv2.py b/tests/test_pydantic_v1_v2_multifile/modelsv2.py
deleted file mode 100644
index d80b77e103..0000000000
--- a/tests/test_pydantic_v1_v2_multifile/modelsv2.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from typing import Union
-
-from pydantic import BaseModel
-
-
-class SubItem(BaseModel):
- new_sub_name: str
-
-
-class Item(BaseModel):
- new_title: str
- new_size: int
- new_description: Union[str, None] = None
- new_sub: SubItem
- new_multi: list[SubItem] = []
-
-
-class ItemInList(BaseModel):
- name2: str
diff --git a/tests/test_pydantic_v1_v2_multifile/modelsv2b.py b/tests/test_pydantic_v1_v2_multifile/modelsv2b.py
deleted file mode 100644
index e992bea2e1..0000000000
--- a/tests/test_pydantic_v1_v2_multifile/modelsv2b.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from typing import Union
-
-from pydantic import BaseModel
-
-
-class SubItem(BaseModel):
- dup_sub_name: str
-
-
-class Item(BaseModel):
- dup_title: str
- dup_size: int
- dup_description: Union[str, None] = None
- dup_sub: SubItem
- dup_multi: list[SubItem] = []
-
-
-class ItemInList(BaseModel):
- dup_name2: str
diff --git a/tests/test_pydantic_v1_v2_multifile/test_multifile.py b/tests/test_pydantic_v1_v2_multifile/test_multifile.py
deleted file mode 100644
index e66d102fb3..0000000000
--- a/tests/test_pydantic_v1_v2_multifile/test_multifile.py
+++ /dev/null
@@ -1,1226 +0,0 @@
-import sys
-
-from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314
-
-if sys.version_info >= (3, 14):
- skip_module_if_py_gte_314()
-
-from fastapi.testclient import TestClient
-from inline_snapshot import snapshot
-
-from .main import app
-
-client = TestClient(app)
-
-
-def test_v1_to_v2_item():
- response = client.post(
- "/v1-to-v2/item",
- json={"title": "Test", "size": 10, "sub": {"name": "SubTest"}},
- )
- assert response.status_code == 200
- assert response.json() == {
- "new_title": "Test",
- "new_size": 10,
- "new_description": None,
- "new_sub": {"new_sub_name": "SubTest"},
- "new_multi": [],
- }
-
-
-def test_v2_to_v1_item():
- response = client.post(
- "/v2-to-v1/item",
- json={
- "new_title": "NewTest",
- "new_size": 20,
- "new_sub": {"new_sub_name": "NewSubTest"},
- },
- )
- assert response.status_code == 200
- assert response.json() == {
- "title": "NewTest",
- "size": 20,
- "description": None,
- "sub": {"name": "NewSubTest"},
- "multi": [],
- }
-
-
-def test_v1_to_v2_item_to_list():
- response = client.post(
- "/v1-to-v2/item-to-list",
- json={"title": "ListTest", "size": 30, "sub": {"name": "SubListTest"}},
- )
- assert response.status_code == 200
- assert response.json() == [
- {
- "new_title": "ListTest",
- "new_size": 30,
- "new_description": None,
- "new_sub": {"new_sub_name": "SubListTest"},
- "new_multi": [],
- },
- {
- "new_title": "ListTest",
- "new_size": 30,
- "new_description": None,
- "new_sub": {"new_sub_name": "SubListTest"},
- "new_multi": [],
- },
- ]
-
-
-def test_v1_to_v2_list_to_list():
- response = client.post(
- "/v1-to-v2/list-to-list",
- json=[
- {"title": "Item1", "size": 40, "sub": {"name": "Sub1"}},
- {"title": "Item2", "size": 50, "sub": {"name": "Sub2"}},
- ],
- )
- assert response.status_code == 200
- assert response.json() == [
- {
- "new_title": "Item1",
- "new_size": 40,
- "new_description": None,
- "new_sub": {"new_sub_name": "Sub1"},
- "new_multi": [],
- },
- {
- "new_title": "Item2",
- "new_size": 50,
- "new_description": None,
- "new_sub": {"new_sub_name": "Sub2"},
- "new_multi": [],
- },
- ]
-
-
-def test_v1_to_v2_list_to_item():
- response = client.post(
- "/v1-to-v2/list-to-item",
- json=[
- {"title": "FirstItem", "size": 60, "sub": {"name": "FirstSub"}},
- {"title": "SecondItem", "size": 70, "sub": {"name": "SecondSub"}},
- ],
- )
- assert response.status_code == 200
- assert response.json() == {
- "new_title": "FirstItem",
- "new_size": 60,
- "new_description": None,
- "new_sub": {"new_sub_name": "FirstSub"},
- "new_multi": [],
- }
-
-
-def test_v2_to_v1_item_to_list():
- response = client.post(
- "/v2-to-v1/item-to-list",
- json={
- "new_title": "ListNew",
- "new_size": 80,
- "new_sub": {"new_sub_name": "SubListNew"},
- },
- )
- assert response.status_code == 200
- assert response.json() == [
- {
- "title": "ListNew",
- "size": 80,
- "description": None,
- "sub": {"name": "SubListNew"},
- "multi": [],
- },
- {
- "title": "ListNew",
- "size": 80,
- "description": None,
- "sub": {"name": "SubListNew"},
- "multi": [],
- },
- ]
-
-
-def test_v2_to_v1_list_to_list():
- response = client.post(
- "/v2-to-v1/list-to-list",
- json=[
- {
- "new_title": "New1",
- "new_size": 90,
- "new_sub": {"new_sub_name": "NewSub1"},
- },
- {
- "new_title": "New2",
- "new_size": 100,
- "new_sub": {"new_sub_name": "NewSub2"},
- },
- ],
- )
- assert response.status_code == 200
- assert response.json() == [
- {
- "title": "New1",
- "size": 90,
- "description": None,
- "sub": {"name": "NewSub1"},
- "multi": [],
- },
- {
- "title": "New2",
- "size": 100,
- "description": None,
- "sub": {"name": "NewSub2"},
- "multi": [],
- },
- ]
-
-
-def test_v2_to_v1_list_to_item():
- response = client.post(
- "/v2-to-v1/list-to-item",
- json=[
- {
- "new_title": "FirstNew",
- "new_size": 110,
- "new_sub": {"new_sub_name": "FirstNewSub"},
- },
- {
- "new_title": "SecondNew",
- "new_size": 120,
- "new_sub": {"new_sub_name": "SecondNewSub"},
- },
- ],
- )
- assert response.status_code == 200
- assert response.json() == {
- "title": "FirstNew",
- "size": 110,
- "description": None,
- "sub": {"name": "FirstNewSub"},
- "multi": [],
- }
-
-
-def test_v1_to_v2_list_to_item_empty():
- response = client.post("/v1-to-v2/list-to-item", json=[])
- assert response.status_code == 200
- assert response.json() == {
- "new_title": "",
- "new_size": 0,
- "new_description": None,
- "new_sub": {"new_sub_name": ""},
- "new_multi": [],
- }
-
-
-def test_v2_to_v1_list_to_item_empty():
- response = client.post("/v2-to-v1/list-to-item", json=[])
- assert response.status_code == 200
- assert response.json() == {
- "title": "",
- "size": 0,
- "description": None,
- "sub": {"name": ""},
- "multi": [],
- }
-
-
-def test_v2_same_name_to_v1():
- response = client.post(
- "/v2-to-v1/same-name",
- json={
- "item1": {
- "new_title": "Title1",
- "new_size": 100,
- "new_description": "Description1",
- "new_sub": {"new_sub_name": "Sub1"},
- "new_multi": [{"new_sub_name": "Multi1"}],
- },
- "item2": {
- "dup_title": "Title2",
- "dup_size": 200,
- "dup_description": "Description2",
- "dup_sub": {"dup_sub_name": "Sub2"},
- "dup_multi": [
- {"dup_sub_name": "Multi2a"},
- {"dup_sub_name": "Multi2b"},
- ],
- },
- },
- )
- assert response.status_code == 200
- assert response.json() == {
- "title": "Title1",
- "size": 200,
- "description": "Description1",
- "sub": {"name": "Sub1"},
- "multi": [{"name": "Multi2a"}, {"name": "Multi2b"}],
- }
-
-
-def test_v2_items_in_list_to_v1_item_in_list():
- response = client.post(
- "/v2-to-v1/list-of-items-to-list-of-items",
- json={
- "data1": [{"name2": "Item1"}, {"name2": "Item2"}],
- "data2": [{"dup_name2": "Item3"}, {"dup_name2": "Item4"}],
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() == [
- {"name1": "Item1"},
- {"name1": "Item3"},
- ]
-
-
-def test_openapi_schema():
- response = client.get("/openapi.json")
- assert response.status_code == 200
- assert response.json() == snapshot(
- {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/v1-to-v2/item": {
- "post": {
- "summary": "Handle V1 Item To V2",
- "operationId": "handle_v1_item_to_v2_v1_to_v2_item_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
- }
- ],
- "title": "Data",
- }
- ),
- v1=snapshot(
- {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
- }
- ),
- )
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item"
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/item": {
- "post": {
- "summary": "Handle V2 Item To V1",
- "operationId": "handle_v2_item_to_v1_v2_to_v1_item_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input"
- }
- ),
- v1=snapshot(
- {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item"
- }
- ),
- ),
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v1-to-v2/item-to-list": {
- "post": {
- "summary": "Handle V1 Item To V2 List",
- "operationId": "handle_v1_item_to_v2_list_v1_to_v2_item_to_list_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
- }
- ],
- "title": "Data",
- }
- ),
- v1=snapshot(
- {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
- }
- ),
- )
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item"
- },
- "type": "array",
- "title": "Response Handle V1 Item To V2 List V1 To V2 Item To List Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v1-to-v2/list-to-list": {
- "post": {
- "summary": "Handle V1 List To V2 List",
- "operationId": "handle_v1_list_to_v2_list_v1_to_v2_list_to_list_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
- },
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item"
- },
- "type": "array",
- "title": "Response Handle V1 List To V2 List V1 To V2 List To List Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v1-to-v2/list-to-item": {
- "post": {
- "summary": "Handle V1 List To V2 Item",
- "operationId": "handle_v1_list_to_v2_item_v1_to_v2_list_to_item_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
- },
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item"
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/item-to-list": {
- "post": {
- "summary": "Handle V2 Item To V1 List",
- "operationId": "handle_v2_item_to_v1_list_v2_to_v1_item_to_list_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input"
- }
- ),
- v1=snapshot(
- {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item"
- }
- ),
- ),
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
- },
- "type": "array",
- "title": "Response Handle V2 Item To V1 List V2 To V1 Item To List Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/list-to-list": {
- "post": {
- "summary": "Handle V2 List To V1 List",
- "operationId": "handle_v2_list_to_v1_list_v2_to_v1_list_to_list_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": pydantic_snapshot(
- v2=snapshot(
- {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input"
- }
- ),
- v1=snapshot(
- {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item"
- }
- ),
- ),
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
- },
- "type": "array",
- "title": "Response Handle V2 List To V1 List V2 To V1 List To List Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/list-to-item": {
- "post": {
- "summary": "Handle V2 List To V1 Item",
- "operationId": "handle_v2_list_to_v1_item_v2_to_v1_list_to_item_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": pydantic_snapshot(
- v2=snapshot(
- {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input"
- }
- ),
- v1=snapshot(
- {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item"
- }
- ),
- ),
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/same-name": {
- "post": {
- "summary": "Handle V2 Same Name To V1",
- "operationId": "handle_v2_same_name_to_v1_v2_to_v1_same_name_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post"
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/list-of-items-to-list-of-items": {
- "post": {
- "summary": "Handle V2 Items In List To V1 Item In List",
- "operationId": "handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post"
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__ItemInList"
- },
- "type": "array",
- "title": "Response Handle V2 Items In List To V1 Item In List V2 To V1 List Of Items To List Of Items Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- },
- "components": {
- "schemas": pydantic_snapshot(
- v1=snapshot(
- {
- "Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post": {
- "properties": {
- "data1": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__ItemInList"
- },
- "type": "array",
- "title": "Data1",
- },
- "data2": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__ItemInList"
- },
- "type": "array",
- "title": "Data2",
- },
- },
- "type": "object",
- "required": ["data1", "data2"],
- "title": "Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post",
- },
- "Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post": {
- "properties": {
- "item1": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item"
- },
- "item2": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__Item"
- },
- },
- "type": "object",
- "required": ["item1", "item2"],
- "title": "Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post",
- },
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {
- "$ref": "#/components/schemas/ValidationError"
- },
- "type": "array",
- "title": "Detail",
- }
- },
- "type": "object",
- "title": "HTTPValidationError",
- },
- "ValidationError": {
- "properties": {
- "loc": {
- "items": {
- "anyOf": [
- {"type": "string"},
- {"type": "integer"},
- ]
- },
- "type": "array",
- "title": "Location",
- },
- "msg": {"type": "string", "title": "Message"},
- "type": {"type": "string", "title": "Error Type"},
- },
- "type": "object",
- "required": ["loc", "msg", "type"],
- "title": "ValidationError",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv1__Item": {
- "properties": {
- "title": {"type": "string", "title": "Title"},
- "size": {"type": "integer", "title": "Size"},
- "description": {
- "type": "string",
- "title": "Description",
- },
- "sub": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem"
- },
- "multi": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem"
- },
- "type": "array",
- "title": "Multi",
- "default": [],
- },
- },
- "type": "object",
- "required": ["title", "size", "sub"],
- "title": "Item",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv1__ItemInList": {
- "properties": {
- "name1": {"type": "string", "title": "Name1"}
- },
- "type": "object",
- "required": ["name1"],
- "title": "ItemInList",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem": {
- "properties": {
- "name": {"type": "string", "title": "Name"}
- },
- "type": "object",
- "required": ["name"],
- "title": "SubItem",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv2__Item": {
- "properties": {
- "new_title": {
- "type": "string",
- "title": "New Title",
- },
- "new_size": {
- "type": "integer",
- "title": "New Size",
- },
- "new_description": {
- "type": "string",
- "title": "New Description",
- },
- "new_sub": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem"
- },
- "new_multi": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem"
- },
- "type": "array",
- "title": "New Multi",
- "default": [],
- },
- },
- "type": "object",
- "required": ["new_title", "new_size", "new_sub"],
- "title": "Item",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv2__ItemInList": {
- "properties": {
- "name2": {"type": "string", "title": "Name2"}
- },
- "type": "object",
- "required": ["name2"],
- "title": "ItemInList",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem": {
- "properties": {
- "new_sub_name": {
- "type": "string",
- "title": "New Sub Name",
- }
- },
- "type": "object",
- "required": ["new_sub_name"],
- "title": "SubItem",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv2b__Item": {
- "properties": {
- "dup_title": {
- "type": "string",
- "title": "Dup Title",
- },
- "dup_size": {
- "type": "integer",
- "title": "Dup Size",
- },
- "dup_description": {
- "type": "string",
- "title": "Dup Description",
- },
- "dup_sub": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem"
- },
- "dup_multi": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem"
- },
- "type": "array",
- "title": "Dup Multi",
- "default": [],
- },
- },
- "type": "object",
- "required": ["dup_title", "dup_size", "dup_sub"],
- "title": "Item",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv2b__ItemInList": {
- "properties": {
- "dup_name2": {
- "type": "string",
- "title": "Dup Name2",
- }
- },
- "type": "object",
- "required": ["dup_name2"],
- "title": "ItemInList",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem": {
- "properties": {
- "dup_sub_name": {
- "type": "string",
- "title": "Dup Sub Name",
- }
- },
- "type": "object",
- "required": ["dup_sub_name"],
- "title": "SubItem",
- },
- }
- ),
- v2=snapshot(
- {
- "Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post": {
- "properties": {
- "data1": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__ItemInList"
- },
- "type": "array",
- "title": "Data1",
- },
- "data2": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__ItemInList"
- },
- "type": "array",
- "title": "Data2",
- },
- },
- "type": "object",
- "required": ["data1", "data2"],
- "title": "Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post",
- },
- "Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post": {
- "properties": {
- "item1": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input"
- },
- "item2": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__Item"
- },
- },
- "type": "object",
- "required": ["item1", "item2"],
- "title": "Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post",
- },
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {
- "$ref": "#/components/schemas/ValidationError"
- },
- "type": "array",
- "title": "Detail",
- }
- },
- "type": "object",
- "title": "HTTPValidationError",
- },
- "ValidationError": {
- "properties": {
- "loc": {
- "items": {
- "anyOf": [
- {"type": "string"},
- {"type": "integer"},
- ]
- },
- "type": "array",
- "title": "Location",
- },
- "msg": {"type": "string", "title": "Message"},
- "type": {"type": "string", "title": "Error Type"},
- },
- "type": "object",
- "required": ["loc", "msg", "type"],
- "title": "ValidationError",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv1__Item": {
- "properties": {
- "title": {"type": "string", "title": "Title"},
- "size": {"type": "integer", "title": "Size"},
- "description": {
- "type": "string",
- "title": "Description",
- },
- "sub": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem"
- },
- "multi": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem"
- },
- "type": "array",
- "title": "Multi",
- "default": [],
- },
- },
- "type": "object",
- "required": ["title", "size", "sub"],
- "title": "Item",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv1__ItemInList": {
- "properties": {
- "name1": {"type": "string", "title": "Name1"}
- },
- "type": "object",
- "required": ["name1"],
- "title": "ItemInList",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem": {
- "properties": {
- "name": {"type": "string", "title": "Name"}
- },
- "type": "object",
- "required": ["name"],
- "title": "SubItem",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv2__Item": {
- "properties": {
- "new_title": {
- "type": "string",
- "title": "New Title",
- },
- "new_size": {
- "type": "integer",
- "title": "New Size",
- },
- "new_description": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "New Description",
- },
- "new_sub": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem"
- },
- "new_multi": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem"
- },
- "type": "array",
- "title": "New Multi",
- "default": [],
- },
- },
- "type": "object",
- "required": ["new_title", "new_size", "new_sub"],
- "title": "Item",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input": {
- "properties": {
- "new_title": {
- "type": "string",
- "title": "New Title",
- },
- "new_size": {
- "type": "integer",
- "title": "New Size",
- },
- "new_description": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "New Description",
- },
- "new_sub": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem"
- },
- "new_multi": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem"
- },
- "type": "array",
- "title": "New Multi",
- "default": [],
- },
- },
- "type": "object",
- "required": ["new_title", "new_size", "new_sub"],
- "title": "Item",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv2__ItemInList": {
- "properties": {
- "name2": {"type": "string", "title": "Name2"}
- },
- "type": "object",
- "required": ["name2"],
- "title": "ItemInList",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem": {
- "properties": {
- "new_sub_name": {
- "type": "string",
- "title": "New Sub Name",
- }
- },
- "type": "object",
- "required": ["new_sub_name"],
- "title": "SubItem",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv2b__Item": {
- "properties": {
- "dup_title": {
- "type": "string",
- "title": "Dup Title",
- },
- "dup_size": {
- "type": "integer",
- "title": "Dup Size",
- },
- "dup_description": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Dup Description",
- },
- "dup_sub": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem"
- },
- "dup_multi": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem"
- },
- "type": "array",
- "title": "Dup Multi",
- "default": [],
- },
- },
- "type": "object",
- "required": ["dup_title", "dup_size", "dup_sub"],
- "title": "Item",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv2b__ItemInList": {
- "properties": {
- "dup_name2": {
- "type": "string",
- "title": "Dup Name2",
- }
- },
- "type": "object",
- "required": ["dup_name2"],
- "title": "ItemInList",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem": {
- "properties": {
- "dup_sub_name": {
- "type": "string",
- "title": "Dup Sub Name",
- }
- },
- "type": "object",
- "required": ["dup_sub_name"],
- "title": "SubItem",
- },
- }
- ),
- ),
- },
- }
- )
diff --git a/tests/test_pydantic_v1_v2_noneable.py b/tests/test_pydantic_v1_v2_noneable.py
deleted file mode 100644
index 3e86469908..0000000000
--- a/tests/test_pydantic_v1_v2_noneable.py
+++ /dev/null
@@ -1,766 +0,0 @@
-import sys
-from typing import Any, Union
-
-from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314
-
-if sys.version_info >= (3, 14):
- skip_module_if_py_gte_314()
-
-from fastapi import FastAPI
-from fastapi._compat.v1 import BaseModel
-from fastapi.testclient import TestClient
-from inline_snapshot import snapshot
-from pydantic import BaseModel as NewBaseModel
-
-
-class SubItem(BaseModel):
- name: str
-
-
-class Item(BaseModel):
- title: str
- size: int
- description: Union[str, None] = None
- sub: SubItem
- multi: list[SubItem] = []
-
-
-class NewSubItem(NewBaseModel):
- new_sub_name: str
-
-
-class NewItem(NewBaseModel):
- new_title: str
- new_size: int
- new_description: Union[str, None] = None
- new_sub: NewSubItem
- new_multi: list[NewSubItem] = []
-
-
-app = FastAPI()
-
-
-@app.post("/v1-to-v2/")
-def handle_v1_item_to_v2(data: Item) -> Union[NewItem, None]:
- if data.size < 0:
- return None
- return NewItem(
- new_title=data.title,
- new_size=data.size,
- new_description=data.description,
- new_sub=NewSubItem(new_sub_name=data.sub.name),
- new_multi=[NewSubItem(new_sub_name=s.name) for s in data.multi],
- )
-
-
-@app.post("/v1-to-v2/item-filter", response_model=Union[NewItem, None])
-def handle_v1_item_to_v2_filter(data: Item) -> Any:
- if data.size < 0:
- return None
- result = {
- "new_title": data.title,
- "new_size": data.size,
- "new_description": data.description,
- "new_sub": {"new_sub_name": data.sub.name, "new_sub_secret": "sub_hidden"},
- "new_multi": [
- {"new_sub_name": s.name, "new_sub_secret": "sub_hidden"} for s in data.multi
- ],
- "secret": "hidden_v1_to_v2",
- }
- return result
-
-
-@app.post("/v2-to-v1/item")
-def handle_v2_item_to_v1(data: NewItem) -> Union[Item, None]:
- if data.new_size < 0:
- return None
- return Item(
- title=data.new_title,
- size=data.new_size,
- description=data.new_description,
- sub=SubItem(name=data.new_sub.new_sub_name),
- multi=[SubItem(name=s.new_sub_name) for s in data.new_multi],
- )
-
-
-@app.post("/v2-to-v1/item-filter", response_model=Union[Item, None])
-def handle_v2_item_to_v1_filter(data: NewItem) -> Any:
- if data.new_size < 0:
- return None
- result = {
- "title": data.new_title,
- "size": data.new_size,
- "description": data.new_description,
- "sub": {"name": data.new_sub.new_sub_name, "sub_secret": "sub_hidden"},
- "multi": [
- {"name": s.new_sub_name, "sub_secret": "sub_hidden"} for s in data.new_multi
- ],
- "secret": "hidden_v2_to_v1",
- }
- return result
-
-
-client = TestClient(app)
-
-
-def test_v1_to_v2_item_success():
- response = client.post(
- "/v1-to-v2/",
- json={
- "title": "Old Item",
- "size": 100,
- "description": "V1 description",
- "sub": {"name": "V1 Sub"},
- "multi": [{"name": "M1"}, {"name": "M2"}],
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "new_title": "Old Item",
- "new_size": 100,
- "new_description": "V1 description",
- "new_sub": {"new_sub_name": "V1 Sub"},
- "new_multi": [{"new_sub_name": "M1"}, {"new_sub_name": "M2"}],
- }
-
-
-def test_v1_to_v2_item_returns_none():
- response = client.post(
- "/v1-to-v2/",
- json={"title": "Invalid Item", "size": -10, "sub": {"name": "Sub"}},
- )
- assert response.status_code == 200, response.text
- assert response.json() is None
-
-
-def test_v1_to_v2_item_minimal():
- response = client.post(
- "/v1-to-v2/", json={"title": "Minimal", "size": 50, "sub": {"name": "MinSub"}}
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "new_title": "Minimal",
- "new_size": 50,
- "new_description": None,
- "new_sub": {"new_sub_name": "MinSub"},
- "new_multi": [],
- }
-
-
-def test_v1_to_v2_item_filter_success():
- response = client.post(
- "/v1-to-v2/item-filter",
- json={
- "title": "Filtered Item",
- "size": 50,
- "sub": {"name": "Sub"},
- "multi": [{"name": "Multi1"}],
- },
- )
- assert response.status_code == 200, response.text
- result = response.json()
- assert result["new_title"] == "Filtered Item"
- assert result["new_size"] == 50
- assert result["new_sub"]["new_sub_name"] == "Sub"
- assert result["new_multi"][0]["new_sub_name"] == "Multi1"
- # Verify secret fields are filtered out
- assert "secret" not in result
- assert "new_sub_secret" not in result["new_sub"]
- assert "new_sub_secret" not in result["new_multi"][0]
-
-
-def test_v1_to_v2_item_filter_returns_none():
- response = client.post(
- "/v1-to-v2/item-filter",
- json={"title": "Invalid", "size": -1, "sub": {"name": "Sub"}},
- )
- assert response.status_code == 200, response.text
- assert response.json() is None
-
-
-def test_v2_to_v1_item_success():
- response = client.post(
- "/v2-to-v1/item",
- json={
- "new_title": "New Item",
- "new_size": 200,
- "new_description": "V2 description",
- "new_sub": {"new_sub_name": "V2 Sub"},
- "new_multi": [{"new_sub_name": "N1"}, {"new_sub_name": "N2"}],
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "title": "New Item",
- "size": 200,
- "description": "V2 description",
- "sub": {"name": "V2 Sub"},
- "multi": [{"name": "N1"}, {"name": "N2"}],
- }
-
-
-def test_v2_to_v1_item_returns_none():
- response = client.post(
- "/v2-to-v1/item",
- json={
- "new_title": "Invalid New",
- "new_size": -5,
- "new_sub": {"new_sub_name": "NewSub"},
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() is None
-
-
-def test_v2_to_v1_item_minimal():
- response = client.post(
- "/v2-to-v1/item",
- json={
- "new_title": "MinimalNew",
- "new_size": 75,
- "new_sub": {"new_sub_name": "MinNewSub"},
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "title": "MinimalNew",
- "size": 75,
- "description": None,
- "sub": {"name": "MinNewSub"},
- "multi": [],
- }
-
-
-def test_v2_to_v1_item_filter_success():
- response = client.post(
- "/v2-to-v1/item-filter",
- json={
- "new_title": "Filtered New",
- "new_size": 75,
- "new_sub": {"new_sub_name": "NewSub"},
- "new_multi": [],
- },
- )
- assert response.status_code == 200, response.text
- result = response.json()
- assert result["title"] == "Filtered New"
- assert result["size"] == 75
- assert result["sub"]["name"] == "NewSub"
- # Verify secret fields are filtered out
- assert "secret" not in result
- assert "sub_secret" not in result["sub"]
-
-
-def test_v2_to_v1_item_filter_returns_none():
- response = client.post(
- "/v2-to-v1/item-filter",
- json={
- "new_title": "Invalid Filtered",
- "new_size": -100,
- "new_sub": {"new_sub_name": "Sub"},
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() is None
-
-
-def test_v1_to_v2_validation_error():
- response = client.post("/v1-to-v2/", json={"title": "Missing fields"})
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", "size"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "sub"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
-
-
-def test_v1_to_v2_nested_validation_error():
- response = client.post(
- "/v1-to-v2/",
- json={"title": "Bad sub", "size": 100, "sub": {"wrong_field": "value"}},
- )
- assert response.status_code == 422, response.text
- error_detail = response.json()["detail"]
- assert len(error_detail) == 1
- assert error_detail[0]["loc"] == ["body", "sub", "name"]
-
-
-def test_v1_to_v2_type_validation_error():
- response = client.post(
- "/v1-to-v2/",
- json={"title": "Bad type", "size": "not_a_number", "sub": {"name": "Sub"}},
- )
- assert response.status_code == 422, response.text
- error_detail = response.json()["detail"]
- assert len(error_detail) == 1
- assert error_detail[0]["loc"] == ["body", "size"]
-
-
-def test_v2_to_v1_validation_error():
- response = client.post("/v2-to-v1/item", json={"new_title": "Missing fields"})
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": pydantic_snapshot(
- v2=snapshot(
- [
- {
- "type": "missing",
- "loc": ["body", "new_size"],
- "msg": "Field required",
- "input": {"new_title": "Missing fields"},
- },
- {
- "type": "missing",
- "loc": ["body", "new_sub"],
- "msg": "Field required",
- "input": {"new_title": "Missing fields"},
- },
- ]
- ),
- v1=snapshot(
- [
- {
- "loc": ["body", "new_size"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "new_sub"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- ),
- )
- }
- )
-
-
-def test_v2_to_v1_nested_validation_error():
- response = client.post(
- "/v2-to-v1/item",
- json={
- "new_title": "Bad sub",
- "new_size": 200,
- "new_sub": {"wrong_field": "value"},
- },
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- pydantic_snapshot(
- v2=snapshot(
- {
- "type": "missing",
- "loc": ["body", "new_sub", "new_sub_name"],
- "msg": "Field required",
- "input": {"wrong_field": "value"},
- }
- ),
- v1=snapshot(
- {
- "loc": ["body", "new_sub", "new_sub_name"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ),
- )
- ]
- }
- )
-
-
-def test_v2_to_v1_type_validation_error():
- response = client.post(
- "/v2-to-v1/item",
- json={
- "new_title": "Bad type",
- "new_size": "not_a_number",
- "new_sub": {"new_sub_name": "Sub"},
- },
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- pydantic_snapshot(
- v2=snapshot(
- {
- "type": "int_parsing",
- "loc": ["body", "new_size"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "not_a_number",
- }
- ),
- v1=snapshot(
- {
- "loc": ["body", "new_size"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ),
- )
- ]
- }
- )
-
-
-def test_v1_to_v2_with_multi_items():
- response = client.post(
- "/v1-to-v2/",
- json={
- "title": "Complex Item",
- "size": 300,
- "description": "Item with multiple sub-items",
- "sub": {"name": "Main Sub"},
- "multi": [{"name": "Sub1"}, {"name": "Sub2"}, {"name": "Sub3"}],
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() == snapshot(
- {
- "new_title": "Complex Item",
- "new_size": 300,
- "new_description": "Item with multiple sub-items",
- "new_sub": {"new_sub_name": "Main Sub"},
- "new_multi": [
- {"new_sub_name": "Sub1"},
- {"new_sub_name": "Sub2"},
- {"new_sub_name": "Sub3"},
- ],
- }
- )
-
-
-def test_v2_to_v1_with_multi_items():
- response = client.post(
- "/v2-to-v1/item",
- json={
- "new_title": "Complex New Item",
- "new_size": 400,
- "new_description": "New item with multiple sub-items",
- "new_sub": {"new_sub_name": "Main New Sub"},
- "new_multi": [{"new_sub_name": "NewSub1"}, {"new_sub_name": "NewSub2"}],
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() == snapshot(
- {
- "title": "Complex New Item",
- "size": 400,
- "description": "New item with multiple sub-items",
- "sub": {"name": "Main New Sub"},
- "multi": [{"name": "NewSub1"}, {"name": "NewSub2"}],
- }
- )
-
-
-def test_openapi_schema():
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == snapshot(
- {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/v1-to-v2/": {
- "post": {
- "summary": "Handle V1 Item To V2",
- "operationId": "handle_v1_item_to_v2_v1_to_v2__post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Item"
- }
- ],
- "title": "Data",
- }
- ),
- v1=snapshot(
- {"$ref": "#/components/schemas/Item"}
- ),
- )
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "anyOf": [
- {
- "$ref": "#/components/schemas/NewItem"
- },
- {"type": "null"},
- ],
- "title": "Response Handle V1 Item To V2 V1 To V2 Post",
- }
- ),
- v1=snapshot(
- {"$ref": "#/components/schemas/NewItem"}
- ),
- )
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v1-to-v2/item-filter": {
- "post": {
- "summary": "Handle V1 Item To V2 Filter",
- "operationId": "handle_v1_item_to_v2_filter_v1_to_v2_item_filter_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Item"
- }
- ],
- "title": "Data",
- }
- ),
- v1=snapshot(
- {"$ref": "#/components/schemas/Item"}
- ),
- )
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "anyOf": [
- {
- "$ref": "#/components/schemas/NewItem"
- },
- {"type": "null"},
- ],
- "title": "Response Handle V1 Item To V2 Filter V1 To V2 Item Filter Post",
- }
- ),
- v1=snapshot(
- {"$ref": "#/components/schemas/NewItem"}
- ),
- )
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/item": {
- "post": {
- "summary": "Handle V2 Item To V1",
- "operationId": "handle_v2_item_to_v1_v2_to_v1_item_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/NewItem"}
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/item-filter": {
- "post": {
- "summary": "Handle V2 Item To V1 Filter",
- "operationId": "handle_v2_item_to_v1_filter_v2_to_v1_item_filter_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/NewItem"}
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- },
- "components": {
- "schemas": {
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {
- "$ref": "#/components/schemas/ValidationError"
- },
- "type": "array",
- "title": "Detail",
- }
- },
- "type": "object",
- "title": "HTTPValidationError",
- },
- "Item": {
- "properties": {
- "title": {"type": "string", "title": "Title"},
- "size": {"type": "integer", "title": "Size"},
- "description": {"type": "string", "title": "Description"},
- "sub": {"$ref": "#/components/schemas/SubItem"},
- "multi": {
- "items": {"$ref": "#/components/schemas/SubItem"},
- "type": "array",
- "title": "Multi",
- "default": [],
- },
- },
- "type": "object",
- "required": ["title", "size", "sub"],
- "title": "Item",
- },
- "NewItem": {
- "properties": {
- "new_title": {"type": "string", "title": "New Title"},
- "new_size": {"type": "integer", "title": "New Size"},
- "new_description": pydantic_snapshot(
- v2=snapshot(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "New Description",
- }
- ),
- v1=snapshot(
- {"type": "string", "title": "New Description"}
- ),
- ),
- "new_sub": {"$ref": "#/components/schemas/NewSubItem"},
- "new_multi": {
- "items": {"$ref": "#/components/schemas/NewSubItem"},
- "type": "array",
- "title": "New Multi",
- "default": [],
- },
- },
- "type": "object",
- "required": ["new_title", "new_size", "new_sub"],
- "title": "NewItem",
- },
- "NewSubItem": {
- "properties": {
- "new_sub_name": {"type": "string", "title": "New Sub Name"}
- },
- "type": "object",
- "required": ["new_sub_name"],
- "title": "NewSubItem",
- },
- "SubItem": {
- "properties": {"name": {"type": "string", "title": "Name"}},
- "type": "object",
- "required": ["name"],
- "title": "SubItem",
- },
- "ValidationError": {
- "properties": {
- "loc": {
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- "type": "array",
- "title": "Location",
- },
- "msg": {"type": "string", "title": "Message"},
- "type": {"type": "string", "title": "Error Type"},
- },
- "type": "object",
- "required": ["loc", "msg", "type"],
- "title": "ValidationError",
- },
- }
- },
- }
- )
diff --git a/tests/test_query.py b/tests/test_query.py
index 57f551d2ab..c25960caca 100644
--- a/tests/test_query.py
+++ b/tests/test_query.py
@@ -1,4 +1,3 @@
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
from .main import app
@@ -9,29 +8,16 @@ client = TestClient(app)
def test_query():
response = client.get("/query")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "query"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "query"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "query"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_query_query_baz():
@@ -43,29 +29,16 @@ def test_query_query_baz():
def test_query_not_declared_baz():
response = client.get("/query?not_declared=baz")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "query"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "query"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "query"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_query_optional():
@@ -89,29 +62,16 @@ def test_query_optional_not_declared_baz():
def test_query_int():
response = client.get("/query/int")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "query"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "query"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "query"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_query_int_query_42():
@@ -123,85 +83,46 @@ def test_query_int_query_42():
def test_query_int_query_42_5():
response = client.get("/query/int?query=42.5")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["query", "query"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "42.5",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "query"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["query", "query"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "42.5",
+ }
+ ]
+ }
def test_query_int_query_baz():
response = client.get("/query/int?query=baz")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["query", "query"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "baz",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "query"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["query", "query"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "baz",
+ }
+ ]
+ }
def test_query_int_not_declared_baz():
response = client.get("/query/int?not_declared=baz")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "query"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "query"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "query"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_query_int_optional():
@@ -219,29 +140,16 @@ def test_query_int_optional_query_50():
def test_query_int_optional_query_foo():
response = client.get("/query/int/optional?query=foo")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["query", "query"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "foo",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "query"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["query", "query"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "foo",
+ }
+ ]
+ }
def test_query_int_default():
@@ -259,29 +167,16 @@ def test_query_int_default_query_50():
def test_query_int_default_query_foo():
response = client.get("/query/int/default?query=foo")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["query", "query"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "foo",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "query"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["query", "query"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "foo",
+ }
+ ]
+ }
def test_query_param():
@@ -299,29 +194,16 @@ def test_query_param_query_50():
def test_query_param_required():
response = client.get("/query/param-required")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "query"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "query"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "query"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_query_param_required_query_50():
@@ -333,29 +215,16 @@ def test_query_param_required_query_50():
def test_query_param_required_int():
response = client.get("/query/param-required/int")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "query"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "query"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "query"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_query_param_required_int_query_50():
@@ -367,29 +236,16 @@ def test_query_param_required_int_query_50():
def test_query_param_required_int_query_foo():
response = client.get("/query/param-required/int?query=foo")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["query", "query"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "foo",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "query"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["query", "query"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "foo",
+ }
+ ]
+ }
def test_query_frozenset_query_1_query_1_query_2():
diff --git a/tests/test_query_cookie_header_model_extra_params.py b/tests/test_query_cookie_header_model_extra_params.py
index f4ebefb3f3..d361e1e533 100644
--- a/tests/test_query_cookie_header_model_extra_params.py
+++ b/tests/test_query_cookie_header_model_extra_params.py
@@ -1,5 +1,4 @@
from fastapi import Cookie, FastAPI, Header, Query
-from fastapi._compat import PYDANTIC_V2
from fastapi.testclient import TestClient
from pydantic import BaseModel
@@ -9,12 +8,7 @@ app = FastAPI()
class Model(BaseModel):
param: str
- if PYDANTIC_V2:
- model_config = {"extra": "allow"}
- else:
-
- class Config:
- extra = "allow"
+ model_config = {"extra": "allow"}
@app.get("/query")
diff --git a/tests/test_read_with_orm_mode.py b/tests/test_read_with_orm_mode.py
index b359874439..cd7389252a 100644
--- a/tests/test_read_with_orm_mode.py
+++ b/tests/test_read_with_orm_mode.py
@@ -4,10 +4,7 @@ from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel, ConfigDict
-from .utils import needs_pydanticv1, needs_pydanticv2
-
-@needs_pydanticv2
def test_read_with_orm_mode() -> None:
class PersonBase(BaseModel):
name: str
@@ -44,45 +41,3 @@ def test_read_with_orm_mode() -> None:
assert data["name"] == person_data["name"]
assert data["lastname"] == person_data["lastname"]
assert data["full_name"] == person_data["name"] + " " + person_data["lastname"]
-
-
-@needs_pydanticv1
-def test_read_with_orm_mode_pv1() -> None:
- class PersonBase(BaseModel):
- name: str
- lastname: str
-
- class Person(PersonBase):
- @property
- def full_name(self) -> str:
- return f"{self.name} {self.lastname}"
-
- class Config:
- orm_mode = True
- read_with_orm_mode = True
-
- class PersonCreate(PersonBase):
- pass
-
- class PersonRead(PersonBase):
- full_name: str
-
- class Config:
- orm_mode = True
-
- app = FastAPI()
-
- @app.post("/people/", response_model=PersonRead)
- def create_person(person: PersonCreate) -> Any:
- db_person = Person.from_orm(person)
- return db_person
-
- client = TestClient(app)
-
- person_data = {"name": "Dive", "lastname": "Wilson"}
- response = client.post("/people/", json=person_data)
- data = response.json()
- assert response.status_code == 200, response.text
- assert data["name"] == person_data["name"]
- assert data["lastname"] == person_data["lastname"]
- assert data["full_name"] == person_data["name"] + " " + person_data["lastname"]
diff --git a/tests/test_regex_deprecated_body.py b/tests/test_regex_deprecated_body.py
index cfbff19c87..5b4daa450f 100644
--- a/tests/test_regex_deprecated_body.py
+++ b/tests/test_regex_deprecated_body.py
@@ -1,16 +1,17 @@
from typing import Annotated
import pytest
-from dirty_equals import IsDict
from fastapi import FastAPI, Form
+from fastapi.exceptions import FastAPIDeprecationWarning
from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
from .utils import needs_py310
def get_client():
app = FastAPI()
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.post("/items/")
async def read_items(
@@ -46,31 +47,17 @@ def test_query_nonregexquery():
client = get_client()
response = client.post("/items/", data={"q": "nonregexquery"})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "string_pattern_mismatch",
- "loc": ["body", "q"],
- "msg": "String should match pattern '^fixedquery$'",
- "input": "nonregexquery",
- "ctx": {"pattern": "^fixedquery$"},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "ctx": {"pattern": "^fixedquery$"},
- "loc": ["body", "q"],
- "msg": 'string does not match regex "^fixedquery$"',
- "type": "value_error.str.regex",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_pattern_mismatch",
+ "loc": ["body", "q"],
+ "msg": "String should match pattern '^fixedquery$'",
+ "input": "nonregexquery",
+ "ctx": {"pattern": "^fixedquery$"},
+ }
+ ]
+ }
@needs_py310
@@ -78,104 +65,88 @@ def test_openapi_schema():
client = get_client()
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
- # insert_assert(response.json())
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/": {
- "post": {
- "summary": "Read Items",
- "operationId": "read_items_items__post",
- "requestBody": {
- "content": {
- "application/x-www-form-urlencoded": {
- "schema": IsDict(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Body_read_items_items__post"
- }
- ],
- "title": "Body",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "post": {
+ "summary": "Read Items",
+ "operationId": "read_items_items__post",
+ "requestBody": {
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {
"$ref": "#/components/schemas/Body_read_items_items__post"
}
- )
- }
- }
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
}
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
},
},
- },
+ }
}
- }
- },
- "components": {
- "schemas": {
- "Body_read_items_items__post": {
- "properties": {
- "q": IsDict(
- {
+ },
+ "components": {
+ "schemas": {
+ "Body_read_items_items__post": {
+ "properties": {
+ "q": {
"anyOf": [
{"type": "string", "pattern": "^fixedquery$"},
{"type": "null"},
],
"title": "Q",
}
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"type": "string", "pattern": "^fixedquery$", "title": "Q"}
- )
- },
- "type": "object",
- "title": "Body_read_items_items__post",
- },
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {"$ref": "#/components/schemas/ValidationError"},
- "type": "array",
- "title": "Detail",
- }
- },
- "type": "object",
- "title": "HTTPValidationError",
- },
- "ValidationError": {
- "properties": {
- "loc": {
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- "type": "array",
- "title": "Location",
},
- "msg": {"type": "string", "title": "Message"},
- "type": {"type": "string", "title": "Error Type"},
+ "type": "object",
+ "title": "Body_read_items_items__post",
},
- "type": "object",
- "required": ["loc", "msg", "type"],
- "title": "ValidationError",
- },
- }
- },
- }
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
+ "type": "array",
+ "title": "Detail",
+ }
+ },
+ "type": "object",
+ "title": "HTTPValidationError",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ "type": "array",
+ "title": "Location",
+ },
+ "msg": {"type": "string", "title": "Message"},
+ "type": {"type": "string", "title": "Error Type"},
+ },
+ "type": "object",
+ "required": ["loc", "msg", "type"],
+ "title": "ValidationError",
+ },
+ }
+ },
+ }
+ )
diff --git a/tests/test_regex_deprecated_params.py b/tests/test_regex_deprecated_params.py
index 7d9988f9f8..d6eaa45fb1 100644
--- a/tests/test_regex_deprecated_params.py
+++ b/tests/test_regex_deprecated_params.py
@@ -1,8 +1,8 @@
from typing import Annotated
import pytest
-from dirty_equals import IsDict
from fastapi import FastAPI, Query
+from fastapi.exceptions import FastAPIDeprecationWarning
from fastapi.testclient import TestClient
from .utils import needs_py310
@@ -10,7 +10,7 @@ from .utils import needs_py310
def get_client():
app = FastAPI()
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.get("/items/")
async def read_items(
@@ -46,31 +46,17 @@ def test_query_params_str_validations_item_query_nonregexquery():
client = get_client()
response = client.get("/items/", params={"q": "nonregexquery"})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "string_pattern_mismatch",
- "loc": ["query", "q"],
- "msg": "String should match pattern '^fixedquery$'",
- "input": "nonregexquery",
- "ctx": {"pattern": "^fixedquery$"},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "ctx": {"pattern": "^fixedquery$"},
- "loc": ["query", "q"],
- "msg": 'string does not match regex "^fixedquery$"',
- "type": "value_error.str.regex",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_pattern_mismatch",
+ "loc": ["query", "q"],
+ "msg": "String should match pattern '^fixedquery$'",
+ "input": "nonregexquery",
+ "ctx": {"pattern": "^fixedquery$"},
+ }
+ ]
+ }
@needs_py310
@@ -92,23 +78,13 @@ def test_openapi_schema():
"name": "q",
"in": "query",
"required": False,
- "schema": IsDict(
- {
- "anyOf": [
- {"type": "string", "pattern": "^fixedquery$"},
- {"type": "null"},
- ],
- "title": "Q",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "string",
- "pattern": "^fixedquery$",
- "title": "Q",
- }
- ),
+ "schema": {
+ "anyOf": [
+ {"type": "string", "pattern": "^fixedquery$"},
+ {"type": "null"},
+ ],
+ "title": "Q",
+ },
}
],
"responses": {
diff --git a/tests/test_request_param_model_by_alias.py b/tests/test_request_param_model_by_alias.py
index a6f759f23b..c29130d7a6 100644
--- a/tests/test_request_param_model_by_alias.py
+++ b/tests/test_request_param_model_by_alias.py
@@ -1,6 +1,5 @@
from dirty_equals import IsPartialDict
from fastapi import Cookie, FastAPI, Header, Query
-from fastapi._compat import PYDANTIC_V2
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
@@ -53,8 +52,7 @@ def test_query_model_with_alias_by_name():
response = client.get("/query", params={"param": "value"})
assert response.status_code == 422, response.text
details = response.json()
- if PYDANTIC_V2:
- assert details["detail"][0]["input"] == {"param": "value"}
+ assert details["detail"][0]["input"] == {"param": "value"}
def test_header_model_with_alias_by_name():
@@ -62,8 +60,7 @@ def test_header_model_with_alias_by_name():
response = client.get("/header", headers={"param": "value"})
assert response.status_code == 422, response.text
details = response.json()
- if PYDANTIC_V2:
- assert details["detail"][0]["input"] == IsPartialDict({"param": "value"})
+ assert details["detail"][0]["input"] == IsPartialDict({"param": "value"})
def test_cookie_model_with_alias_by_name():
@@ -72,5 +69,4 @@ def test_cookie_model_with_alias_by_name():
response = client.get("/cookie")
assert response.status_code == 422, response.text
details = response.json()
- if PYDANTIC_V2:
- assert details["detail"][0]["input"] == {"param": "value"}
+ assert details["detail"][0]["input"] == {"param": "value"}
diff --git a/tests/test_request_params/test_body/test_list.py b/tests/test_request_params/test_body/test_list.py
index b2503a1c44..970e6a6607 100644
--- a/tests/test_request_params/test_body/test_list.py
+++ b/tests/test_request_params/test_body/test_list.py
@@ -1,13 +1,11 @@
from typing import Annotated, Union
import pytest
-from dirty_equals import IsDict, IsOneOf, IsPartialDict
+from dirty_equals import IsOneOf, IsPartialDict
from fastapi import Body, FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
-from tests.utils import needs_pydanticv2
-
from .utils import get_body_model_name
app = FastAPI()
@@ -61,28 +59,16 @@ def test_required_list_str_missing(path: str, json: Union[dict, None]):
client = TestClient(app)
response = client.post(path, json=json)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": IsOneOf(["body", "p"], ["body"]),
- "msg": "Field required",
- "input": IsOneOf(None, {}),
- }
- ]
- }
- ) | IsDict(
- {
- "detail": [
- {
- "loc": IsOneOf(["body", "p"], ["body"]),
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": IsOneOf(["body", "p"], ["body"]),
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -150,29 +136,16 @@ def test_required_list_alias_missing(path: str, json: Union[dict, None]):
client = TestClient(app)
response = client.post(path, json=json)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": IsOneOf(["body", "p_alias"], ["body"]),
- "msg": "Field required",
- "input": IsOneOf(None, {}),
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": IsOneOf(["body", "p_alias"], ["body"]),
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": IsOneOf(["body", "p_alias"], ["body"]),
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -183,29 +156,16 @@ def test_required_list_alias_by_name(path: str):
client = TestClient(app)
response = client.post(path, json={"p": ["hello", "world"]})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "p_alias"],
- "msg": "Field required",
- "input": IsOneOf(None, {"p": ["hello", "world"]}),
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "p_alias"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {"p": ["hello", "world"]}),
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -246,7 +206,6 @@ async def read_model_required_list_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/required-list-validation-alias", "/model-required-list-validation-alias"],
@@ -269,7 +228,6 @@ def test_required_list_validation_alias_schema(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize("json", [None, {}])
@pytest.mark.parametrize(
"path",
@@ -294,7 +252,6 @@ def test_required_list_validation_alias_missing(path: str, json: Union[dict, Non
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -319,7 +276,6 @@ def test_required_list_validation_alias_by_name(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -364,7 +320,6 @@ def read_model_required_list_alias_and_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -390,7 +345,6 @@ def test_required_list_alias_and_validation_alias_schema(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize("json", [None, {}])
@pytest.mark.parametrize(
"path",
@@ -415,7 +369,6 @@ def test_required_list_alias_and_validation_alias_missing(path: str, json):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -442,7 +395,6 @@ def test_required_list_alias_and_validation_alias_by_name(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -467,7 +419,6 @@ def test_required_list_alias_and_validation_alias_by_alias(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
diff --git a/tests/test_request_params/test_body/test_optional_list.py b/tests/test_request_params/test_body/test_optional_list.py
index ede635f840..ba8ba9092e 100644
--- a/tests/test_request_params/test_body/test_optional_list.py
+++ b/tests/test_request_params/test_body/test_optional_list.py
@@ -1,13 +1,10 @@
from typing import Annotated, Optional
import pytest
-from dirty_equals import IsDict
from fastapi import Body, FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
-from tests.utils import needs_pydanticv2
-
from .utils import get_body_model_name
app = FastAPI()
@@ -40,30 +37,19 @@ def test_optional_list_str_schema(path: str):
openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path)
- assert app.openapi()["components"]["schemas"][body_model_name] == IsDict(
- {
- "properties": {
- "p": {
- "anyOf": [
- {"items": {"type": "string"}, "type": "array"},
- {"type": "null"},
- ],
- "title": "P",
- },
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P",
},
- "title": body_model_name,
- "type": "object",
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "properties": {
- "p": {"items": {"type": "string"}, "type": "array", "title": "P"},
- },
- "title": body_model_name,
- "type": "object",
- }
- )
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
def test_optional_list_str_missing():
@@ -77,29 +63,16 @@ def test_model_optional_list_str_missing():
client = TestClient(app)
response = client.post("/model-optional-list-str")
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "input": None,
- "loc": ["body"],
- "msg": "Field required",
- "type": "missing",
- },
- ],
- }
- ) | IsDict(
- {
- # TODO: remove when deprecating Pydantic v1
- "detail": [
- {
- "loc": ["body"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ],
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "input": None,
+ "loc": ["body"],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ],
+ }
@pytest.mark.parametrize(
@@ -155,34 +128,19 @@ def test_optional_list_str_alias_schema(path: str):
openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path)
- assert app.openapi()["components"]["schemas"][body_model_name] == IsDict(
- {
- "properties": {
- "p_alias": {
- "anyOf": [
- {"items": {"type": "string"}, "type": "array"},
- {"type": "null"},
- ],
- "title": "P Alias",
- },
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_alias": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P Alias",
},
- "title": body_model_name,
- "type": "object",
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "properties": {
- "p_alias": {
- "items": {"type": "string"},
- "type": "array",
- "title": "P Alias",
- },
- },
- "title": body_model_name,
- "type": "object",
- }
- )
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
def test_optional_list_alias_missing():
@@ -196,29 +154,16 @@ def test_model_optional_list_alias_missing():
client = TestClient(app)
response = client.post("/model-optional-list-alias")
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "input": None,
- "loc": ["body"],
- "msg": "Field required",
- "type": "missing",
- },
- ],
- }
- ) | IsDict(
- {
- # TODO: remove when deprecating Pydantic v1
- "detail": [
- {
- "loc": ["body"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ],
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "input": None,
+ "loc": ["body"],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ],
+ }
@pytest.mark.parametrize(
@@ -283,7 +228,6 @@ def read_model_optional_list_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/optional-list-validation-alias", "/model-optional-list-validation-alias"],
@@ -292,34 +236,19 @@ def test_optional_list_validation_alias_schema(path: str):
openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path)
- assert app.openapi()["components"]["schemas"][body_model_name] == IsDict(
- {
- "properties": {
- "p_val_alias": {
- "anyOf": [
- {"items": {"type": "string"}, "type": "array"},
- {"type": "null"},
- ],
- "title": "P Val Alias",
- },
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P Val Alias",
},
- "title": body_model_name,
- "type": "object",
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "properties": {
- "p_val_alias": {
- "items": {"type": "string"},
- "type": "array",
- "title": "P Val Alias",
- },
- },
- "title": body_model_name,
- "type": "object",
- }
- )
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
def test_optional_list_validation_alias_missing():
@@ -333,29 +262,16 @@ def test_model_optional_list_validation_alias_missing():
client = TestClient(app)
response = client.post("/model-optional-list-validation-alias")
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "input": None,
- "loc": ["body"],
- "msg": "Field required",
- "type": "missing",
- },
- ],
- }
- ) | IsDict(
- {
- # TODO: remove when deprecating Pydantic v1
- "detail": [
- {
- "loc": ["body"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ],
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "input": None,
+ "loc": ["body"],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ],
+ }
@pytest.mark.parametrize(
@@ -369,7 +285,6 @@ def test_optional_list_validation_alias_missing_empty_dict(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -384,7 +299,6 @@ def test_optional_list_validation_alias_by_name(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -432,7 +346,6 @@ def read_model_optional_list_alias_and_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -444,34 +357,19 @@ def test_optional_list_alias_and_validation_alias_schema(path: str):
openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path)
- assert app.openapi()["components"]["schemas"][body_model_name] == IsDict(
- {
- "properties": {
- "p_val_alias": {
- "anyOf": [
- {"items": {"type": "string"}, "type": "array"},
- {"type": "null"},
- ],
- "title": "P Val Alias",
- },
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P Val Alias",
},
- "title": body_model_name,
- "type": "object",
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "properties": {
- "p_val_alias": {
- "items": {"type": "string"},
- "type": "array",
- "title": "P Val Alias",
- },
- },
- "title": body_model_name,
- "type": "object",
- }
- )
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
def test_optional_list_alias_and_validation_alias_missing():
@@ -485,29 +383,16 @@ def test_model_optional_list_alias_and_validation_alias_missing():
client = TestClient(app)
response = client.post("/model-optional-list-alias-and-validation-alias")
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "input": None,
- "loc": ["body"],
- "msg": "Field required",
- "type": "missing",
- },
- ],
- }
- ) | IsDict(
- {
- # TODO: remove when deprecating Pydantic v1
- "detail": [
- {
- "loc": ["body"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ],
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "input": None,
+ "loc": ["body"],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ],
+ }
@pytest.mark.parametrize(
@@ -524,7 +409,6 @@ def test_optional_list_alias_and_validation_alias_missing_empty_dict(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -539,7 +423,6 @@ def test_optional_list_alias_and_validation_alias_by_name(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -554,7 +437,6 @@ def test_optional_list_alias_and_validation_alias_by_alias(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
diff --git a/tests/test_request_params/test_body/test_optional_str.py b/tests/test_request_params/test_body/test_optional_str.py
index 9e0c7217cc..b9c18034da 100644
--- a/tests/test_request_params/test_body/test_optional_str.py
+++ b/tests/test_request_params/test_body/test_optional_str.py
@@ -1,13 +1,10 @@
from typing import Annotated, Optional
import pytest
-from dirty_equals import IsDict
from fastapi import Body, FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
-from tests.utils import needs_pydanticv2
-
from .utils import get_body_model_name
app = FastAPI()
@@ -38,27 +35,16 @@ def test_optional_str_schema(path: str):
openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path)
- assert app.openapi()["components"]["schemas"][body_model_name] == IsDict(
- {
- "properties": {
- "p": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "P",
- },
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P",
},
- "title": body_model_name,
- "type": "object",
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "properties": {
- "p": {"type": "string", "title": "P"},
- },
- "title": body_model_name,
- "type": "object",
- }
- )
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
def test_optional_str_missing():
@@ -72,29 +58,16 @@ def test_model_optional_str_missing():
client = TestClient(app)
response = client.post("/model-optional-str")
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "input": None,
- "loc": ["body"],
- "msg": "Field required",
- "type": "missing",
- },
- ],
- }
- ) | IsDict(
- {
- # TODO: remove when deprecating Pydantic v1
- "detail": [
- {
- "loc": ["body"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ],
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "input": None,
+ "loc": ["body"],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ],
+ }
@pytest.mark.parametrize(
@@ -150,27 +123,16 @@ def test_optional_str_alias_schema(path: str):
openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path)
- assert app.openapi()["components"]["schemas"][body_model_name] == IsDict(
- {
- "properties": {
- "p_alias": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "P Alias",
- },
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_alias": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P Alias",
},
- "title": body_model_name,
- "type": "object",
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "properties": {
- "p_alias": {"type": "string", "title": "P Alias"},
- },
- "title": body_model_name,
- "type": "object",
- }
- )
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
def test_optional_alias_missing():
@@ -184,29 +146,16 @@ def test_model_optional_alias_missing():
client = TestClient(app)
response = client.post("/model-optional-alias")
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "input": None,
- "loc": ["body"],
- "msg": "Field required",
- "type": "missing",
- },
- ],
- }
- ) | IsDict(
- {
- # TODO: remove when deprecating Pydantic v1
- "detail": [
- {
- "loc": ["body"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ],
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "input": None,
+ "loc": ["body"],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ],
+ }
@pytest.mark.parametrize(
@@ -268,7 +217,6 @@ def read_model_optional_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/optional-validation-alias", "/model-optional-validation-alias"],
@@ -277,30 +225,18 @@ def test_optional_validation_alias_schema(path: str):
openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path)
- assert app.openapi()["components"]["schemas"][body_model_name] == IsDict(
- {
- "properties": {
- "p_val_alias": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "P Val Alias",
- },
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P Val Alias",
},
- "title": body_model_name,
- "type": "object",
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "properties": {
- "p_val_alias": {"type": "string", "title": "P Val Alias"},
- },
- "title": body_model_name,
- "type": "object",
- }
- )
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
-@needs_pydanticv2
def test_optional_validation_alias_missing():
client = TestClient(app)
response = client.post("/optional-validation-alias")
@@ -308,37 +244,22 @@ def test_optional_validation_alias_missing():
assert response.json() == {"p": None}
-@needs_pydanticv2
def test_model_optional_validation_alias_missing():
client = TestClient(app)
response = client.post("/model-optional-validation-alias")
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "input": None,
- "loc": ["body"],
- "msg": "Field required",
- "type": "missing",
- },
- ],
- }
- ) | IsDict(
- {
- # TODO: remove when deprecating Pydantic v1
- "detail": [
- {
- "loc": ["body"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ],
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "input": None,
+ "loc": ["body"],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ],
+ }
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/optional-validation-alias", "/model-optional-validation-alias"],
@@ -350,7 +271,6 @@ def test_model_optional_validation_alias_missing_empty_dict(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -365,7 +285,6 @@ def test_optional_validation_alias_by_name(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -410,7 +329,6 @@ def read_model_optional_alias_and_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -422,30 +340,18 @@ def test_optional_alias_and_validation_alias_schema(path: str):
openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path)
- assert app.openapi()["components"]["schemas"][body_model_name] == IsDict(
- {
- "properties": {
- "p_val_alias": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "P Val Alias",
- },
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P Val Alias",
},
- "title": body_model_name,
- "type": "object",
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "properties": {
- "p_val_alias": {"type": "string", "title": "P Val Alias"},
- },
- "title": body_model_name,
- "type": "object",
- }
- )
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
-@needs_pydanticv2
def test_optional_alias_and_validation_alias_missing():
client = TestClient(app)
response = client.post("/optional-alias-and-validation-alias")
@@ -453,37 +359,22 @@ def test_optional_alias_and_validation_alias_missing():
assert response.json() == {"p": None}
-@needs_pydanticv2
def test_model_optional_alias_and_validation_alias_missing():
client = TestClient(app)
response = client.post("/model-optional-alias-and-validation-alias")
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "input": None,
- "loc": ["body"],
- "msg": "Field required",
- "type": "missing",
- },
- ],
- }
- ) | IsDict(
- {
- # TODO: remove when deprecating Pydantic v1
- "detail": [
- {
- "loc": ["body"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ],
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "input": None,
+ "loc": ["body"],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ],
+ }
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -498,7 +389,6 @@ def test_model_optional_alias_and_validation_alias_missing_empty_dict(path: str)
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -513,7 +403,6 @@ def test_optional_alias_and_validation_alias_by_name(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -528,7 +417,6 @@ def test_optional_alias_and_validation_alias_by_alias(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
diff --git a/tests/test_request_params/test_body/test_required_str.py b/tests/test_request_params/test_body/test_required_str.py
index aa47a4ede3..5b434fa1db 100644
--- a/tests/test_request_params/test_body/test_required_str.py
+++ b/tests/test_request_params/test_body/test_required_str.py
@@ -1,13 +1,11 @@
from typing import Annotated, Any, Union
import pytest
-from dirty_equals import IsDict, IsOneOf
+from dirty_equals import IsOneOf
from fastapi import Body, FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
-from tests.utils import needs_pydanticv2
-
from .utils import get_body_model_name
app = FastAPI()
@@ -57,29 +55,16 @@ def test_required_str_missing(path: str, json: Union[dict[str, Any], None]):
client = TestClient(app)
response = client.post(path, json=json)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": IsOneOf(["body"], ["body", "p"]),
- "msg": "Field required",
- "input": IsOneOf(None, {}),
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": IsOneOf(["body"], ["body", "p"]),
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": IsOneOf(["body"], ["body", "p"]),
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -143,29 +128,16 @@ def test_required_alias_missing(path: str, json: Union[dict[str, Any], None]):
client = TestClient(app)
response = client.post(path, json=json)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": IsOneOf(["body", "p_alias"], ["body"]),
- "msg": "Field required",
- "input": IsOneOf(None, {}),
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": IsOneOf(["body", "p_alias"], ["body"]),
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": IsOneOf(["body", "p_alias"], ["body"]),
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -176,29 +148,16 @@ def test_required_alias_by_name(path: str):
client = TestClient(app)
response = client.post(path, json={"p": "hello"})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "p_alias"],
- "msg": "Field required",
- "input": IsOneOf(None, {"p": "hello"}),
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": IsOneOf(["body", "p_alias"], ["body"]),
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {"p": "hello"}),
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -236,7 +195,6 @@ def read_model_required_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/required-validation-alias", "/model-required-validation-alias"],
@@ -255,7 +213,6 @@ def test_required_validation_alias_schema(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize("json", [None, {}])
@pytest.mark.parametrize(
"path",
@@ -282,7 +239,6 @@ def test_required_validation_alias_missing(
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -307,7 +263,6 @@ def test_required_validation_alias_by_name(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -353,7 +308,6 @@ def read_model_required_alias_and_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -375,7 +329,6 @@ def test_required_alias_and_validation_alias_schema(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize("json", [None, {}])
@pytest.mark.parametrize(
"path",
@@ -402,7 +355,6 @@ def test_required_alias_and_validation_alias_missing(
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -430,7 +382,6 @@ def test_required_alias_and_validation_alias_by_name(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -455,7 +406,6 @@ def test_required_alias_and_validation_alias_by_alias(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
diff --git a/tests/test_request_params/test_cookie/test_optional_str.py b/tests/test_request_params/test_cookie/test_optional_str.py
index f2d02dae54..6f381c8b86 100644
--- a/tests/test_request_params/test_cookie/test_optional_str.py
+++ b/tests/test_request_params/test_cookie/test_optional_str.py
@@ -1,13 +1,10 @@
from typing import Annotated, Optional
import pytest
-from dirty_equals import IsDict
from fastapi import Cookie, FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
-from tests.utils import needs_pydanticv2
-
app = FastAPI()
# =====================================================================================
@@ -34,26 +31,15 @@ async def read_model_optional_str(p: Annotated[CookieModelOptionalStr, Cookie()]
)
def test_optional_str_schema(path: str):
assert app.openapi()["paths"][path]["get"]["parameters"] == [
- IsDict(
- {
- "required": False,
- "schema": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "P",
- },
- "name": "p",
- "in": "cookie",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "required": False,
- "schema": {"title": "P", "type": "string"},
- "name": "p",
- "in": "cookie",
- }
- )
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P",
+ },
+ "name": "p",
+ "in": "cookie",
+ }
]
@@ -106,26 +92,15 @@ async def read_model_optional_alias(p: Annotated[CookieModelOptionalAlias, Cooki
)
def test_optional_str_alias_schema(path: str):
assert app.openapi()["paths"][path]["get"]["parameters"] == [
- IsDict(
- {
- "required": False,
- "schema": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "P Alias",
- },
- "name": "p_alias",
- "in": "cookie",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "required": False,
- "schema": {"title": "P Alias", "type": "string"},
- "name": "p_alias",
- "in": "cookie",
- }
- )
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P Alias",
+ },
+ "name": "p_alias",
+ "in": "cookie",
+ }
]
@@ -189,7 +164,6 @@ def read_model_optional_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/optional-validation-alias", "/model-optional-validation-alias"],
@@ -208,7 +182,6 @@ def test_optional_validation_alias_schema(path: str):
]
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/optional-validation-alias", "/model-optional-validation-alias"],
@@ -220,7 +193,6 @@ def test_optional_validation_alias_missing(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -236,7 +208,6 @@ def test_optional_validation_alias_by_name(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -276,7 +247,6 @@ def read_model_optional_alias_and_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -298,7 +268,6 @@ def test_optional_alias_and_validation_alias_schema(path: str):
]
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -313,7 +282,6 @@ def test_optional_alias_and_validation_alias_missing(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -329,7 +297,6 @@ def test_optional_alias_and_validation_alias_by_name(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -345,7 +312,6 @@ def test_optional_alias_and_validation_alias_by_alias(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
diff --git a/tests/test_request_params/test_cookie/test_required_str.py b/tests/test_request_params/test_cookie/test_required_str.py
index 3255857d44..3e877b3e3d 100644
--- a/tests/test_request_params/test_cookie/test_required_str.py
+++ b/tests/test_request_params/test_cookie/test_required_str.py
@@ -1,13 +1,11 @@
from typing import Annotated
import pytest
-from dirty_equals import IsDict, IsOneOf
+from dirty_equals import IsOneOf
from fastapi import Cookie, FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
-from tests.utils import needs_pydanticv2
-
app = FastAPI()
# =====================================================================================
@@ -51,29 +49,16 @@ def test_required_str_missing(path: str):
client = TestClient(app)
response = client.get(path)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["cookie", "p"],
- "msg": "Field required",
- "input": IsOneOf(None, {}),
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["cookie", "p"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["cookie", "p"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -129,29 +114,16 @@ def test_required_alias_missing(path: str):
client = TestClient(app)
response = client.get(path)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["cookie", "p_alias"],
- "msg": "Field required",
- "input": IsOneOf(None, {}),
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["cookie", "p_alias"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["cookie", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -166,32 +138,19 @@ def test_required_alias_by_name(path: str):
client.cookies.set("p", "hello")
response = client.get(path)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["cookie", "p_alias"],
- "msg": "Field required",
- "input": IsOneOf(
- None,
- {"p": "hello"},
- ),
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["cookie", "p_alias"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["cookie", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(
+ None,
+ {"p": "hello"},
+ ),
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -231,7 +190,6 @@ def read_model_required_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/required-validation-alias", "/model-required-validation-alias"],
@@ -247,7 +205,6 @@ def test_required_validation_alias_schema(path: str):
]
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -274,7 +231,6 @@ def test_required_validation_alias_missing(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -300,7 +256,6 @@ def test_required_validation_alias_by_name(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -339,7 +294,6 @@ def read_model_required_alias_and_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -358,7 +312,6 @@ def test_required_alias_and_validation_alias_schema(path: str):
]
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -385,7 +338,6 @@ def test_required_alias_and_validation_alias_missing(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -417,7 +369,6 @@ def test_required_alias_and_validation_alias_by_name(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -446,7 +397,6 @@ def test_required_alias_and_validation_alias_by_alias(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
diff --git a/tests/test_request_params/test_file/test_list.py b/tests/test_request_params/test_file/test_list.py
index 52b032e441..68280fcf32 100644
--- a/tests/test_request_params/test_file/test_list.py
+++ b/tests/test_request_params/test_file/test_list.py
@@ -1,12 +1,9 @@
from typing import Annotated
import pytest
-from dirty_equals import IsDict
from fastapi import FastAPI, File, UploadFile
from fastapi.testclient import TestClient
-from tests.utils import needs_pydanticv2
-
from .utils import get_body_model_name
app = FastAPI()
@@ -38,27 +35,11 @@ def test_list_schema(path: str):
assert app.openapi()["components"]["schemas"][body_model_name] == {
"properties": {
- "p": (
- IsDict(
- {
- "anyOf": [
- {
- "type": "array",
- "items": {"type": "string", "format": "binary"},
- },
- {"type": "null"},
- ],
- "title": "P",
- },
- )
- | IsDict(
- {
- "type": "array",
- "items": {"type": "string", "format": "binary"},
- "title": "P",
- },
- )
- )
+ "p": {
+ "type": "array",
+ "items": {"type": "string", "format": "binary"},
+ "title": "P",
+ },
},
"required": ["p"],
"title": body_model_name,
@@ -77,29 +58,16 @@ def test_list_missing(path: str):
client = TestClient(app)
response = client.post(path)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "p"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "p"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -145,27 +113,11 @@ def test_list_alias_schema(path: str):
assert app.openapi()["components"]["schemas"][body_model_name] == {
"properties": {
- "p_alias": (
- IsDict(
- {
- "anyOf": [
- {
- "type": "array",
- "items": {"type": "string", "format": "binary"},
- },
- {"type": "null"},
- ],
- "title": "P Alias",
- },
- )
- | IsDict(
- {
- "type": "array",
- "items": {"type": "string", "format": "binary"},
- "title": "P Alias",
- },
- )
- )
+ "p_alias": {
+ "type": "array",
+ "items": {"type": "string", "format": "binary"},
+ "title": "P Alias",
+ },
},
"required": ["p_alias"],
"title": body_model_name,
@@ -184,29 +136,16 @@ def test_list_alias_missing(path: str):
client = TestClient(app)
response = client.post(path)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "p_alias"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "p_alias"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_alias"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -220,29 +159,16 @@ def test_list_alias_by_name(path: str):
client = TestClient(app)
response = client.post(path, files=[("p", b"hello"), ("p", b"world")])
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "p_alias"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "p_alias"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_alias"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -280,7 +206,6 @@ def read_list_uploadfile_validation_alias(
return {"file_size": [file.size for file in p]}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -294,27 +219,11 @@ def test_list_validation_alias_schema(path: str):
assert app.openapi()["components"]["schemas"][body_model_name] == {
"properties": {
- "p_val_alias": (
- IsDict(
- {
- "anyOf": [
- {
- "type": "array",
- "items": {"type": "string", "format": "binary"},
- },
- {"type": "null"},
- ],
- "title": "P Val Alias",
- },
- )
- | IsDict(
- {
- "type": "array",
- "items": {"type": "string", "format": "binary"},
- "title": "P Val Alias",
- },
- )
- )
+ "p_val_alias": {
+ "type": "array",
+ "items": {"type": "string", "format": "binary"},
+ "title": "P Val Alias",
+ },
},
"required": ["p_val_alias"],
"title": body_model_name,
@@ -322,7 +231,6 @@ def test_list_validation_alias_schema(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -349,7 +257,6 @@ def test_list_validation_alias_missing(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -374,7 +281,6 @@ def test_list_validation_alias_by_name(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -417,7 +323,6 @@ def read_list_uploadfile_alias_and_validation_alias(
return {"file_size": [file.size for file in p]}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -431,27 +336,11 @@ def test_list_alias_and_validation_alias_schema(path: str):
assert app.openapi()["components"]["schemas"][body_model_name] == {
"properties": {
- "p_val_alias": (
- IsDict(
- {
- "anyOf": [
- {
- "type": "array",
- "items": {"type": "string", "format": "binary"},
- },
- {"type": "null"},
- ],
- "title": "P Val Alias",
- },
- )
- | IsDict(
- {
- "type": "array",
- "items": {"type": "string", "format": "binary"},
- "title": "P Val Alias",
- },
- )
- )
+ "p_val_alias": {
+ "type": "array",
+ "items": {"type": "string", "format": "binary"},
+ "title": "P Val Alias",
+ },
},
"required": ["p_val_alias"],
"title": body_model_name,
@@ -459,7 +348,6 @@ def test_list_alias_and_validation_alias_schema(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -486,7 +374,6 @@ def test_list_alias_and_validation_alias_missing(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -514,7 +401,6 @@ def test_list_alias_and_validation_alias_by_name(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -539,7 +425,6 @@ def test_list_alias_and_validation_alias_by_alias(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
diff --git a/tests/test_request_params/test_file/test_optional.py b/tests/test_request_params/test_file/test_optional.py
index b7177e071d..45ef7bdec4 100644
--- a/tests/test_request_params/test_file/test_optional.py
+++ b/tests/test_request_params/test_file/test_optional.py
@@ -1,12 +1,9 @@
from typing import Annotated, Optional
import pytest
-from dirty_equals import IsDict
from fastapi import FastAPI, File, UploadFile
from fastapi.testclient import TestClient
-from tests.utils import needs_pydanticv2
-
from .utils import get_body_model_name
app = FastAPI()
@@ -38,21 +35,13 @@ def test_optional_schema(path: str):
assert app.openapi()["components"]["schemas"][body_model_name] == {
"properties": {
- "p": (
- IsDict(
- {
- "anyOf": [
- {"type": "string", "format": "binary"},
- {"type": "null"},
- ],
- "title": "P",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "P", "type": "string", "format": "binary"}
- )
- ),
+ "p": {
+ "anyOf": [
+ {"type": "string", "format": "binary"},
+ {"type": "null"},
+ ],
+ "title": "P",
+ }
},
"title": body_model_name,
"type": "object",
@@ -118,21 +107,13 @@ def test_optional_alias_schema(path: str):
assert app.openapi()["components"]["schemas"][body_model_name] == {
"properties": {
- "p_alias": (
- IsDict(
- {
- "anyOf": [
- {"type": "string", "format": "binary"},
- {"type": "null"},
- ],
- "title": "P Alias",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "P Alias", "type": "string", "format": "binary"}
- )
- ),
+ "p_alias": {
+ "anyOf": [
+ {"type": "string", "format": "binary"},
+ {"type": "null"},
+ ],
+ "title": "P Alias",
+ }
},
"title": body_model_name,
"type": "object",
@@ -204,7 +185,6 @@ def read_optional_uploadfile_validation_alias(
return {"file_size": p.size if p else None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -218,28 +198,19 @@ def test_optional_validation_alias_schema(path: str):
assert app.openapi()["components"]["schemas"][body_model_name] == {
"properties": {
- "p_val_alias": (
- IsDict(
- {
- "anyOf": [
- {"type": "string", "format": "binary"},
- {"type": "null"},
- ],
- "title": "P Val Alias",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "P Val Alias", "type": "string", "format": "binary"}
- )
- ),
+ "p_val_alias": {
+ "anyOf": [
+ {"type": "string", "format": "binary"},
+ {"type": "null"},
+ ],
+ "title": "P Val Alias",
+ }
},
"title": body_model_name,
"type": "object",
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -254,7 +225,6 @@ def test_optional_validation_alias_missing(path: str):
assert response.json() == {"file_size": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -269,7 +239,6 @@ def test_optional_validation_alias_by_name(path: str):
assert response.json() == {"file_size": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -312,7 +281,6 @@ def read_optional_uploadfile_alias_and_validation_alias(
return {"file_size": p.size if p else None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -326,28 +294,19 @@ def test_optional_alias_and_validation_alias_schema(path: str):
assert app.openapi()["components"]["schemas"][body_model_name] == {
"properties": {
- "p_val_alias": (
- IsDict(
- {
- "anyOf": [
- {"type": "string", "format": "binary"},
- {"type": "null"},
- ],
- "title": "P Val Alias",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "P Val Alias", "type": "string", "format": "binary"}
- )
- ),
+ "p_val_alias": {
+ "anyOf": [
+ {"type": "string", "format": "binary"},
+ {"type": "null"},
+ ],
+ "title": "P Val Alias",
+ }
},
"title": body_model_name,
"type": "object",
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -362,7 +321,6 @@ def test_optional_alias_and_validation_alias_missing(path: str):
assert response.json() == {"file_size": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -377,7 +335,6 @@ def test_optional_alias_and_validation_alias_by_name(path: str):
assert response.json() == {"file_size": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -392,7 +349,6 @@ def test_optional_alias_and_validation_alias_by_alias(path: str):
assert response.json() == {"file_size": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
diff --git a/tests/test_request_params/test_file/test_optional_list.py b/tests/test_request_params/test_file/test_optional_list.py
index af9fe552c9..162fbe08ae 100644
--- a/tests/test_request_params/test_file/test_optional_list.py
+++ b/tests/test_request_params/test_file/test_optional_list.py
@@ -1,12 +1,9 @@
from typing import Annotated, Optional
import pytest
-from dirty_equals import IsDict
from fastapi import FastAPI, File, UploadFile
from fastapi.testclient import TestClient
-from tests.utils import needs_pydanticv2
-
from .utils import get_body_model_name
app = FastAPI()
@@ -40,28 +37,16 @@ def test_optional_list_schema(path: str):
assert app.openapi()["components"]["schemas"][body_model_name] == {
"properties": {
- "p": (
- IsDict(
+ "p": {
+ "anyOf": [
{
- "anyOf": [
- {
- "type": "array",
- "items": {"type": "string", "format": "binary"},
- },
- {"type": "null"},
- ],
- "title": "P",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "P",
"type": "array",
"items": {"type": "string", "format": "binary"},
},
- )
- ),
+ {"type": "null"},
+ ],
+ "title": "P",
+ }
},
"title": body_model_name,
"type": "object",
@@ -127,28 +112,16 @@ def test_optional_list_alias_schema(path: str):
assert app.openapi()["components"]["schemas"][body_model_name] == {
"properties": {
- "p_alias": (
- IsDict(
+ "p_alias": {
+ "anyOf": [
{
- "anyOf": [
- {
- "type": "array",
- "items": {"type": "string", "format": "binary"},
- },
- {"type": "null"},
- ],
- "title": "P Alias",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "P Alias",
"type": "array",
"items": {"type": "string", "format": "binary"},
- }
- )
- ),
+ },
+ {"type": "null"},
+ ],
+ "title": "P Alias",
+ }
},
"title": body_model_name,
"type": "object",
@@ -217,7 +190,6 @@ def read_optional_list_uploadfile_validation_alias(
return {"file_size": [file.size for file in p] if p else None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -231,35 +203,22 @@ def test_optional_validation_alias_schema(path: str):
assert app.openapi()["components"]["schemas"][body_model_name] == {
"properties": {
- "p_val_alias": (
- IsDict(
+ "p_val_alias": {
+ "anyOf": [
{
- "anyOf": [
- {
- "type": "array",
- "items": {"type": "string", "format": "binary"},
- },
- {"type": "null"},
- ],
- "title": "P Val Alias",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "P Val Alias",
"type": "array",
"items": {"type": "string", "format": "binary"},
- }
- )
- ),
+ },
+ {"type": "null"},
+ ],
+ "title": "P Val Alias",
+ }
},
"title": body_model_name,
"type": "object",
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -274,7 +233,6 @@ def test_optional_validation_alias_missing(path: str):
assert response.json() == {"file_size": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -289,7 +247,6 @@ def test_optional_validation_alias_by_name(path: str):
assert response.json() == {"file_size": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -329,7 +286,6 @@ def read_optional_list_uploadfile_alias_and_validation_alias(
return {"file_size": [file.size for file in p] if p else None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -343,35 +299,22 @@ def test_optional_list_alias_and_validation_alias_schema(path: str):
assert app.openapi()["components"]["schemas"][body_model_name] == {
"properties": {
- "p_val_alias": (
- IsDict(
+ "p_val_alias": {
+ "anyOf": [
{
- "anyOf": [
- {
- "type": "array",
- "items": {"type": "string", "format": "binary"},
- },
- {"type": "null"},
- ],
- "title": "P Val Alias",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "P Val Alias",
"type": "array",
"items": {"type": "string", "format": "binary"},
- }
- )
- ),
+ },
+ {"type": "null"},
+ ],
+ "title": "P Val Alias",
+ }
},
"title": body_model_name,
"type": "object",
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -386,7 +329,6 @@ def test_optional_list_alias_and_validation_alias_missing(path: str):
assert response.json() == {"file_size": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -401,7 +343,6 @@ def test_optional_list_alias_and_validation_alias_by_name(path: str):
assert response.json() == {"file_size": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -416,7 +357,6 @@ def test_optional_list_alias_and_validation_alias_by_alias(path: str):
assert response.json() == {"file_size": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
diff --git a/tests/test_request_params/test_file/test_required.py b/tests/test_request_params/test_file/test_required.py
index 2979a1040a..a0f9d23a6b 100644
--- a/tests/test_request_params/test_file/test_required.py
+++ b/tests/test_request_params/test_file/test_required.py
@@ -1,12 +1,9 @@
from typing import Annotated
import pytest
-from dirty_equals import IsDict
from fastapi import FastAPI, File, UploadFile
from fastapi.testclient import TestClient
-from tests.utils import needs_pydanticv2
-
from .utils import get_body_model_name
app = FastAPI()
@@ -57,29 +54,16 @@ def test_required_missing(path: str):
client = TestClient(app)
response = client.post(path)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "p"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "p"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -144,29 +128,16 @@ def test_required_alias_missing(path: str):
client = TestClient(app)
response = client.post(path)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "p_alias"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "p_alias"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_alias"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -180,29 +151,16 @@ def test_required_alias_by_name(path: str):
client = TestClient(app)
response = client.post(path, files=[("p", b"hello")])
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "p_alias"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "p_alias"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_alias"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -242,7 +200,6 @@ def read_required_uploadfile_validation_alias(
return {"file_size": p.size}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -268,7 +225,6 @@ def test_required_validation_alias_schema(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -295,7 +251,6 @@ def test_required_validation_alias_missing(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -320,7 +275,6 @@ def test_required_validation_alias_by_name(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -359,7 +313,6 @@ def read_required_uploadfile_alias_and_validation_alias(
return {"file_size": p.size}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -385,7 +338,6 @@ def test_required_alias_and_validation_alias_schema(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -412,7 +364,6 @@ def test_required_alias_and_validation_alias_missing(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -440,7 +391,6 @@ def test_required_alias_and_validation_alias_by_name(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -465,7 +415,6 @@ def test_required_alias_and_validation_alias_by_alias(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
diff --git a/tests/test_request_params/test_form/test_list.py b/tests/test_request_params/test_form/test_list.py
index 9f45aa755d..abe781c945 100644
--- a/tests/test_request_params/test_form/test_list.py
+++ b/tests/test_request_params/test_form/test_list.py
@@ -1,13 +1,11 @@
from typing import Annotated
import pytest
-from dirty_equals import IsDict, IsOneOf, IsPartialDict
+from dirty_equals import IsOneOf, IsPartialDict
from fastapi import FastAPI, Form
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
-from tests.utils import needs_pydanticv2
-
from .utils import get_body_model_name
app = FastAPI()
@@ -60,28 +58,16 @@ def test_required_list_str_missing(path: str):
client = TestClient(app)
response = client.post(path)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "p"],
- "msg": "Field required",
- "input": IsOneOf(None, {}),
- }
- ]
- }
- ) | IsDict(
- {
- "detail": [
- {
- "loc": ["body", "p"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -148,29 +134,16 @@ def test_required_list_alias_missing(path: str):
client = TestClient(app)
response = client.post(path)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "p_alias"],
- "msg": "Field required",
- "input": IsOneOf(None, {}),
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "p_alias"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -184,29 +157,16 @@ def test_required_list_alias_by_name(path: str):
client = TestClient(app)
response = client.post(path, data={"p": ["hello", "world"]})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "p_alias"],
- "msg": "Field required",
- "input": IsOneOf(None, {"p": ["hello", "world"]}),
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "p_alias"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {"p": ["hello", "world"]}),
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -247,7 +207,6 @@ async def read_model_required_list_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/required-list-validation-alias", "/model-required-list-validation-alias"],
@@ -270,7 +229,6 @@ def test_required_list_validation_alias_schema(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -297,7 +255,6 @@ def test_required_list_validation_alias_missing(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -322,7 +279,6 @@ def test_required_list_validation_alias_by_name(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/required-list-validation-alias", "/model-required-list-validation-alias"],
@@ -363,7 +319,6 @@ def read_model_required_list_alias_and_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -389,7 +344,6 @@ def test_required_list_alias_and_validation_alias_schema(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -416,7 +370,6 @@ def test_required_list_alias_and_validation_alias_missing(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -446,7 +399,6 @@ def test_required_list_alias_and_validation_alias_by_name(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -470,7 +422,6 @@ def test_required_list_alias_and_validation_alias_by_alias(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
diff --git a/tests/test_request_params/test_form/test_optional_list.py b/tests/test_request_params/test_form/test_optional_list.py
index 0af6d34777..6d1957a18c 100644
--- a/tests/test_request_params/test_form/test_optional_list.py
+++ b/tests/test_request_params/test_form/test_optional_list.py
@@ -1,13 +1,10 @@
from typing import Annotated, Optional
import pytest
-from dirty_equals import IsDict
from fastapi import FastAPI, Form
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
-from tests.utils import needs_pydanticv2
-
from .utils import get_body_model_name
app = FastAPI()
@@ -40,30 +37,19 @@ def test_optional_list_str_schema(path: str):
openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path)
- assert app.openapi()["components"]["schemas"][body_model_name] == IsDict(
- {
- "properties": {
- "p": {
- "anyOf": [
- {"items": {"type": "string"}, "type": "array"},
- {"type": "null"},
- ],
- "title": "P",
- },
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P",
},
- "title": body_model_name,
- "type": "object",
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "properties": {
- "p": {"items": {"type": "string"}, "type": "array", "title": "P"},
- },
- "title": body_model_name,
- "type": "object",
- }
- )
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
@pytest.mark.parametrize(
@@ -121,34 +107,19 @@ def test_optional_list_str_alias_schema(path: str):
openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path)
- assert app.openapi()["components"]["schemas"][body_model_name] == IsDict(
- {
- "properties": {
- "p_alias": {
- "anyOf": [
- {"items": {"type": "string"}, "type": "array"},
- {"type": "null"},
- ],
- "title": "P Alias",
- },
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_alias": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P Alias",
},
- "title": body_model_name,
- "type": "object",
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "properties": {
- "p_alias": {
- "items": {"type": "string"},
- "type": "array",
- "title": "P Alias",
- },
- },
- "title": body_model_name,
- "type": "object",
- }
- )
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
@pytest.mark.parametrize(
@@ -211,7 +182,6 @@ def read_model_optional_list_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/optional-list-validation-alias", "/model-optional-list-validation-alias"],
@@ -220,37 +190,21 @@ def test_optional_list_validation_alias_schema(path: str):
openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path)
- assert app.openapi()["components"]["schemas"][body_model_name] == IsDict(
- {
- "properties": {
- "p_val_alias": {
- "anyOf": [
- {"items": {"type": "string"}, "type": "array"},
- {"type": "null"},
- ],
- "title": "P Val Alias",
- },
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P Val Alias",
},
- "title": body_model_name,
- "type": "object",
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "properties": {
- "p_val_alias": {
- "items": {"type": "string"},
- "type": "array",
- "title": "P Val Alias",
- },
- },
- "title": body_model_name,
- "type": "object",
- }
- )
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/optional-list-validation-alias", "/model-optional-list-validation-alias"],
@@ -262,7 +216,6 @@ def test_optional_list_validation_alias_missing(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -277,7 +230,6 @@ def test_optional_list_validation_alias_by_name(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/optional-list-validation-alias", "/model-optional-list-validation-alias"],
@@ -321,7 +273,6 @@ def read_model_optional_list_alias_and_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -333,37 +284,21 @@ def test_optional_list_alias_and_validation_alias_schema(path: str):
openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path)
- assert app.openapi()["components"]["schemas"][body_model_name] == IsDict(
- {
- "properties": {
- "p_val_alias": {
- "anyOf": [
- {"items": {"type": "string"}, "type": "array"},
- {"type": "null"},
- ],
- "title": "P Val Alias",
- },
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P Val Alias",
},
- "title": body_model_name,
- "type": "object",
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "properties": {
- "p_val_alias": {
- "items": {"type": "string"},
- "type": "array",
- "title": "P Val Alias",
- },
- },
- "title": body_model_name,
- "type": "object",
- }
- )
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -378,7 +313,6 @@ def test_optional_list_alias_and_validation_alias_missing(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -393,7 +327,6 @@ def test_optional_list_alias_and_validation_alias_by_name(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -408,7 +341,6 @@ def test_optional_list_alias_and_validation_alias_by_alias(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
diff --git a/tests/test_request_params/test_form/test_optional_str.py b/tests/test_request_params/test_form/test_optional_str.py
index 92329216ec..810e83caa3 100644
--- a/tests/test_request_params/test_form/test_optional_str.py
+++ b/tests/test_request_params/test_form/test_optional_str.py
@@ -1,13 +1,10 @@
from typing import Annotated, Optional
import pytest
-from dirty_equals import IsDict
from fastapi import FastAPI, Form
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
-from tests.utils import needs_pydanticv2
-
from .utils import get_body_model_name
app = FastAPI()
@@ -38,27 +35,16 @@ def test_optional_str_schema(path: str):
openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path)
- assert app.openapi()["components"]["schemas"][body_model_name] == IsDict(
- {
- "properties": {
- "p": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "P",
- },
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P",
},
- "title": body_model_name,
- "type": "object",
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "properties": {
- "p": {"type": "string", "title": "P"},
- },
- "title": body_model_name,
- "type": "object",
- }
- )
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
@pytest.mark.parametrize(
@@ -114,27 +100,16 @@ def test_optional_str_alias_schema(path: str):
openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path)
- assert app.openapi()["components"]["schemas"][body_model_name] == IsDict(
- {
- "properties": {
- "p_alias": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "P Alias",
- },
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_alias": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P Alias",
},
- "title": body_model_name,
- "type": "object",
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "properties": {
- "p_alias": {"type": "string", "title": "P Alias"},
- },
- "title": body_model_name,
- "type": "object",
- }
- )
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
@pytest.mark.parametrize(
@@ -194,7 +169,6 @@ def read_model_optional_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/optional-validation-alias", "/model-optional-validation-alias"],
@@ -203,30 +177,18 @@ def test_optional_validation_alias_schema(path: str):
openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path)
- assert app.openapi()["components"]["schemas"][body_model_name] == IsDict(
- {
- "properties": {
- "p_val_alias": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "P Val Alias",
- },
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P Val Alias",
},
- "title": body_model_name,
- "type": "object",
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "properties": {
- "p_val_alias": {"type": "string", "title": "P Val Alias"},
- },
- "title": body_model_name,
- "type": "object",
- }
- )
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/optional-validation-alias", "/model-optional-validation-alias"],
@@ -238,7 +200,6 @@ def test_optional_validation_alias_missing(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -253,7 +214,6 @@ def test_optional_validation_alias_by_name(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -298,7 +258,6 @@ def read_model_optional_alias_and_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -310,30 +269,18 @@ def test_optional_alias_and_validation_alias_schema(path: str):
openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path)
- assert app.openapi()["components"]["schemas"][body_model_name] == IsDict(
- {
- "properties": {
- "p_val_alias": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "P Val Alias",
- },
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P Val Alias",
},
- "title": body_model_name,
- "type": "object",
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "properties": {
- "p_val_alias": {"type": "string", "title": "P Val Alias"},
- },
- "title": body_model_name,
- "type": "object",
- }
- )
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -348,7 +295,6 @@ def test_optional_alias_and_validation_alias_missing(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -363,7 +309,6 @@ def test_optional_alias_and_validation_alias_by_name(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -378,7 +323,6 @@ def test_optional_alias_and_validation_alias_by_alias(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
diff --git a/tests/test_request_params/test_form/test_required_str.py b/tests/test_request_params/test_form/test_required_str.py
index 1e33038040..7c9523b308 100644
--- a/tests/test_request_params/test_form/test_required_str.py
+++ b/tests/test_request_params/test_form/test_required_str.py
@@ -1,13 +1,11 @@
from typing import Annotated
import pytest
-from dirty_equals import IsDict, IsOneOf
+from dirty_equals import IsOneOf
from fastapi import FastAPI, Form
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
-from tests.utils import needs_pydanticv2
-
from .utils import get_body_model_name
app = FastAPI()
@@ -56,29 +54,16 @@ def test_required_str_missing(path: str):
client = TestClient(app)
response = client.post(path)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "p"],
- "msg": "Field required",
- "input": IsOneOf(None, {}),
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "p"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -139,29 +124,16 @@ def test_required_alias_missing(path: str):
client = TestClient(app)
response = client.post(path)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "p_alias"],
- "msg": "Field required",
- "input": IsOneOf(None, {}),
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "p_alias"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -172,29 +144,16 @@ def test_required_alias_by_name(path: str):
client = TestClient(app)
response = client.post(path, data={"p": "hello"})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "p_alias"],
- "msg": "Field required",
- "input": IsOneOf(None, {"p": "hello"}),
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "p_alias"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {"p": "hello"}),
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -232,7 +191,6 @@ def read_model_required_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/required-validation-alias", "/model-required-validation-alias"],
@@ -251,7 +209,6 @@ def test_required_validation_alias_schema(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -278,7 +235,6 @@ def test_required_validation_alias_missing(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -303,7 +259,6 @@ def test_required_validation_alias_by_name(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -347,7 +302,6 @@ def read_model_required_alias_and_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -369,7 +323,6 @@ def test_required_alias_and_validation_alias_schema(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -396,7 +349,6 @@ def test_required_alias_and_validation_alias_missing(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -424,7 +376,6 @@ def test_required_alias_and_validation_alias_by_name(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -449,7 +400,6 @@ def test_required_alias_and_validation_alias_by_alias(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
diff --git a/tests/test_request_params/test_header/test_list.py b/tests/test_request_params/test_header/test_list.py
index 62f9f36ff6..489a6b3e7d 100644
--- a/tests/test_request_params/test_header/test_list.py
+++ b/tests/test_request_params/test_header/test_list.py
@@ -1,13 +1,11 @@
from typing import Annotated
import pytest
-from dirty_equals import AnyThing, IsDict, IsOneOf, IsPartialDict
+from dirty_equals import AnyThing, IsOneOf, IsPartialDict
from fastapi import FastAPI, Header
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
-from tests.utils import needs_pydanticv2
-
app = FastAPI()
# =====================================================================================
@@ -55,28 +53,16 @@ def test_required_list_str_missing(path: str):
client = TestClient(app)
response = client.get(path)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["header", "p"],
- "msg": "Field required",
- "input": AnyThing,
- }
- ]
- }
- ) | IsDict(
- {
- "detail": [
- {
- "loc": ["header", "p"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["header", "p"],
+ "msg": "Field required",
+ "input": AnyThing,
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -137,29 +123,16 @@ def test_required_list_alias_missing(path: str):
client = TestClient(app)
response = client.get(path)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["header", "p_alias"],
- "msg": "Field required",
- "input": AnyThing,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["header", "p_alias"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["header", "p_alias"],
+ "msg": "Field required",
+ "input": AnyThing,
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -173,29 +146,16 @@ def test_required_list_alias_by_name(path: str):
client = TestClient(app)
response = client.get(path, headers=[("p", "hello"), ("p", "world")])
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["header", "p_alias"],
- "msg": "Field required",
- "input": IsOneOf(None, IsPartialDict({"p": ["hello", "world"]})),
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["header", "p_alias"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["header", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, IsPartialDict({"p": ["hello", "world"]})),
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -234,7 +194,6 @@ async def read_model_required_list_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/required-list-validation-alias", "/model-required-list-validation-alias"],
@@ -254,7 +213,6 @@ def test_required_list_validation_alias_schema(path: str):
]
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -281,7 +239,6 @@ def test_required_list_validation_alias_missing(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -306,7 +263,6 @@ def test_required_list_validation_alias_by_name(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/required-list-validation-alias", "/model-required-list-validation-alias"],
@@ -343,7 +299,6 @@ def read_model_required_list_alias_and_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -366,7 +321,6 @@ def test_required_list_alias_and_validation_alias_schema(path: str):
]
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -393,7 +347,6 @@ def test_required_list_alias_and_validation_alias_missing(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -423,7 +376,6 @@ def test_required_list_alias_and_validation_alias_by_name(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -450,7 +402,6 @@ def test_required_list_alias_and_validation_alias_by_alias(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
diff --git a/tests/test_request_params/test_header/test_optional_list.py b/tests/test_request_params/test_header/test_optional_list.py
index 88243f05a9..5dd4ea9ade 100644
--- a/tests/test_request_params/test_header/test_optional_list.py
+++ b/tests/test_request_params/test_header/test_optional_list.py
@@ -1,13 +1,10 @@
from typing import Annotated, Optional
import pytest
-from dirty_equals import IsDict
from fastapi import FastAPI, Header
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
-from tests.utils import needs_pydanticv2
-
app = FastAPI()
# =====================================================================================
@@ -38,29 +35,18 @@ async def read_model_optional_list_str(
)
def test_optional_list_str_schema(path: str):
assert app.openapi()["paths"][path]["get"]["parameters"] == [
- IsDict(
- {
- "required": False,
- "schema": {
- "anyOf": [
- {"items": {"type": "string"}, "type": "array"},
- {"type": "null"},
- ],
- "title": "P",
- },
- "name": "p",
- "in": "header",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "required": False,
- "schema": {"items": {"type": "string"}, "type": "array", "title": "P"},
- "name": "p",
- "in": "header",
- }
- )
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P",
+ },
+ "name": "p",
+ "in": "header",
+ }
]
@@ -114,33 +100,18 @@ async def read_model_optional_list_alias(
)
def test_optional_list_str_alias_schema(path: str):
assert app.openapi()["paths"][path]["get"]["parameters"] == [
- IsDict(
- {
- "required": False,
- "schema": {
- "anyOf": [
- {"items": {"type": "string"}, "type": "array"},
- {"type": "null"},
- ],
- "title": "P Alias",
- },
- "name": "p_alias",
- "in": "header",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "required": False,
- "schema": {
- "items": {"type": "string"},
- "type": "array",
- "title": "P Alias",
- },
- "name": "p_alias",
- "in": "header",
- }
- )
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P Alias",
+ },
+ "name": "p_alias",
+ "in": "header",
+ }
]
@@ -202,7 +173,6 @@ def read_model_optional_list_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/optional-list-validation-alias", "/model-optional-list-validation-alias"],
@@ -224,7 +194,6 @@ def test_optional_list_validation_alias_schema(path: str):
]
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/optional-list-validation-alias", "/model-optional-list-validation-alias"],
@@ -236,7 +205,6 @@ def test_optional_list_validation_alias_missing(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -251,7 +219,6 @@ def test_optional_list_validation_alias_by_name(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/optional-list-validation-alias", "/model-optional-list-validation-alias"],
@@ -291,7 +258,6 @@ def read_model_optional_list_alias_and_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -316,7 +282,6 @@ def test_optional_list_alias_and_validation_alias_schema(path: str):
]
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -331,7 +296,6 @@ def test_optional_list_alias_and_validation_alias_missing(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -346,7 +310,6 @@ def test_optional_list_alias_and_validation_alias_by_name(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -361,7 +324,6 @@ def test_optional_list_alias_and_validation_alias_by_alias(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
diff --git a/tests/test_request_params/test_header/test_optional_str.py b/tests/test_request_params/test_header/test_optional_str.py
index e40b1669ee..0bd0eddc1b 100644
--- a/tests/test_request_params/test_header/test_optional_str.py
+++ b/tests/test_request_params/test_header/test_optional_str.py
@@ -1,13 +1,10 @@
from typing import Annotated, Optional
import pytest
-from dirty_equals import IsDict
from fastapi import FastAPI, Header
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
-from tests.utils import needs_pydanticv2
-
app = FastAPI()
# =====================================================================================
@@ -34,26 +31,15 @@ async def read_model_optional_str(p: Annotated[HeaderModelOptionalStr, Header()]
)
def test_optional_str_schema(path: str):
assert app.openapi()["paths"][path]["get"]["parameters"] == [
- IsDict(
- {
- "required": False,
- "schema": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "P",
- },
- "name": "p",
- "in": "header",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "required": False,
- "schema": {"title": "P", "type": "string"},
- "name": "p",
- "in": "header",
- }
- )
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P",
+ },
+ "name": "p",
+ "in": "header",
+ }
]
@@ -105,26 +91,15 @@ async def read_model_optional_alias(p: Annotated[HeaderModelOptionalAlias, Heade
)
def test_optional_str_alias_schema(path: str):
assert app.openapi()["paths"][path]["get"]["parameters"] == [
- IsDict(
- {
- "required": False,
- "schema": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "P Alias",
- },
- "name": "p_alias",
- "in": "header",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "required": False,
- "schema": {"title": "P Alias", "type": "string"},
- "name": "p_alias",
- "in": "header",
- }
- )
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P Alias",
+ },
+ "name": "p_alias",
+ "in": "header",
+ }
]
@@ -186,7 +161,6 @@ def read_model_optional_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/optional-validation-alias", "/model-optional-validation-alias"],
@@ -205,7 +179,6 @@ def test_optional_validation_alias_schema(path: str):
]
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/optional-validation-alias", "/model-optional-validation-alias"],
@@ -217,7 +190,6 @@ def test_optional_validation_alias_missing(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -232,7 +204,6 @@ def test_optional_validation_alias_by_name(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -271,7 +242,6 @@ def read_model_optional_alias_and_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -293,7 +263,6 @@ def test_optional_alias_and_validation_alias_schema(path: str):
]
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -308,7 +277,6 @@ def test_optional_alias_and_validation_alias_missing(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -323,7 +291,6 @@ def test_optional_alias_and_validation_alias_by_name(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -338,7 +305,6 @@ def test_optional_alias_and_validation_alias_by_alias(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
diff --git a/tests/test_request_params/test_header/test_required_str.py b/tests/test_request_params/test_header/test_required_str.py
index 23554d3e4a..20dd296570 100644
--- a/tests/test_request_params/test_header/test_required_str.py
+++ b/tests/test_request_params/test_header/test_required_str.py
@@ -1,13 +1,11 @@
from typing import Annotated
import pytest
-from dirty_equals import AnyThing, IsDict, IsOneOf, IsPartialDict
+from dirty_equals import AnyThing, IsOneOf, IsPartialDict
from fastapi import FastAPI, Header
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
-from tests.utils import needs_pydanticv2
-
app = FastAPI()
# =====================================================================================
@@ -51,29 +49,16 @@ def test_required_str_missing(path: str):
client = TestClient(app)
response = client.get(path)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["header", "p"],
- "msg": "Field required",
- "input": AnyThing,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["header", "p"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["header", "p"],
+ "msg": "Field required",
+ "input": AnyThing,
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -128,29 +113,16 @@ def test_required_alias_missing(path: str):
client = TestClient(app)
response = client.get(path)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["header", "p_alias"],
- "msg": "Field required",
- "input": AnyThing,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["header", "p_alias"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["header", "p_alias"],
+ "msg": "Field required",
+ "input": AnyThing,
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -164,29 +136,16 @@ def test_required_alias_by_name(path: str):
client = TestClient(app)
response = client.get(path, headers={"p": "hello"})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["header", "p_alias"],
- "msg": "Field required",
- "input": IsOneOf(None, IsPartialDict({"p": "hello"})),
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["header", "p_alias"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["header", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, IsPartialDict({"p": "hello"})),
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -225,7 +184,6 @@ def read_model_required_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/required-validation-alias", "/model-required-validation-alias"],
@@ -241,7 +199,6 @@ def test_required_validation_alias_schema(path: str):
]
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -268,7 +225,6 @@ def test_required_validation_alias_missing(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -293,7 +249,6 @@ def test_required_validation_alias_by_name(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -331,7 +286,6 @@ def read_model_required_alias_and_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -350,7 +304,6 @@ def test_required_alias_and_validation_alias_schema(path: str):
]
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -377,7 +330,6 @@ def test_required_alias_and_validation_alias_missing(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -408,7 +360,6 @@ def test_required_alias_and_validation_alias_by_name(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -436,7 +387,6 @@ def test_required_alias_and_validation_alias_by_alias(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
diff --git a/tests/test_request_params/test_path/test_required_str.py b/tests/test_request_params/test_path/test_required_str.py
index ecd4eb61cd..b2d63667e2 100644
--- a/tests/test_request_params/test_path/test_required_str.py
+++ b/tests/test_request_params/test_path/test_required_str.py
@@ -4,8 +4,6 @@ import pytest
from fastapi import FastAPI, Path
from fastapi.testclient import TestClient
-from tests.utils import needs_pydanticv2
-
app = FastAPI()
@@ -45,14 +43,12 @@ def read_required_alias_and_validation_alias(
"p_val_alias",
"P Val Alias",
id="required-validation-alias",
- marks=needs_pydanticv2,
),
pytest.param(
"/required-alias-and-validation-alias/{p_val_alias}",
"p_val_alias",
"P Val Alias",
id="required-alias-and-validation-alias",
- marks=needs_pydanticv2,
),
],
)
@@ -75,12 +71,10 @@ def test_schema(path: str, expected_name: str, expected_title: str):
pytest.param(
"/required-validation-alias",
id="required-validation-alias",
- marks=needs_pydanticv2,
),
pytest.param(
"/required-alias-and-validation-alias",
id="required-alias-and-validation-alias",
- marks=needs_pydanticv2,
),
],
)
diff --git a/tests/test_request_params/test_query/test_list.py b/tests/test_request_params/test_query/test_list.py
index 6a3000fbf6..e933da214d 100644
--- a/tests/test_request_params/test_query/test_list.py
+++ b/tests/test_request_params/test_query/test_list.py
@@ -1,13 +1,11 @@
from typing import Annotated
import pytest
-from dirty_equals import IsDict, IsOneOf
+from dirty_equals import IsOneOf
from fastapi import FastAPI, Query
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
-from tests.utils import needs_pydanticv2
-
app = FastAPI()
# =====================================================================================
@@ -55,28 +53,16 @@ def test_required_list_str_missing(path: str):
client = TestClient(app)
response = client.get(path)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "p"],
- "msg": "Field required",
- "input": IsOneOf(None, {}),
- }
- ]
- }
- ) | IsDict(
- {
- "detail": [
- {
- "loc": ["query", "p"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "p"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -137,29 +123,16 @@ def test_required_list_alias_missing(path: str):
client = TestClient(app)
response = client.get(path)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "p_alias"],
- "msg": "Field required",
- "input": IsOneOf(None, {}),
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "p_alias"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -173,29 +146,16 @@ def test_required_list_alias_by_name(path: str):
client = TestClient(app)
response = client.get(f"{path}?p=hello&p=world")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "p_alias"],
- "msg": "Field required",
- "input": IsOneOf(None, {"p": ["hello", "world"]}),
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "p_alias"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {"p": ["hello", "world"]}),
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -234,7 +194,6 @@ async def read_model_required_list_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/required-list-validation-alias", "/model-required-list-validation-alias"],
@@ -254,7 +213,6 @@ def test_required_list_validation_alias_schema(path: str):
]
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -281,7 +239,6 @@ def test_required_list_validation_alias_missing(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -306,7 +263,6 @@ def test_required_list_validation_alias_by_name(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/required-list-validation-alias", "/model-required-list-validation-alias"],
@@ -341,7 +297,6 @@ def read_model_required_list_alias_and_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -364,7 +319,6 @@ def test_required_list_alias_and_validation_alias_schema(path: str):
]
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -391,7 +345,6 @@ def test_required_list_alias_and_validation_alias_missing(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -426,7 +379,6 @@ def test_required_list_alias_and_validation_alias_by_name(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -453,7 +405,6 @@ def test_required_list_alias_and_validation_alias_by_alias(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
diff --git a/tests/test_request_params/test_query/test_optional_list.py b/tests/test_request_params/test_query/test_optional_list.py
index f4b8ec6a82..351e03a713 100644
--- a/tests/test_request_params/test_query/test_optional_list.py
+++ b/tests/test_request_params/test_query/test_optional_list.py
@@ -1,13 +1,10 @@
from typing import Annotated, Optional
import pytest
-from dirty_equals import IsDict
from fastapi import FastAPI, Query
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
-from tests.utils import needs_pydanticv2
-
app = FastAPI()
# =====================================================================================
@@ -38,29 +35,18 @@ async def read_model_optional_list_str(
)
def test_optional_list_str_schema(path: str):
assert app.openapi()["paths"][path]["get"]["parameters"] == [
- IsDict(
- {
- "required": False,
- "schema": {
- "anyOf": [
- {"items": {"type": "string"}, "type": "array"},
- {"type": "null"},
- ],
- "title": "P",
- },
- "name": "p",
- "in": "query",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "required": False,
- "schema": {"items": {"type": "string"}, "type": "array", "title": "P"},
- "name": "p",
- "in": "query",
- }
- )
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P",
+ },
+ "name": "p",
+ "in": "query",
+ }
]
@@ -114,33 +100,18 @@ async def read_model_optional_list_alias(
)
def test_optional_list_str_alias_schema(path: str):
assert app.openapi()["paths"][path]["get"]["parameters"] == [
- IsDict(
- {
- "required": False,
- "schema": {
- "anyOf": [
- {"items": {"type": "string"}, "type": "array"},
- {"type": "null"},
- ],
- "title": "P Alias",
- },
- "name": "p_alias",
- "in": "query",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "required": False,
- "schema": {
- "items": {"type": "string"},
- "type": "array",
- "title": "P Alias",
- },
- "name": "p_alias",
- "in": "query",
- }
- )
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P Alias",
+ },
+ "name": "p_alias",
+ "in": "query",
+ }
]
@@ -202,7 +173,6 @@ def read_model_optional_list_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/optional-list-validation-alias", "/model-optional-list-validation-alias"],
@@ -224,7 +194,6 @@ def test_optional_list_validation_alias_schema(path: str):
]
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/optional-list-validation-alias", "/model-optional-list-validation-alias"],
@@ -236,7 +205,6 @@ def test_optional_list_validation_alias_missing(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -251,7 +219,6 @@ def test_optional_list_validation_alias_by_name(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/optional-list-validation-alias", "/model-optional-list-validation-alias"],
@@ -289,7 +256,6 @@ def read_model_optional_list_alias_and_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -314,7 +280,6 @@ def test_optional_list_alias_and_validation_alias_schema(path: str):
]
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -329,7 +294,6 @@ def test_optional_list_alias_and_validation_alias_missing(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -344,7 +308,6 @@ def test_optional_list_alias_and_validation_alias_by_name(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -359,7 +322,6 @@ def test_optional_list_alias_and_validation_alias_by_alias(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
diff --git a/tests/test_request_params/test_query/test_optional_str.py b/tests/test_request_params/test_query/test_optional_str.py
index c7d20e37d1..12e1b465a7 100644
--- a/tests/test_request_params/test_query/test_optional_str.py
+++ b/tests/test_request_params/test_query/test_optional_str.py
@@ -1,13 +1,10 @@
from typing import Annotated, Optional
import pytest
-from dirty_equals import IsDict
from fastapi import FastAPI, Query
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
-from tests.utils import needs_pydanticv2
-
app = FastAPI()
# =====================================================================================
@@ -34,26 +31,15 @@ async def read_model_optional_str(p: Annotated[QueryModelOptionalStr, Query()]):
)
def test_optional_str_schema(path: str):
assert app.openapi()["paths"][path]["get"]["parameters"] == [
- IsDict(
- {
- "required": False,
- "schema": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "P",
- },
- "name": "p",
- "in": "query",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "required": False,
- "schema": {"title": "P", "type": "string"},
- "name": "p",
- "in": "query",
- }
- )
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P",
+ },
+ "name": "p",
+ "in": "query",
+ }
]
@@ -105,26 +91,15 @@ async def read_model_optional_alias(p: Annotated[QueryModelOptionalAlias, Query(
)
def test_optional_str_alias_schema(path: str):
assert app.openapi()["paths"][path]["get"]["parameters"] == [
- IsDict(
- {
- "required": False,
- "schema": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "P Alias",
- },
- "name": "p_alias",
- "in": "query",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "required": False,
- "schema": {"title": "P Alias", "type": "string"},
- "name": "p_alias",
- "in": "query",
- }
- )
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P Alias",
+ },
+ "name": "p_alias",
+ "in": "query",
+ }
]
@@ -186,7 +161,6 @@ def read_model_optional_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/optional-validation-alias", "/model-optional-validation-alias"],
@@ -205,7 +179,6 @@ def test_optional_validation_alias_schema(path: str):
]
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/optional-validation-alias", "/model-optional-validation-alias"],
@@ -217,7 +190,6 @@ def test_optional_validation_alias_missing(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -232,7 +204,6 @@ def test_optional_validation_alias_by_name(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -271,7 +242,6 @@ def read_model_optional_alias_and_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -293,7 +263,6 @@ def test_optional_alias_and_validation_alias_schema(path: str):
]
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -308,7 +277,6 @@ def test_optional_alias_and_validation_alias_missing(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -323,7 +291,6 @@ def test_optional_alias_and_validation_alias_by_name(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -338,7 +305,6 @@ def test_optional_alias_and_validation_alias_by_alias(path: str):
assert response.json() == {"p": None}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
diff --git a/tests/test_request_params/test_query/test_required_str.py b/tests/test_request_params/test_query/test_required_str.py
index ce30f3b1f8..9e7b961453 100644
--- a/tests/test_request_params/test_query/test_required_str.py
+++ b/tests/test_request_params/test_query/test_required_str.py
@@ -1,13 +1,11 @@
from typing import Annotated
import pytest
-from dirty_equals import IsDict, IsOneOf
+from dirty_equals import IsOneOf
from fastapi import FastAPI, Query
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
-from tests.utils import needs_pydanticv2
-
app = FastAPI()
# =====================================================================================
@@ -51,29 +49,16 @@ def test_required_str_missing(path: str):
client = TestClient(app)
response = client.get(path)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "p"],
- "msg": "Field required",
- "input": IsOneOf(None, {}),
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "p"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "p"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -128,29 +113,16 @@ def test_required_alias_missing(path: str):
client = TestClient(app)
response = client.get(path)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "p_alias"],
- "msg": "Field required",
- "input": IsOneOf(None, {}),
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "p_alias"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -164,32 +136,19 @@ def test_required_alias_by_name(path: str):
client = TestClient(app)
response = client.get(f"{path}?p=hello")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "p_alias"],
- "msg": "Field required",
- "input": IsOneOf(
- None,
- {"p": "hello"},
- ),
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "p_alias"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(
+ None,
+ {"p": "hello"},
+ ),
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -228,7 +187,6 @@ def read_model_required_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
["/required-validation-alias", "/model-required-validation-alias"],
@@ -244,7 +202,6 @@ def test_required_validation_alias_schema(path: str):
]
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -271,7 +228,6 @@ def test_required_validation_alias_missing(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -296,7 +252,6 @@ def test_required_validation_alias_by_name(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -334,7 +289,6 @@ def read_model_required_alias_and_validation_alias(
return {"p": p.p}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -353,7 +307,6 @@ def test_required_alias_and_validation_alias_schema(path: str):
]
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -380,7 +333,6 @@ def test_required_alias_and_validation_alias_missing(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -411,7 +363,6 @@ def test_required_alias_and_validation_alias_by_name(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
@@ -439,7 +390,6 @@ def test_required_alias_and_validation_alias_by_alias(path: str):
}
-@needs_pydanticv2
@pytest.mark.parametrize(
"path",
[
diff --git a/tests/test_response_by_alias.py b/tests/test_response_by_alias.py
index 5b241c76b6..807d2600b9 100644
--- a/tests/test_response_by_alias.py
+++ b/tests/test_response_by_alias.py
@@ -1,5 +1,4 @@
from fastapi import FastAPI
-from fastapi._compat import PYDANTIC_V2
from fastapi.testclient import TestClient
from pydantic import BaseModel, ConfigDict, Field
@@ -13,24 +12,14 @@ class Model(BaseModel):
class ModelNoAlias(BaseModel):
name: str
- if PYDANTIC_V2:
- model_config = ConfigDict(
- json_schema_extra={
- "description": (
- "response_model_by_alias=False is basically a quick hack, to support "
- "proper OpenAPI use another model with the correct field names"
- )
- }
- )
- else:
-
- class Config:
- schema_extra = {
- "description": (
- "response_model_by_alias=False is basically a quick hack, to support "
- "proper OpenAPI use another model with the correct field names"
- )
- }
+ model_config = ConfigDict(
+ json_schema_extra={
+ "description": (
+ "response_model_by_alias=False is basically a quick hack, to support "
+ "proper OpenAPI use another model with the correct field names"
+ )
+ }
+ )
@app.get("/dict", response_model=Model, response_model_by_alias=False)
diff --git a/tests/test_response_model_as_return_annotation.py b/tests/test_response_model_as_return_annotation.py
index 44e882a76e..58fba89f1a 100644
--- a/tests/test_response_model_as_return_annotation.py
+++ b/tests/test_response_model_as_return_annotation.py
@@ -7,8 +7,6 @@ from fastapi.responses import JSONResponse, Response
from fastapi.testclient import TestClient
from pydantic import BaseModel
-from tests.utils import needs_pydanticv1
-
class BaseUser(BaseModel):
name: str
@@ -511,26 +509,6 @@ def test_invalid_response_model_field():
assert "parameter response_model=None" in e.value.args[0]
-# TODO: remove when dropping Pydantic v1 support
-@needs_pydanticv1
-def test_invalid_response_model_field_pv1():
- from fastapi._compat import v1
-
- app = FastAPI()
-
- class Model(v1.BaseModel):
- foo: str
-
- with pytest.raises(FastAPIError) as e:
-
- @app.get("/")
- def read_root() -> Union[Response, Model, None]:
- return Response(content="Foo") # pragma: no cover
-
- assert "valid Pydantic field type" in e.value.args[0]
- assert "parameter response_model=None" in e.value.args[0]
-
-
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
diff --git a/tests/test_schema_compat_pydantic_v2.py b/tests/test_schema_compat_pydantic_v2.py
index 39626c0eca..737687f256 100644
--- a/tests/test_schema_compat_pydantic_v2.py
+++ b/tests/test_schema_compat_pydantic_v2.py
@@ -4,7 +4,7 @@ from fastapi.testclient import TestClient
from inline_snapshot import snapshot
from pydantic import BaseModel
-from tests.utils import needs_py310, needs_pydanticv2
+from tests.utils import needs_py310
@pytest.fixture(name="client")
@@ -32,14 +32,12 @@ def get_client():
@needs_py310
-@needs_pydanticv2
def test_get(client: TestClient):
response = client.get("/users")
assert response.json() == {"username": "alice", "role": "admin"}
@needs_py310
-@needs_pydanticv2
def test_openapi_schema(client: TestClient):
response = client.get("openapi.json")
assert response.json() == snapshot(
diff --git a/tests/test_schema_extra_examples.py b/tests/test_schema_extra_examples.py
index b313f47e90..ac8999c90a 100644
--- a/tests/test_schema_extra_examples.py
+++ b/tests/test_schema_extra_examples.py
@@ -1,9 +1,8 @@
from typing import Union
import pytest
-from dirty_equals import IsDict
from fastapi import Body, Cookie, FastAPI, Header, Path, Query
-from fastapi._compat import PYDANTIC_V2
+from fastapi.exceptions import FastAPIDeprecationWarning
from fastapi.testclient import TestClient
from pydantic import BaseModel, ConfigDict
@@ -14,20 +13,15 @@ def create_app():
class Item(BaseModel):
data: str
- if PYDANTIC_V2:
- model_config = ConfigDict(
- json_schema_extra={"example": {"data": "Data in schema_extra"}}
- )
- else:
-
- class Config:
- schema_extra = {"example": {"data": "Data in schema_extra"}}
+ model_config = ConfigDict(
+ json_schema_extra={"example": {"data": "Data in schema_extra"}}
+ )
@app.post("/schema_extra/")
def schema_extra(item: Item):
return item
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.post("/example/")
def example(item: Item = Body(example={"data": "Data in Body example"})):
@@ -44,7 +38,7 @@ def create_app():
):
return item
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.post("/example_examples/")
def example_examples(
@@ -89,7 +83,7 @@ def create_app():
# ):
# return lastname
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.get("/path_example/{item_id}")
def path_example(
@@ -107,7 +101,7 @@ def create_app():
):
return item_id
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.get("/path_example_examples/{item_id}")
def path_example_examples(
@@ -118,7 +112,7 @@ def create_app():
):
return item_id
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.get("/query_example/")
def query_example(
@@ -138,7 +132,7 @@ def create_app():
):
return data
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.get("/query_example_examples/")
def query_example_examples(
@@ -150,7 +144,7 @@ def create_app():
):
return data
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.get("/header_example/")
def header_example(
@@ -173,7 +167,7 @@ def create_app():
):
return data
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.get("/header_example_examples/")
def header_example_examples(
@@ -185,7 +179,7 @@ def create_app():
):
return data
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.get("/cookie_example/")
def cookie_example(
@@ -205,7 +199,7 @@ def create_app():
):
return data
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.get("/cookie_example_examples/")
def cookie_example_examples(
@@ -341,28 +335,13 @@ def test_openapi_schema():
"requestBody": {
"content": {
"application/json": {
- "schema": IsDict(
- {
- "$ref": "#/components/schemas/Item",
- "examples": [
- {"data": "Data in Body examples, example1"},
- {"data": "Data in Body examples, example2"},
- ],
- }
- )
- | IsDict(
- # TODO: remove this when deprecating Pydantic v1
- {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Item",
- "examples": [
- {"data": "Data in Body examples, example1"},
- {"data": "Data in Body examples, example2"},
- ],
- }
- )
+ "schema": {
+ "$ref": "#/components/schemas/Item",
+ "examples": [
+ {"data": "Data in Body examples, example1"},
+ {"data": "Data in Body examples, example2"},
+ ],
+ }
}
},
"required": True,
@@ -392,28 +371,13 @@ def test_openapi_schema():
"requestBody": {
"content": {
"application/json": {
- "schema": IsDict(
- {
- "$ref": "#/components/schemas/Item",
- "examples": [
- {"data": "examples example_examples 1"},
- {"data": "examples example_examples 2"},
- ],
- }
- )
- | IsDict(
- # TODO: remove this when deprecating Pydantic v1
- {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Item",
- "examples": [
- {"data": "examples example_examples 1"},
- {"data": "examples example_examples 2"},
- ],
- },
- ),
+ "schema": {
+ "$ref": "#/components/schemas/Item",
+ "examples": [
+ {"data": "examples example_examples 1"},
+ {"data": "examples example_examples 2"},
+ ],
+ },
"example": {"data": "Overridden example"},
}
},
@@ -544,16 +508,10 @@ def test_openapi_schema():
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Data",
- }
- )
- | IsDict(
- # TODO: Remove this when deprecating Pydantic v1
- {"title": "Data", "type": "string"}
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Data",
+ },
"example": "query1",
"name": "data",
"in": "query",
@@ -584,21 +542,11 @@ def test_openapi_schema():
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Data",
- "examples": ["query1", "query2"],
- }
- )
- | IsDict(
- # TODO: Remove this when deprecating Pydantic v1
- {
- "type": "string",
- "title": "Data",
- "examples": ["query1", "query2"],
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Data",
+ "examples": ["query1", "query2"],
+ },
"name": "data",
"in": "query",
}
@@ -628,21 +576,11 @@ def test_openapi_schema():
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Data",
- "examples": ["query1", "query2"],
- }
- )
- | IsDict(
- # TODO: Remove this when deprecating Pydantic v1
- {
- "type": "string",
- "title": "Data",
- "examples": ["query1", "query2"],
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Data",
+ "examples": ["query1", "query2"],
+ },
"example": "query_overridden",
"name": "data",
"in": "query",
@@ -673,16 +611,10 @@ def test_openapi_schema():
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Data",
- }
- )
- | IsDict(
- # TODO: Remove this when deprecating Pydantic v1
- {"title": "Data", "type": "string"}
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Data",
+ },
"example": "header1",
"name": "data",
"in": "header",
@@ -713,21 +645,11 @@ def test_openapi_schema():
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Data",
- "examples": ["header1", "header2"],
- }
- )
- | IsDict(
- # TODO: Remove this when deprecating Pydantic v1
- {
- "type": "string",
- "title": "Data",
- "examples": ["header1", "header2"],
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Data",
+ "examples": ["header1", "header2"],
+ },
"name": "data",
"in": "header",
}
@@ -757,21 +679,11 @@ def test_openapi_schema():
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Data",
- "examples": ["header1", "header2"],
- }
- )
- | IsDict(
- # TODO: Remove this when deprecating Pydantic v1
- {
- "title": "Data",
- "type": "string",
- "examples": ["header1", "header2"],
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Data",
+ "examples": ["header1", "header2"],
+ },
"example": "header_overridden",
"name": "data",
"in": "header",
@@ -802,16 +714,10 @@ def test_openapi_schema():
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Data",
- }
- )
- | IsDict(
- # TODO: Remove this when deprecating Pydantic v1
- {"title": "Data", "type": "string"}
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Data",
+ },
"example": "cookie1",
"name": "data",
"in": "cookie",
@@ -842,21 +748,11 @@ def test_openapi_schema():
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Data",
- "examples": ["cookie1", "cookie2"],
- }
- )
- | IsDict(
- # TODO: Remove this when deprecating Pydantic v1
- {
- "title": "Data",
- "type": "string",
- "examples": ["cookie1", "cookie2"],
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Data",
+ "examples": ["cookie1", "cookie2"],
+ },
"name": "data",
"in": "cookie",
}
@@ -886,21 +782,11 @@ def test_openapi_schema():
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Data",
- "examples": ["cookie1", "cookie2"],
- }
- )
- | IsDict(
- # TODO: Remove this when deprecating Pydantic v1
- {
- "title": "Data",
- "type": "string",
- "examples": ["cookie1", "cookie2"],
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Data",
+ "examples": ["cookie1", "cookie2"],
+ },
"example": "cookie_overridden",
"name": "data",
"in": "cookie",
diff --git a/tests/test_schema_ref_pydantic_v2.py b/tests/test_schema_ref_pydantic_v2.py
index 119b76a529..69cb82a356 100644
--- a/tests/test_schema_ref_pydantic_v2.py
+++ b/tests/test_schema_ref_pydantic_v2.py
@@ -6,8 +6,6 @@ from fastapi.testclient import TestClient
from inline_snapshot import snapshot
from pydantic import BaseModel, ConfigDict, Field
-from tests.utils import needs_pydanticv2
-
@pytest.fixture(name="client")
def get_client():
@@ -25,13 +23,11 @@ def get_client():
return client
-@needs_pydanticv2
def test_get(client: TestClient):
response = client.get("/")
assert response.json() == {"$ref": "some-ref"}
-@needs_pydanticv2
def test_openapi_schema(client: TestClient):
response = client.get("openapi.json")
assert response.json() == snapshot(
diff --git a/tests/test_security_oauth2.py b/tests/test_security_oauth2.py
index 804e4152db..7ad9369956 100644
--- a/tests/test_security_oauth2.py
+++ b/tests/test_security_oauth2.py
@@ -1,5 +1,4 @@
import pytest
-from dirty_equals import IsDict
from fastapi import Depends, FastAPI, Security
from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict
from fastapi.testclient import TestClient
@@ -64,79 +63,43 @@ def test_security_oauth2_password_bearer_no_header():
def test_strict_login_no_data():
response = client.post("/login")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "grant_type"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "username"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "password"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "grant_type"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "username"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "password"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "grant_type"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": None,
+ },
+ ]
+ }
def test_strict_login_no_grant_type():
response = client.post("/login", data={"username": "johndoe", "password": "secret"})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "grant_type"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "grant_type"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "grant_type"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -153,31 +116,17 @@ def test_strict_login_incorrect_grant_type(grant_type: str):
data={"username": "johndoe", "password": "secret", "grant_type": grant_type},
)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "string_pattern_mismatch",
- "loc": ["body", "grant_type"],
- "msg": "String should match pattern '^password$'",
- "input": grant_type,
- "ctx": {"pattern": "^password$"},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "grant_type"],
- "msg": 'string does not match regex "^password$"',
- "type": "value_error.str.regex",
- "ctx": {"pattern": "^password$"},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_pattern_mismatch",
+ "loc": ["body", "grant_type"],
+ "msg": "String should match pattern '^password$'",
+ "input": grant_type,
+ "ctx": {"pattern": "^password$"},
+ }
+ ]
+ }
def test_strict_login_correct_grant_type():
@@ -264,26 +213,14 @@ def test_openapi_schema():
"username": {"title": "Username", "type": "string"},
"password": {"title": "Password", "type": "string"},
"scope": {"title": "Scope", "type": "string", "default": ""},
- "client_id": IsDict(
- {
- "title": "Client Id",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Id", "type": "string"}
- ),
- "client_secret": IsDict(
- {
- "title": "Client Secret",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Secret", "type": "string"}
- ),
+ "client_id": {
+ "title": "Client Id",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "client_secret": {
+ "title": "Client Secret",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
},
},
"ValidationError": {
diff --git a/tests/test_security_oauth2_optional.py b/tests/test_security_oauth2_optional.py
index 046ac57637..57c16058af 100644
--- a/tests/test_security_oauth2_optional.py
+++ b/tests/test_security_oauth2_optional.py
@@ -1,7 +1,6 @@
from typing import Optional
import pytest
-from dirty_equals import IsDict
from fastapi import Depends, FastAPI, Security
from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict
from fastapi.testclient import TestClient
@@ -67,79 +66,43 @@ def test_security_oauth2_password_bearer_no_header():
def test_strict_login_no_data():
response = client.post("/login")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "grant_type"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "username"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "password"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "grant_type"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "username"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "password"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "grant_type"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": None,
+ },
+ ]
+ }
def test_strict_login_no_grant_type():
response = client.post("/login", data={"username": "johndoe", "password": "secret"})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "grant_type"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "grant_type"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "grant_type"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -156,31 +119,17 @@ def test_strict_login_incorrect_grant_type(grant_type: str):
data={"username": "johndoe", "password": "secret", "grant_type": grant_type},
)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "string_pattern_mismatch",
- "loc": ["body", "grant_type"],
- "msg": "String should match pattern '^password$'",
- "input": grant_type,
- "ctx": {"pattern": "^password$"},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "grant_type"],
- "msg": 'string does not match regex "^password$"',
- "type": "value_error.str.regex",
- "ctx": {"pattern": "^password$"},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_pattern_mismatch",
+ "loc": ["body", "grant_type"],
+ "msg": "String should match pattern '^password$'",
+ "input": grant_type,
+ "ctx": {"pattern": "^password$"},
+ }
+ ]
+ }
def test_strict_login_correct_data():
@@ -267,26 +216,14 @@ def test_openapi_schema():
"username": {"title": "Username", "type": "string"},
"password": {"title": "Password", "type": "string"},
"scope": {"title": "Scope", "type": "string", "default": ""},
- "client_id": IsDict(
- {
- "title": "Client Id",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Id", "type": "string"}
- ),
- "client_secret": IsDict(
- {
- "title": "Client Secret",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Secret", "type": "string"}
- ),
+ "client_id": {
+ "title": "Client Id",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "client_secret": {
+ "title": "Client Secret",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
},
},
"ValidationError": {
diff --git a/tests/test_security_oauth2_optional_description.py b/tests/test_security_oauth2_optional_description.py
index 629cddca2f..60c6c242e0 100644
--- a/tests/test_security_oauth2_optional_description.py
+++ b/tests/test_security_oauth2_optional_description.py
@@ -1,7 +1,6 @@
from typing import Optional
import pytest
-from dirty_equals import IsDict
from fastapi import Depends, FastAPI, Security
from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict
from fastapi.testclient import TestClient
@@ -68,79 +67,43 @@ def test_security_oauth2_password_bearer_no_header():
def test_strict_login_None():
response = client.post("/login", data=None)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "grant_type"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "username"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "password"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "grant_type"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "username"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "password"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "grant_type"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": None,
+ },
+ ]
+ }
def test_strict_login_no_grant_type():
response = client.post("/login", data={"username": "johndoe", "password": "secret"})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "grant_type"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "grant_type"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "grant_type"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -157,31 +120,17 @@ def test_strict_login_incorrect_grant_type(grant_type: str):
data={"username": "johndoe", "password": "secret", "grant_type": grant_type},
)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "string_pattern_mismatch",
- "loc": ["body", "grant_type"],
- "msg": "String should match pattern '^password$'",
- "input": grant_type,
- "ctx": {"pattern": "^password$"},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "grant_type"],
- "msg": 'string does not match regex "^password$"',
- "type": "value_error.str.regex",
- "ctx": {"pattern": "^password$"},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_pattern_mismatch",
+ "loc": ["body", "grant_type"],
+ "msg": "String should match pattern '^password$'",
+ "input": grant_type,
+ "ctx": {"pattern": "^password$"},
+ }
+ ]
+ }
def test_strict_login_correct_correct_grant_type():
@@ -268,26 +217,14 @@ def test_openapi_schema():
"username": {"title": "Username", "type": "string"},
"password": {"title": "Password", "type": "string"},
"scope": {"title": "Scope", "type": "string", "default": ""},
- "client_id": IsDict(
- {
- "title": "Client Id",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Id", "type": "string"}
- ),
- "client_secret": IsDict(
- {
- "title": "Client Secret",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Secret", "type": "string"}
- ),
+ "client_id": {
+ "title": "Client Id",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "client_secret": {
+ "title": "Client Secret",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
},
},
"ValidationError": {
diff --git a/tests/test_sub_callbacks.py b/tests/test_sub_callbacks.py
index ed7f4efe8a..cc7e5f5c6a 100644
--- a/tests/test_sub_callbacks.py
+++ b/tests/test_sub_callbacks.py
@@ -1,6 +1,5 @@
from typing import Optional
-from dirty_equals import IsDict
from fastapi import APIRouter, FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel, HttpUrl
@@ -99,30 +98,18 @@ def test_openapi_schema():
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "title": "Callback Url",
- "anyOf": [
- {
- "type": "string",
- "format": "uri",
- "minLength": 1,
- "maxLength": 2083,
- },
- {"type": "null"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Callback Url",
- "maxLength": 2083,
- "minLength": 1,
- "type": "string",
- "format": "uri",
- }
- ),
+ "schema": {
+ "title": "Callback Url",
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uri",
+ "minLength": 1,
+ "maxLength": 2083,
+ },
+ {"type": "null"},
+ ],
+ },
"name": "callback_url",
"in": "query",
}
@@ -262,16 +249,10 @@ def test_openapi_schema():
"type": "object",
"properties": {
"id": {"title": "Id", "type": "string"},
- "title": IsDict(
- {
- "title": "Title",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Title", "type": "string"}
- ),
+ "title": {
+ "title": "Title",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
"customer": {"title": "Customer", "type": "string"},
"total": {"title": "Total", "type": "number"},
},
diff --git a/tests/test_tuples.py b/tests/test_tuples.py
index fbc69a6145..d3c89045b4 100644
--- a/tests/test_tuples.py
+++ b/tests/test_tuples.py
@@ -1,4 +1,3 @@
-from dirty_equals import IsDict
from fastapi import FastAPI, Form
from fastapi.testclient import TestClient
from pydantic import BaseModel
@@ -125,31 +124,16 @@ def test_openapi_schema():
"requestBody": {
"content": {
"application/json": {
- "schema": IsDict(
- {
- "title": "Square",
- "maxItems": 2,
- "minItems": 2,
- "type": "array",
- "prefixItems": [
- {"$ref": "#/components/schemas/Coordinate"},
- {"$ref": "#/components/schemas/Coordinate"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Square",
- "maxItems": 2,
- "minItems": 2,
- "type": "array",
- "items": [
- {"$ref": "#/components/schemas/Coordinate"},
- {"$ref": "#/components/schemas/Coordinate"},
- ],
- }
- )
+ "schema": {
+ "title": "Square",
+ "maxItems": 2,
+ "minItems": 2,
+ "type": "array",
+ "prefixItems": [
+ {"$ref": "#/components/schemas/Coordinate"},
+ {"$ref": "#/components/schemas/Coordinate"},
+ ],
+ }
}
},
"required": True,
@@ -212,28 +196,16 @@ def test_openapi_schema():
"required": ["values"],
"type": "object",
"properties": {
- "values": IsDict(
- {
- "title": "Values",
- "maxItems": 2,
- "minItems": 2,
- "type": "array",
- "prefixItems": [
- {"type": "integer"},
- {"type": "integer"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Values",
- "maxItems": 2,
- "minItems": 2,
- "type": "array",
- "items": [{"type": "integer"}, {"type": "integer"}],
- }
- )
+ "values": {
+ "title": "Values",
+ "maxItems": 2,
+ "minItems": 2,
+ "type": "array",
+ "prefixItems": [
+ {"type": "integer"},
+ {"type": "integer"},
+ ],
+ }
},
},
"Coordinate": {
@@ -264,26 +236,15 @@ def test_openapi_schema():
"items": {
"title": "Items",
"type": "array",
- "items": IsDict(
- {
- "maxItems": 2,
- "minItems": 2,
- "type": "array",
- "prefixItems": [
- {"type": "string"},
- {"type": "string"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "maxItems": 2,
- "minItems": 2,
- "type": "array",
- "items": [{"type": "string"}, {"type": "string"}],
- }
- ),
+ "items": {
+ "maxItems": 2,
+ "minItems": 2,
+ "type": "array",
+ "prefixItems": [
+ {"type": "string"},
+ {"type": "string"},
+ ],
+ },
}
},
},
diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial002.py b/tests/test_tutorial/test_additional_responses/test_tutorial002.py
index bbcad8f294..8208605956 100644
--- a/tests/test_tutorial/test_additional_responses/test_tutorial002.py
+++ b/tests/test_tutorial/test_additional_responses/test_tutorial002.py
@@ -3,7 +3,6 @@ import os
import shutil
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
from tests.utils import needs_py310
@@ -80,16 +79,10 @@ def test_openapi_schema(client: TestClient):
},
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "boolean"}, {"type": "null"}],
- "title": "Img",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Img", "type": "boolean"}
- ),
+ "schema": {
+ "anyOf": [{"type": "boolean"}, {"type": "null"}],
+ "title": "Img",
+ },
"name": "img",
"in": "query",
},
diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial004.py b/tests/test_tutorial/test_additional_responses/test_tutorial004.py
index cbd4fff7d6..c6abf5e466 100644
--- a/tests/test_tutorial/test_additional_responses/test_tutorial004.py
+++ b/tests/test_tutorial/test_additional_responses/test_tutorial004.py
@@ -3,7 +3,6 @@ import os
import shutil
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
from tests.utils import needs_py310
@@ -83,16 +82,10 @@ def test_openapi_schema(client: TestClient):
},
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "boolean"}, {"type": "null"}],
- "title": "Img",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Img", "type": "boolean"}
- ),
+ "schema": {
+ "anyOf": [{"type": "boolean"}, {"type": "null"}],
+ "title": "Img",
+ },
"name": "img",
"in": "query",
},
diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial003.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial003.py
index 2d1d1b03c8..a164bb80b5 100644
--- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial003.py
+++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial003.py
@@ -1,5 +1,5 @@
-from dirty_equals import IsOneOf
from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
from docs_src.behind_a_proxy.tutorial003_py39 import app
@@ -15,40 +15,34 @@ def test_main():
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "servers": [
- {"url": "/api/v1"},
- {
- "url": IsOneOf(
- "https://stag.example.com/",
- # TODO: remove when deprecating Pydantic v1
- "https://stag.example.com",
- ),
- "description": "Staging environment",
- },
- {
- "url": IsOneOf(
- "https://prod.example.com/",
- # TODO: remove when deprecating Pydantic v1
- "https://prod.example.com",
- ),
- "description": "Production environment",
- },
- ],
- "paths": {
- "/app": {
- "get": {
- "summary": "Read Main",
- "operationId": "read_main_app_get",
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "servers": [
+ {"url": "/api/v1"},
+ {
+ "url": "https://stag.example.com",
+ "description": "Staging environment",
+ },
+ {
+ "url": "https://prod.example.com",
+ "description": "Production environment",
+ },
+ ],
+ "paths": {
+ "/app": {
+ "get": {
+ "summary": "Read Main",
+ "operationId": "read_main_app_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
}
- }
- },
- }
+ },
+ }
+ )
diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial004.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial004.py
index e8a03e8112..01bba9fedd 100644
--- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial004.py
+++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial004.py
@@ -1,5 +1,5 @@
-from dirty_equals import IsOneOf
from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
from docs_src.behind_a_proxy.tutorial004_py39 import app
@@ -15,39 +15,33 @@ def test_main():
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "servers": [
- {
- "url": IsOneOf(
- "https://stag.example.com/",
- # TODO: remove when deprecating Pydantic v1
- "https://stag.example.com",
- ),
- "description": "Staging environment",
- },
- {
- "url": IsOneOf(
- "https://prod.example.com/",
- # TODO: remove when deprecating Pydantic v1
- "https://prod.example.com",
- ),
- "description": "Production environment",
- },
- ],
- "paths": {
- "/app": {
- "get": {
- "summary": "Read Main",
- "operationId": "read_main_app_get",
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "servers": [
+ {
+ "url": "https://stag.example.com",
+ "description": "Staging environment",
+ },
+ {
+ "url": "https://prod.example.com",
+ "description": "Production environment",
+ },
+ ],
+ "paths": {
+ "/app": {
+ "get": {
+ "summary": "Read Main",
+ "operationId": "read_main_app_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
}
- }
- },
- }
+ },
+ }
+ )
diff --git a/tests/test_tutorial/test_bigger_applications/test_main.py b/tests/test_tutorial/test_bigger_applications/test_main.py
index 7493a9e661..f5e243b95a 100644
--- a/tests/test_tutorial/test_bigger_applications/test_main.py
+++ b/tests/test_tutorial/test_bigger_applications/test_main.py
@@ -1,7 +1,6 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
@@ -28,29 +27,16 @@ def test_users_token_jessica(client: TestClient):
def test_users_with_no_token(client: TestClient):
response = client.get("/users")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "token"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "token"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "token"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_users_foo_token_jessica(client: TestClient):
@@ -62,29 +48,16 @@ def test_users_foo_token_jessica(client: TestClient):
def test_users_foo_with_no_token(client: TestClient):
response = client.get("/users/foo")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "token"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "token"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "token"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_users_me_token_jessica(client: TestClient):
@@ -96,29 +69,16 @@ def test_users_me_token_jessica(client: TestClient):
def test_users_me_with_no_token(client: TestClient):
response = client.get("/users/me")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "token"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "token"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "token"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_users_token_monica_with_no_jessica(client: TestClient):
@@ -141,29 +101,16 @@ def test_items_token_jessica(client: TestClient):
def test_items_with_no_token_jessica(client: TestClient):
response = client.get("/items", headers={"X-Token": "fake-super-secret-token"})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "token"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "token"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "token"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_items_plumbus_token_jessica(client: TestClient):
@@ -187,29 +134,16 @@ def test_items_plumbus_with_no_token(client: TestClient):
"/items/plumbus", headers={"X-Token": "fake-super-secret-token"}
)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "token"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "token"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "token"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_items_with_invalid_token(client: TestClient):
@@ -227,57 +161,31 @@ def test_items_bar_with_invalid_token(client: TestClient):
def test_items_with_missing_x_token_header(client: TestClient):
response = client.get("/items?token=jessica")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["header", "x-token"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["header", "x-token"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["header", "x-token"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_items_plumbus_with_missing_x_token_header(client: TestClient):
response = client.get("/items/plumbus?token=jessica")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["header", "x-token"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["header", "x-token"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["header", "x-token"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_root_token_jessica(client: TestClient):
@@ -289,68 +197,37 @@ def test_root_token_jessica(client: TestClient):
def test_root_with_no_token(client: TestClient):
response = client.get("/")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "token"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "token"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "token"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_put_no_header(client: TestClient):
response = client.put("/items/foo")
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "token"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["header", "x-token"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "token"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["header", "x-token"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "token"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["header", "x-token"],
+ "msg": "Field required",
+ "input": None,
+ },
+ ]
+ }
def test_put_invalid_header(client: TestClient):
diff --git a/tests/test_tutorial/test_body/test_tutorial001.py b/tests/test_tutorial/test_body/test_tutorial001.py
index 6aa9f2593e..5a7cae1603 100644
--- a/tests/test_tutorial/test_body/test_tutorial001.py
+++ b/tests/test_tutorial/test_body/test_tutorial001.py
@@ -2,7 +2,6 @@ import importlib
from unittest.mock import patch
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py310
@@ -74,124 +73,67 @@ def test_post_with_str_float_description_tax(client: TestClient):
def test_post_with_only_name(client: TestClient):
response = client.post("/items/", json={"name": "Foo"})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "price"],
- "msg": "Field required",
- "input": {"name": "Foo"},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "price"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "price"],
+ "msg": "Field required",
+ "input": {"name": "Foo"},
+ }
+ ]
+ }
def test_post_with_only_name_price(client: TestClient):
response = client.post("/items/", json={"name": "Foo", "price": "twenty"})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "float_parsing",
- "loc": ["body", "price"],
- "msg": "Input should be a valid number, unable to parse string as a number",
- "input": "twenty",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "price"],
- "msg": "value is not a valid float",
- "type": "type_error.float",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "float_parsing",
+ "loc": ["body", "price"],
+ "msg": "Input should be a valid number, unable to parse string as a number",
+ "input": "twenty",
+ }
+ ]
+ }
def test_post_with_no_data(client: TestClient):
response = client.post("/items/", json={})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "name"],
- "msg": "Field required",
- "input": {},
- },
- {
- "type": "missing",
- "loc": ["body", "price"],
- "msg": "Field required",
- "input": {},
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "name"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "price"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "name"],
+ "msg": "Field required",
+ "input": {},
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "price"],
+ "msg": "Field required",
+ "input": {},
+ },
+ ]
+ }
def test_post_with_none(client: TestClient):
response = client.post("/items/", json=None)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_post_broken_body(client: TestClient):
@@ -201,67 +143,32 @@ def test_post_broken_body(client: TestClient):
content="{some broken json}",
)
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "json_invalid",
- "loc": ["body", 1],
- "msg": "JSON decode error",
- "input": {},
- "ctx": {
- "error": "Expecting property name enclosed in double quotes"
- },
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", 1],
- "msg": "Expecting property name enclosed in double quotes: line 1 column 2 (char 1)",
- "type": "value_error.jsondecode",
- "ctx": {
- "msg": "Expecting property name enclosed in double quotes",
- "doc": "{some broken json}",
- "pos": 1,
- "lineno": 1,
- "colno": 2,
- },
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "json_invalid",
+ "loc": ["body", 1],
+ "msg": "JSON decode error",
+ "input": {},
+ "ctx": {"error": "Expecting property name enclosed in double quotes"},
+ }
+ ]
+ }
def test_post_form_for_json(client: TestClient):
response = client.post("/items/", data={"name": "Foo", "price": 50.5})
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "model_attributes_type",
- "loc": ["body"],
- "msg": "Input should be a valid dictionary or object to extract fields from",
- "input": "name=Foo&price=50.5",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body"],
- "msg": "value is not a valid dict",
- "type": "type_error.dict",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "model_attributes_type",
+ "loc": ["body"],
+ "msg": "Input should be a valid dictionary or object to extract fields from",
+ "input": "name=Foo&price=50.5",
+ }
+ ]
+ }
def test_explicit_content_type(client: TestClient):
@@ -302,84 +209,46 @@ def test_wrong_headers(client: TestClient):
"/items/", content=data, headers={"Content-Type": "text/plain"}
)
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "model_attributes_type",
- "loc": ["body"],
- "msg": "Input should be a valid dictionary or object to extract fields from",
- "input": '{"name": "Foo", "price": 50.5}',
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body"],
- "msg": "value is not a valid dict",
- "type": "type_error.dict",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "model_attributes_type",
+ "loc": ["body"],
+ "msg": "Input should be a valid dictionary or object to extract fields from",
+ "input": '{"name": "Foo", "price": 50.5}',
+ }
+ ]
+ }
response = client.post(
"/items/", content=data, headers={"Content-Type": "application/geo+json-seq"}
)
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "model_attributes_type",
- "loc": ["body"],
- "msg": "Input should be a valid dictionary or object to extract fields from",
- "input": '{"name": "Foo", "price": 50.5}',
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body"],
- "msg": "value is not a valid dict",
- "type": "type_error.dict",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "model_attributes_type",
+ "loc": ["body"],
+ "msg": "Input should be a valid dictionary or object to extract fields from",
+ "input": '{"name": "Foo", "price": 50.5}',
+ }
+ ]
+ }
+
response = client.post(
"/items/", content=data, headers={"Content-Type": "application/not-really-json"}
)
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "model_attributes_type",
- "loc": ["body"],
- "msg": "Input should be a valid dictionary or object to extract fields from",
- "input": '{"name": "Foo", "price": 50.5}',
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body"],
- "msg": "value is not a valid dict",
- "type": "type_error.dict",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "model_attributes_type",
+ "loc": ["body"],
+ "msg": "Input should be a valid dictionary or object to extract fields from",
+ "input": '{"name": "Foo", "price": 50.5}',
+ }
+ ]
+ }
def test_other_exceptions(client: TestClient):
@@ -435,26 +304,14 @@ def test_openapi_schema(client: TestClient):
"properties": {
"name": {"title": "Name", "type": "string"},
"price": {"title": "Price", "type": "number"},
- "description": IsDict(
- {
- "title": "Description",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Description", "type": "string"}
- ),
- "tax": IsDict(
- {
- "title": "Tax",
- "anyOf": [{"type": "number"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Tax", "type": "number"}
- ),
+ "description": {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "tax": {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ },
},
},
"ValidationError": {
diff --git a/tests/test_tutorial/test_body/test_tutorial002.py b/tests/test_tutorial/test_body/test_tutorial002.py
new file mode 100644
index 0000000000..b6d51d5235
--- /dev/null
+++ b/tests/test_tutorial/test_body/test_tutorial002.py
@@ -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"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_body/test_tutorial003.py b/tests/test_tutorial/test_body/test_tutorial003.py
new file mode 100644
index 0000000000..227a125e78
--- /dev/null
+++ b/tests/test_tutorial/test_body/test_tutorial003.py
@@ -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"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_body/test_tutorial004.py b/tests/test_tutorial/test_body/test_tutorial004.py
new file mode 100644
index 0000000000..10212843ee
--- /dev/null
+++ b/tests/test_tutorial/test_body/test_tutorial004.py
@@ -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"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_body_fields/test_tutorial001.py b/tests/test_tutorial/test_body_fields/test_tutorial001.py
index d54ec7191a..0ecadbb660 100644
--- a/tests/test_tutorial/test_body_fields/test_tutorial001.py
+++ b/tests/test_tutorial/test_body_fields/test_tutorial001.py
@@ -1,7 +1,6 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py310
@@ -59,31 +58,17 @@ def test_items_6(client: TestClient):
def test_invalid_price(client: TestClient):
response = client.put("/items/5", json={"item": {"name": "Foo", "price": -3.0}})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "greater_than",
- "loc": ["body", "item", "price"],
- "msg": "Input should be greater than 0",
- "input": -3.0,
- "ctx": {"gt": 0.0},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "ctx": {"limit_value": 0},
- "loc": ["body", "item", "price"],
- "msg": "ensure this value is greater than 0",
- "type": "value_error.number.not_gt",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "greater_than",
+ "loc": ["body", "item", "price"],
+ "msg": "Input should be greater than 0",
+ "input": -3.0,
+ "ctx": {"gt": 0.0},
+ }
+ ]
+ }
def test_openapi_schema(client: TestClient):
@@ -142,39 +127,23 @@ def test_openapi_schema(client: TestClient):
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
- "description": IsDict(
- {
- "title": "The description of the item",
- "anyOf": [
- {"maxLength": 300, "type": "string"},
- {"type": "null"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "The description of the item",
- "maxLength": 300,
- "type": "string",
- }
- ),
+ "description": {
+ "title": "The description of the item",
+ "anyOf": [
+ {"maxLength": 300, "type": "string"},
+ {"type": "null"},
+ ],
+ },
"price": {
"title": "Price",
"exclusiveMinimum": 0.0,
"type": "number",
"description": "The price must be greater than zero",
},
- "tax": IsDict(
- {
- "title": "Tax",
- "anyOf": [{"type": "number"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Tax", "type": "number"}
- ),
+ "tax": {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ },
},
},
"Body_update_item_items__item_id__put": {
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py
index 2035cf9448..63c9c16d62 100644
--- a/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py
+++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py
@@ -1,7 +1,6 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py310
@@ -53,29 +52,16 @@ def test_post_no_body(client: TestClient):
def test_post_id_foo(client: TestClient):
response = client.put("/items/foo", json=None)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "foo",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ 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):
@@ -119,16 +105,10 @@ def test_openapi_schema(client: TestClient):
},
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Q",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Q", "type": "string"}
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Q",
+ },
"name": "q",
"in": "query",
},
@@ -136,19 +116,13 @@ def test_openapi_schema(client: TestClient):
"requestBody": {
"content": {
"application/json": {
- "schema": IsDict(
- {
- "anyOf": [
- {"$ref": "#/components/schemas/Item"},
- {"type": "null"},
- ],
- "title": "Item",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"$ref": "#/components/schemas/Item"}
- )
+ "schema": {
+ "anyOf": [
+ {"$ref": "#/components/schemas/Item"},
+ {"type": "null"},
+ ],
+ "title": "Item",
+ }
}
}
},
@@ -163,27 +137,15 @@ def test_openapi_schema(client: TestClient):
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
- "description": IsDict(
- {
- "title": "Description",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Description", "type": "string"}
- ),
+ "description": {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
"price": {"title": "Price", "type": "number"},
- "tax": IsDict(
- {
- "title": "Tax",
- "anyOf": [{"type": "number"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Tax", "type": "number"}
- ),
+ "tax": {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ },
},
},
"ValidationError": {
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial002.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial002.py
new file mode 100644
index 0000000000..e98d5860fe
--- /dev/null
+++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial002.py
@@ -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",
+ },
+ },
+ },
+ }
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py
index d3e6401af2..76b7ff7099 100644
--- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py
+++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py
@@ -1,7 +1,6 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py310
@@ -49,101 +48,55 @@ def test_post_body_valid(client: TestClient):
def test_post_body_no_data(client: TestClient):
response = client.put("/items/5", json=None)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "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,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "item"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "user"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "importance"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ 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_post_body_empty_list(client: TestClient):
response = client.put("/items/5", json=[])
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "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,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "item"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "user"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "importance"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ 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_openapi_schema(client: TestClient):
@@ -202,27 +155,15 @@ def test_openapi_schema(client: TestClient):
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
- "description": IsDict(
- {
- "title": "Description",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Description", "type": "string"}
- ),
+ "description": {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
"price": {"title": "Price", "type": "number"},
- "tax": IsDict(
- {
- "title": "Tax",
- "anyOf": [{"type": "number"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Tax", "type": "number"}
- ),
+ "tax": {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ },
},
},
"User": {
@@ -231,16 +172,10 @@ def test_openapi_schema(client: TestClient):
"type": "object",
"properties": {
"username": {"title": "Username", "type": "string"},
- "full_name": IsDict(
- {
- "title": "Full Name",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Full Name", "type": "string"}
- ),
+ "full_name": {
+ "title": "Full Name",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
},
},
"Body_update_item_items__item_id__put": {
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial004.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial004.py
new file mode 100644
index 0000000000..979c054cd0
--- /dev/null
+++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial004.py
@@ -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"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial005.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial005.py
new file mode 100644
index 0000000000..d47aa1b4f9
--- /dev/null
+++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial005.py
@@ -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",
+ },
+ },
+ },
+ }
diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial001_tutorial002_tutorial003.py b/tests/test_tutorial/test_body_nested_models/test_tutorial001_tutorial002_tutorial003.py
new file mode 100644
index 0000000000..d452929c38
--- /dev/null
+++ b/tests/test_tutorial/test_body_nested_models/test_tutorial001_tutorial002_tutorial003.py
@@ -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"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial004.py b/tests/test_tutorial/test_body_nested_models/test_tutorial004.py
new file mode 100644
index 0000000000..ff9596943d
--- /dev/null
+++ b/tests/test_tutorial/test_body_nested_models/test_tutorial004.py
@@ -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"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial005.py b/tests/test_tutorial/test_body_nested_models/test_tutorial005.py
new file mode 100644
index 0000000000..9a07a904e6
--- /dev/null
+++ b/tests/test_tutorial/test_body_nested_models/test_tutorial005.py
@@ -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"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial006.py b/tests/test_tutorial/test_body_nested_models/test_tutorial006.py
new file mode 100644
index 0000000000..088177cb95
--- /dev/null
+++ b/tests/test_tutorial/test_body_nested_models/test_tutorial006.py
@@ -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"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial007.py b/tests/test_tutorial/test_body_nested_models/test_tutorial007.py
new file mode 100644
index 0000000000..a302819505
--- /dev/null
+++ b/tests/test_tutorial/test_body_nested_models/test_tutorial007.py
@@ -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"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial008.py b/tests/test_tutorial/test_body_nested_models/test_tutorial008.py
new file mode 100644
index 0000000000..32eb8ee75c
--- /dev/null
+++ b/tests/test_tutorial/test_body_nested_models/test_tutorial008.py
@@ -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"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial009.py b/tests/test_tutorial/test_body_nested_models/test_tutorial009.py
index db9f04546e..f2e56d40fb 100644
--- a/tests/test_tutorial/test_body_nested_models/test_tutorial009.py
+++ b/tests/test_tutorial/test_body_nested_models/test_tutorial009.py
@@ -1,7 +1,6 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
@@ -29,29 +28,16 @@ def test_post_invalid_body(client: TestClient):
data = {"foo": 2.2, "3": 3.3}
response = client.post("/index-weights/", json=data)
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["body", "foo", "[key]"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "foo",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "__key__"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["body", "foo", "[key]"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "foo",
+ }
+ ]
+ }
def test_openapi_schema(client: TestClient):
diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001.py b/tests/test_tutorial/test_body_updates/test_tutorial001.py
index 8bbc4d6992..0401eb7d0d 100644
--- a/tests/test_tutorial/test_body_updates/test_tutorial001.py
+++ b/tests/test_tutorial/test_body_updates/test_tutorial001.py
@@ -3,7 +3,7 @@ import importlib
import pytest
from fastapi.testclient import TestClient
-from ...utils import needs_py310, needs_pydanticv1, needs_pydanticv2
+from ...utils import needs_py310
@pytest.fixture(
@@ -45,7 +45,6 @@ def test_put(client: TestClient):
}
-@needs_pydanticv2
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
@@ -185,137 +184,3 @@ def test_openapi_schema(client: TestClient):
}
},
}
-
-
-# TODO: remove when deprecating Pydantic v1
-@needs_pydanticv1
-def test_openapi_schema_pv1(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}": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Read Item",
- "operationId": "read_item_items__item_id__get",
- "parameters": [
- {
- "required": True,
- "schema": {"title": "Item Id", "type": "string"},
- "name": "item_id",
- "in": "path",
- }
- ],
- },
- "put": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "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": "string"},
- "name": "item_id",
- "in": "path",
- }
- ],
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- "required": True,
- },
- },
- }
- },
- "components": {
- "schemas": {
- "Item": {
- "title": "Item",
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "description": {"title": "Description", "type": "string"},
- "price": {"title": "Price", "type": "number"},
- "tax": {"title": "Tax", "type": "number", "default": 10.5},
- "tags": {
- "title": "Tags",
- "type": "array",
- "items": {"type": "string"},
- "default": [],
- },
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_body_updates/test_tutorial002.py b/tests/test_tutorial/test_body_updates/test_tutorial002.py
new file mode 100644
index 0000000000..466e6af8fd
--- /dev/null
+++ b/tests/test_tutorial/test_body_updates/test_tutorial002.py
@@ -0,0 +1,207 @@
+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_updates.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_get(client: TestClient):
+ response = client.get("/items/baz")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "name": "Baz",
+ "description": None,
+ "price": 50.2,
+ "tax": 10.5,
+ "tags": [],
+ }
+
+
+def test_patch_all(client: TestClient):
+ response = client.patch(
+ "/items/foo",
+ json={
+ "name": "Fooz",
+ "description": "Item description",
+ "price": 3,
+ "tax": 10.5,
+ "tags": ["tag1", "tag2"],
+ },
+ )
+ assert response.json() == {
+ "name": "Fooz",
+ "description": "Item description",
+ "price": 3,
+ "tax": 10.5,
+ "tags": ["tag1", "tag2"],
+ }
+
+
+def test_patch_name(client: TestClient):
+ response = client.patch(
+ "/items/bar",
+ json={"name": "Barz"},
+ )
+ assert response.json() == {
+ "name": "Barz",
+ "description": "The bartenders",
+ "price": 62,
+ "tax": 20.2,
+ "tags": [],
+ }
+
+
+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}": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Item",
+ "operationId": "read_item_items__item_id__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ },
+ "patch": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Update Item",
+ "operationId": "update_item_items__item_id__patch",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "required": True,
+ },
+ },
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "type": "object",
+ "title": "Item",
+ "properties": {
+ "name": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Name",
+ },
+ "description": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Description",
+ },
+ "price": {
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ "title": "Price",
+ },
+ "tax": {"title": "Tax", "type": "number", "default": 10.5},
+ "tags": {
+ "title": "Tags",
+ "type": "array",
+ "items": {"type": "string"},
+ "default": [],
+ },
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py b/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py
index a425e893d8..ddc282d85e 100644
--- a/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py
+++ b/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py
@@ -2,8 +2,6 @@ import importlib
from fastapi.testclient import TestClient
-from ...utils import needs_pydanticv2
-
def get_client() -> TestClient:
from docs_src.conditional_openapi import tutorial001_py39
@@ -14,7 +12,6 @@ def get_client() -> TestClient:
return client
-@needs_pydanticv2
def test_disable_openapi(monkeypatch):
monkeypatch.setenv("OPENAPI_URL", "")
# Load the client after setting the env var
@@ -27,7 +24,6 @@ def test_disable_openapi(monkeypatch):
assert response.status_code == 404, response.text
-@needs_pydanticv2
def test_root():
client = get_client()
response = client.get("/")
@@ -35,7 +31,6 @@ def test_root():
assert response.json() == {"message": "Hello World"}
-@needs_pydanticv2
def test_default_openapi():
client = get_client()
response = client.get("/docs")
diff --git a/tests/test_tutorial/test_cookie_param_models/test_tutorial001.py b/tests/test_tutorial/test_cookie_param_models/test_tutorial001.py
index 265dee944e..ac8e7bdae1 100644
--- a/tests/test_tutorial/test_cookie_param_models/test_tutorial001.py
+++ b/tests/test_tutorial/test_cookie_param_models/test_tutorial001.py
@@ -1,7 +1,6 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
@@ -54,30 +53,16 @@ def test_cookie_param_model_invalid(client: TestClient):
response = client.get("/items/")
assert response.status_code == 422
assert response.json() == snapshot(
- IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["cookie", "session_id"],
- "msg": "Field required",
- "input": {},
- }
- ]
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "type": "value_error.missing",
- "loc": ["cookie", "session_id"],
- "msg": "field required",
- }
- ]
- }
- )
+ {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["cookie", "session_id"],
+ "msg": "Field required",
+ "input": {},
+ }
+ ]
+ }
)
@@ -115,37 +100,19 @@ def test_openapi_schema(client: TestClient):
"name": "fatebook_tracker",
"in": "cookie",
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Fatebook Tracker",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "string",
- "title": "Fatebook Tracker",
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Fatebook Tracker",
+ },
},
{
"name": "googall_tracker",
"in": "cookie",
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Googall Tracker",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "string",
- "title": "Googall Tracker",
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Googall Tracker",
+ },
},
],
"responses": {
diff --git a/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py b/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py
index 4a826a5374..d7c3d15f1b 100644
--- a/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py
+++ b/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py
@@ -1,29 +1,19 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
-from tests.utils import (
- needs_py310,
- needs_pydanticv1,
- needs_pydanticv2,
- pydantic_snapshot,
-)
+from tests.utils import needs_py310
@pytest.fixture(
name="client",
params=[
- pytest.param("tutorial002_py39", marks=needs_pydanticv2),
- pytest.param("tutorial002_py310", marks=[needs_py310, needs_pydanticv2]),
- pytest.param("tutorial002_an_py39", marks=needs_pydanticv2),
- pytest.param("tutorial002_an_py310", marks=[needs_py310, needs_pydanticv2]),
- pytest.param("tutorial002_pv1_py39", marks=needs_pydanticv1),
- pytest.param("tutorial002_pv1_py310", marks=[needs_py310, needs_pydanticv1]),
- pytest.param("tutorial002_pv1_an_py39", marks=needs_pydanticv1),
- pytest.param("tutorial002_pv1_an_py310", marks=[needs_py310, needs_pydanticv1]),
+ pytest.param("tutorial002_py39"),
+ pytest.param("tutorial002_py310", marks=[needs_py310]),
+ pytest.param("tutorial002_an_py39"),
+ pytest.param("tutorial002_an_py310", marks=[needs_py310]),
],
)
def get_client(request: pytest.FixtureRequest):
@@ -62,31 +52,16 @@ def test_cookie_param_model_defaults(client: TestClient):
def test_cookie_param_model_invalid(client: TestClient):
response = client.get("/items/")
assert response.status_code == 422
- assert response.json() == pydantic_snapshot(
- v2=snapshot(
+ assert response.json() == {
+ "detail": [
{
- "detail": [
- {
- "type": "missing",
- "loc": ["cookie", "session_id"],
- "msg": "Field required",
- "input": {},
- }
- ]
+ "type": "missing",
+ "loc": ["cookie", "session_id"],
+ "msg": "Field required",
+ "input": {},
}
- ),
- v1=snapshot(
- {
- "detail": [
- {
- "type": "value_error.missing",
- "loc": ["cookie", "session_id"],
- "msg": "field required",
- }
- ]
- }
- ),
- )
+ ]
+ }
def test_cookie_param_model_extra(client: TestClient):
@@ -96,30 +71,16 @@ def test_cookie_param_model_extra(client: TestClient):
response = c.get("/items/")
assert response.status_code == 422
assert response.json() == snapshot(
- IsDict(
- {
- "detail": [
- {
- "type": "extra_forbidden",
- "loc": ["cookie", "extra"],
- "msg": "Extra inputs are not permitted",
- "input": "track-me-here-too",
- }
- ]
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "type": "value_error.extra",
- "loc": ["cookie", "extra"],
- "msg": "extra fields not permitted",
- }
- ]
- }
- )
+ {
+ "detail": [
+ {
+ "type": "extra_forbidden",
+ "loc": ["cookie", "extra"],
+ "msg": "Extra inputs are not permitted",
+ "input": "track-me-here-too",
+ }
+ ]
+ }
)
@@ -146,42 +107,22 @@ def test_openapi_schema(client: TestClient):
"name": "fatebook_tracker",
"in": "cookie",
"required": False,
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "anyOf": [
- {"type": "string"},
- {"type": "null"},
- ],
- "title": "Fatebook Tracker",
- }
- ),
- v1=snapshot(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "string",
- "title": "Fatebook Tracker",
- }
- ),
- ),
+ "schema": {
+ "anyOf": [
+ {"type": "string"},
+ {"type": "null"},
+ ],
+ "title": "Fatebook Tracker",
+ },
},
{
"name": "googall_tracker",
"in": "cookie",
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Googall Tracker",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "string",
- "title": "Googall Tracker",
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Googall Tracker",
+ },
},
],
"responses": {
diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001.py b/tests/test_tutorial/test_cookie_params/test_tutorial001.py
index a65249d657..9b47cbc67a 100644
--- a/tests/test_tutorial/test_cookie_params/test_tutorial001.py
+++ b/tests/test_tutorial/test_cookie_params/test_tutorial001.py
@@ -2,7 +2,6 @@ import importlib
from types import ModuleType
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py310
@@ -75,16 +74,10 @@ def test_openapi_schema(mod: ModuleType):
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Ads Id",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Ads Id", "type": "string"}
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Ads Id",
+ },
"name": "ads_id",
"in": "cookie",
}
diff --git a/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py b/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py
index 643011637e..a9c7ae638b 100644
--- a/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py
+++ b/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py
@@ -1,7 +1,7 @@
import importlib
import pytest
-from dirty_equals import IsDict, IsOneOf
+from dirty_equals import IsOneOf
from fastapi.testclient import TestClient
from tests.utils import needs_py310
@@ -30,34 +30,17 @@ def test_endpoint_works(client: TestClient):
def test_exception_handler_body_access(client: TestClient):
response = client.post("/", json={"numbers": [1, 2, 3]})
- assert response.json() == IsDict(
- {
- "detail": {
- "errors": [
- {
- "type": "list_type",
- "loc": ["body"],
- "msg": "Input should be a valid list",
- "input": {"numbers": [1, 2, 3]},
- }
- ],
- # httpx 0.28.0 switches to compact JSON https://github.com/encode/httpx/issues/3363
- "body": IsOneOf('{"numbers": [1, 2, 3]}', '{"numbers":[1,2,3]}'),
- }
+ assert response.json() == {
+ "detail": {
+ "errors": [
+ {
+ "type": "list_type",
+ "loc": ["body"],
+ "msg": "Input should be a valid list",
+ "input": {"numbers": [1, 2, 3]},
+ }
+ ],
+ # httpx 0.28.0 switches to compact JSON https://github.com/encode/httpx/issues/3363
+ "body": IsOneOf('{"numbers": [1, 2, 3]}', '{"numbers":[1,2,3]}'),
}
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": {
- # httpx 0.28.0 switches to compact JSON https://github.com/encode/httpx/issues/3363
- "body": IsOneOf('{"numbers": [1, 2, 3]}', '{"numbers":[1,2,3]}'),
- "errors": [
- {
- "loc": ["body"],
- "msg": "value is not a valid list",
- "type": "type_error.list",
- }
- ],
- }
- }
- )
+ }
diff --git a/tests/test_tutorial/test_custom_response/test_tutorial001.py b/tests/test_tutorial/test_custom_response/test_tutorial001.py
index c81e991ebf..f1d2accef2 100644
--- a/tests/test_tutorial/test_custom_response/test_tutorial001.py
+++ b/tests/test_tutorial/test_custom_response/test_tutorial001.py
@@ -1,17 +1,29 @@
+import importlib
+
+import pytest
from fastapi.testclient import TestClient
-from docs_src.custom_response.tutorial001_py39 import app
-client = TestClient(app)
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial001_py39"),
+ pytest.param("tutorial010_py39"),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.custom_response.{request.param}")
+ client = TestClient(mod.app)
+ return client
-def test_get_custom_response():
+def test_get_custom_response(client: TestClient):
response = client.get("/items/")
assert response.status_code == 200, response.text
assert response.json() == [{"item_id": "Foo"}]
-def test_openapi_schema():
+def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
diff --git a/tests/test_tutorial/test_custom_response/test_tutorial002_tutorial003_tutorial004.py b/tests/test_tutorial/test_custom_response/test_tutorial002_tutorial003_tutorial004.py
new file mode 100644
index 0000000000..22e2e02540
--- /dev/null
+++ b/tests/test_tutorial/test_custom_response/test_tutorial002_tutorial003_tutorial004.py
@@ -0,0 +1,68 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+
+@pytest.fixture(
+ name="mod_name",
+ params=[
+ pytest.param("tutorial002_py39"),
+ pytest.param("tutorial003_py39"),
+ pytest.param("tutorial004_py39"),
+ ],
+)
+def get_mod_name(request: pytest.FixtureRequest) -> str:
+ return request.param
+
+
+@pytest.fixture(name="client")
+def get_client(mod_name: str) -> TestClient:
+ mod = importlib.import_module(f"docs_src.custom_response.{mod_name}")
+ return TestClient(mod.app)
+
+
+html_contents = """
+
+
+