diff --git a/.github/workflows/translate.yml b/.github/workflows/translate.yml
index e681762ca..f1267d21f 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/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 77e06bd96..10a0949e4 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -12,15 +12,23 @@ repos:
- --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-ruff-check
+ name: ruff check
+ entry: uv run ruff check --force-exclude --fix --exit-non-zero-on-fix
+ require_serial: true
+ language: unsupported
+ 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
@@ -28,18 +36,21 @@ repos:
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
diff --git a/docs/de/docs/advanced/dataclasses.md b/docs/de/docs/advanced/dataclasses.md
index e2d59c776..52b9634ae 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/how-to/graphql.md b/docs/de/docs/how-to/graphql.md
index 0583faf4a..5c908cec4 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/llm-prompt.md b/docs/de/llm-prompt.md
index 35ca9f069..2d345bf6d 100644
--- a/docs/de/llm-prompt.md
+++ b/docs/de/llm-prompt.md
@@ -4,213 +4,197 @@ Translate to German (Deutsch).
Language code: de.
-
-### Definitions
-
-"hyphen"
- The character «-»
- Unicode U+002D (HYPHEN-MINUS)
- Alternative names: hyphen, dash, minus sign
-
-"dash"
- The character «–»
- Unicode U+2013 (EN DASH)
- German name: Halbgeviertstrich
-
-
### Grammar to use when talking to the reader
-Use the formal grammar (use «Sie» instead of «Du»).
-
+Use the formal grammar (use `Sie` instead of `Du`).
### Quotes
-1) Convert neutral double quotes («"») and English double typographic quotes («“» and «”») to German double typographic quotes («„» and «“»). Convert neutral single quotes («'») and English single typographic quotes («‘» and «’») to German single typographic quotes («‚» and «‘»). Do NOT convert «`"» to «„», do NOT convert «"`» to «“».
+1) Convert neutral double quotes (`"`) to German double typographic quotes (`„` and `“`). Convert neutral single quotes (`'`) to German single typographic quotes (`‚` and `‘`).
+
+Do NOT convert quotes in code snippets and code blocks to their German typographic equivalents.
Examples:
- Source (English):
+Source (English):
- «««
- "Hello world"
- “Hello Universe”
- "He said: 'Hello'"
- “my name is ‘Nils’”
- `"__main__"`
- `"items"`
- »»»
+```
+"Hello world"
+“Hello Universe”
+"He said: 'Hello'"
+“my name is ‘Nils’”
+`"__main__"`
+`"items"`
+```
- Result (German):
-
- «««
- „Hallo Welt“
- „Hallo Universum“
- „Er sagte: ‚Hallo‘“
- „Mein Name ist ‚Nils‘“
- `"__main__"`
- `"items"`
- »»»
+Result (German):
+```
+„Hallo Welt“
+„Hallo Universum“
+„Er sagte: ‚Hallo‘“
+„Mein Name ist ‚Nils‘“
+`"__main__"`
+`"items"`
+```
### Ellipsis
-1) Make sure there is a space between an ellipsis and a word following or preceding the ellipsis.
+- Make sure there is a space between an ellipsis and a word following or preceding the ellipsis.
Examples:
- Source (English):
+Source (English):
- «««
- ...as we intended.
- ...this would work:
- ...etc.
- others...
- More to come...
- »»»
+```
+...as we intended.
+...this would work:
+...etc.
+others...
+More to come...
+```
- Result (German):
+Result (German):
- «««
- ... wie wir es beabsichtigt hatten.
- ... das würde funktionieren:
- ... usw.
- Andere ...
- Später mehr ...
- »»»
-
-2) This does not apply in URLs, code blocks, and code snippets. Do not remove or add spaces there.
+```
+... wie wir es beabsichtigt hatten.
+... das würde funktionieren:
+... usw.
+Andere ...
+Später mehr ...
+```
+- This does not apply in URLs, code blocks, and code snippets. Do not remove or add spaces there.
### Headings
-1) Translate headings using the infinite form.
+- Translate headings using the infinite form.
Examples:
- Source (English):
+Source (English):
- «««
- ## Create a Project { #create-a-project }
- »»»
+```
+## Create a Project { #create-a-project }
+```
- Translate with (German):
+Result (German):
- «««
- ## Ein Projekt erstellen { #create-a-project }
- »»»
+```
+## Ein Projekt erstellen { #create-a-project }
+```
- Do NOT translate with (German):
+Do NOT translate with (German):
- «««
- ## Erstellen Sie ein Projekt { #create-a-project }
- »»»
+```
+## Erstellen Sie ein Projekt { #create-a-project }
+```
- Source (English):
+Source (English):
- «««
- # Install Packages { #install-packages }
- »»»
+```
+# Install Packages { #install-packages }
+```
- Translate with (German):
+Translate with (German):
- «««
- # Pakete installieren { #install-packages }
- »»»
+```
+# Pakete installieren { #install-packages }
+```
- Do NOT translate with (German):
+Do NOT translate with (German):
- «««
- # Installieren Sie Pakete { #install-packages }
- »»»
+```
+# Installieren Sie Pakete { #install-packages }
+```
- Source (English):
+Source (English):
- «««
- ### Run Your Program { #run-your-program }
- »»»
+```
+### Run Your Program { #run-your-program }
+```
- Translate with (German):
+Translate with (German):
- «««
- ### Ihr Programm ausführen { #run-your-program }
- »»»
+```
+### Ihr Programm ausführen { #run-your-program }
+```
- Do NOT translate with (German):
+Do NOT translate with (German):
- «««
- ### Führen Sie Ihr Programm aus { #run-your-program }
- »»»
+```
+### Führen Sie Ihr Programm aus { #run-your-program }
+```
-2) Make sure that the translated part of the heading does not end with a period.
+- Make sure that the translated part of the heading does not end with a period.
Example:
- Source (English):
+Source (English):
- «««
- ## Another module with `APIRouter` { #another-module-with-apirouter }
- »»»
+```
+## Another module with `APIRouter` { #another-module-with-apirouter }
+```
- Translate with (German):
+Translate with (German):
- «««
- ## Ein weiteres Modul mit `APIRouter` { #another-module-with-apirouter }
- »»»
+```
+## Ein weiteres Modul mit `APIRouter` { #another-module-with-apirouter }
+```
- Do NOT translate with (German) – notice the added period:
+Do NOT translate with (German) – notice the added period:
- «««
- ## Ein weiteres Modul mit `APIRouter`. { #another-module-with-apirouter }
- »»»
+```
+## Ein weiteres Modul mit `APIRouter`. { #another-module-with-apirouter }
+```
-3) Replace occurrences of literal « - » (a space followed by a hyphen followed by a space) with « – » (a space followed by a dash followed by a space) in the translated part of the heading.
+- Replace occurrences of literal ` - ` (a space followed by a hyphen followed by a space) with ` – ` (a space followed by a dash followed by a space) in the translated part of the heading.
Example:
- Source (English):
+Source (English):
- «««
- # FastAPI in Containers - Docker { #fastapi-in-containers-docker }
- »»»
+```
+# FastAPI in Containers - Docker { #fastapi-in-containers-docker }
+```
- Translate with (German) – notice the dash:
+Translate with (German) – notice the dash:
- «««
- # FastAPI in Containern – Docker { #fastapi-in-containers-docker }
- »»»
+```
+# FastAPI in Containern – Docker { #fastapi-in-containers-docker }
+```
- Do NOT translate with (German) – notice the hyphen:
+Do NOT translate with (German) – notice the hyphen:
- «««
- # FastAPI in Containern - Docker { #fastapi-in-containers-docker }
- »»»
+```
+# FastAPI in Containern - Docker { #fastapi-in-containers-docker }
+```
-3.1) Do not apply rule 3 when there is no space before or no space after the hyphen.
+- Do not apply rule 3 when there is no space before or no space after the hyphen.
Example:
- Source (English):
+Source (English):
- «««
- ## Type hints and annotations { #type-hints-and-annotations }
- »»»
+```
+## Type hints and annotations { #type-hints-and-annotations }
+```
- Translate with (German) – notice the hyphen:
+Translate with (German) - notice the hyphen:
- «««
- ## Typhinweise und -annotationen { #type-hints-and-annotations }
- »»»
+```
+## Typhinweise und -annotationen { #type-hints-and-annotations }
+```
- Do NOT translate with (German) – notice the dash:
+Do NOT translate with (German) - notice the dash:
- «««
- ## Typhinweise und –annotationen { #type-hints-and-annotations }
- »»»
+```
+## Typhinweise und –annotationen { #type-hints-and-annotations }
+```
-3.2) Do not apply rule 3 to the untranslated part of the heading inside curly brackets, which you shall not translate.
+- Do not modify the hyphens in the content in headers inside of curly braces, which you shall not translate.
-
-### German instructions, when to use and when not to use hyphens in words (written in first person, which is you)
+### German instructions, when to use and when not to use hyphens in words (written in first person, which is you).
In der Regel versuche ich so weit wie möglich Worte zusammenzuschreiben, also ohne Bindestrich, es sei denn, es ist Konkretesding-Klassevondingen, etwa «Pydantic-Modell» (aber: «Datenbankmodell»), «Python-Modul» (aber: «Standardmodul»). Ich setze auch einen Bindestrich, wenn er die gleichen Buchstaben verbindet, etwa «Enum-Member», «Cloud-Dienst», «Template-Engine». Oder wenn das Wort sonst einfach zu lang wird, etwa, «Performance-Optimierung». Oder um etwas visuell besser zu dokumentieren, etwa «Pfadoperation-Dekorator», «Pfadoperation-Funktion».
@@ -219,123 +203,122 @@ In der Regel versuche ich so weit wie möglich Worte zusammenzuschreiben, also o
Ich versuche nicht, alles einzudeutschen. Das bezieht sich besonders auf Begriffe aus dem Bereich der Programmierung. Ich wandele zwar korrekt in Großschreibung um und setze Bindestriche, wo notwendig, aber ansonsten lasse ich solch ein Wort unverändert. Beispielsweise wird aus dem englischen Wort «string» in der deutschen Übersetzung «String», aber nicht «Zeichenkette». Oder aus dem englischen Wort «request body» wird in der deutschen Übersetzung «Requestbody», aber nicht «Anfragekörper». Oder aus dem englischen «response» wird im Deutschen «Response», aber nicht «Antwort».
-
### List of English terms and their preferred German translations
-Below is a list of English terms and their preferred German translations, separated by a colon («:»). Use these translations, do not use your own. If an existing translation does not use these terms, update it to use them. In the below list, a term or a translation may be followed by an explanation in brackets, which explains when to translate the term this way. If a translation is preceded by «NOT», then that means: do NOT use this translation for this term. English nouns, starting with the word «the», have the German genus – «der», «die», «das» – prepended to their German translation, to help you to grammatically decline them in the translation. They are given in singular case, unless they have «(plural)» attached, which means they are given in plural case. Verbs are given in the full infinitive – starting with the word «to».
+Below is a list of English terms and their preferred German translations, separated by a colon (:). Use these translations, do not use your own. If an existing translation does not use these terms, update it to use them. In the below list, a term or a translation may be followed by an explanation in brackets, which explains when to translate the term this way. If a translation is preceded by `NOT`, then that means: do NOT use this translation for this term. English nouns, starting with the word `the`, have the German genus – `der`, `die`, `das` – prepended to their German translation, to help you to grammatically decline them in the translation. They are given in singular case, unless they have `(plural)` attached, which means they are given in plural case. Verbs are given in the full infinitive – starting with the word `to`.
-* «/// check»: «/// check | Testen»
-* «/// danger»: «/// danger | Gefahr»
-* «/// info»: «/// info | Info»
-* «/// note | Technical Details»: «/// note | Technische Details»
-* «/// note»: «/// note | Hinweis»
-* «/// tip»: «/// tip | Tipp»
-* «/// warning»: «/// warning | Achtung»
-* «you»: «Sie»
-* «your»: «Ihr»
-* «e.g»: «z. B.»
-* «etc.»: «usw.»
-* «ref»: «Ref.»
-* «the Tutorial - User guide»: «das Tutorial – Benutzerhandbuch»
-* «the Advanced User Guide»: «das Handbuch für fortgeschrittene Benutzer»
-* «the SQLModel docs»: «die SQLModel-Dokumentation»
-* «the docs»: «die Dokumentation» (use singular case)
-* «the env var»: «die Umgebungsvariable»
-* «the `PATH` environment variable»: «die `PATH`-Umgebungsvariable»
-* «the `PATH`»: «der `PATH`»
-* «the `requirements.txt`»: «die `requirements.txt`»
-* «the API Router»: «der API-Router»
-* «the Authorization-Header»: «der Autorisierungsheader»
-* «the `Authorization`-Header»: «der `Authorization`-Header»
-* «the background task»: «der Hintergrundtask»
-* «the button»: «der Button»
-* «the cloud provider»: «der Cloudanbieter»
-* «the CLI»: «Das CLI»
-* «the coverage»: «Die Testabdeckung»
-* «the command line interface»: «Das Kommandozeileninterface»
-* «the default value»: «der Defaultwert»
-* «the default value»: NOT «der Standardwert»
-* «the default declaration»: «die Default-Deklaration»
-* «the deployment»: «das Deployment»
-* «the dict»: «das Dict»
-* «the dictionary»: «das Dictionary»
-* «the enumeration»: «die Enumeration»
-* «the enum»: «das Enum»
-* «the engine»: «die Engine»
-* «the error response»: «die Error-Response»
-* «the event»: «das Event»
-* «the exception»: «die Exception»
-* «the exception handler»: «der Exceptionhandler»
-* «the form model»: «das Formularmodell»
-* «the form body»: «der Formularbody»
-* «the header»: «der Header»
-* «the headers» (plural): «die Header»
-* «in headers» (plural): «in Headern»
-* «the forwarded header»: «der Forwarded-Header»
-* «the lifespan event»: «das Lifespan-Event»
-* «the lock»: «der Lock»
-* «the locking»: «das Locking»
-* «the mobile application»: «die Mobile-Anwendung»
-* «the model object»: «das Modellobjekt»
-* «the mounting»: «das Mounten»
-* «mounted»: «gemountet»
-* «the origin»: «das Origin»
-* «the override»: «Die Überschreibung»
-* «the parameter»: «der Parameter»
-* «the parameters» (plural): «die Parameter»
-* «the function parameter»: «der Funktionsparameter»
-* «the default parameter»: «der Defaultparameter»
-* «the body parameter»: «der Body-Parameter»
-* «the request body parameter»: «der Requestbody-Parameter»
-* «the path parameter»: «der Pfad-Parameter»
-* «the query parameter»: «der Query-Parameter»
-* «the cookie parameter»: «der Cookie-Parameter»
-* «the header parameter»: «der Header-Parameter»
-* «the form parameter»: «der Formular-Parameter»
-* «the payload»: «die Payload»
-* «the performance»: NOT «die Performance»
-* «the query»: «die Query»
-* «the recap»: «die Zusammenfassung»
-* «the request» (what the client sends to the server): «der Request»
-* «the request body»: «der Requestbody»
-* «the request bodies» (plural): «die Requestbodys»
-* «the response» (what the server sends back to the client): «die Response»
-* «the return type»: «der Rückgabetyp»
-* «the return value»: «der Rückgabewert»
-* «the startup» (the event of the app): «der Startup»
-* «the shutdown» (the event of the app): «der Shutdown»
-* «the startup event»: «das Startup-Event»
-* «the shutdown event»: «das Shutdown-Event»
-* «the startup» (of the server): «das Hochfahren»
-* «the startup» (the company): «das Startup»
-* «the SDK»: «das SDK»
-* «the tag»: «der Tag»
-* «the type annotation»: «die Typannotation»
-* «the type hint»: «der Typhinweis»
-* «the wildcard»: «die Wildcard»
-* «the worker class»: «die Workerklasse»
-* «the worker class»: NOT «die Arbeiterklasse»
-* «the worker process»: «der Workerprozess»
-* «the worker process»: NOT «der Arbeiterprozess»
-* «to commit»: «committen»
-* «to deploy» (in the cloud): «deployen»
-* «to modify»: «ändern»
-* «to serve» (an application): «bereitstellen»
-* «to serve» (a response): «ausliefern»
-* «to serve»: NOT «bedienen»
-* «to upgrade»: «aktualisieren»
-* «to wrap»: «wrappen»
-* «to wrap»: NOT «hüllen»
-* «`foo` as a `type`»: «`foo` vom Typ `type`»
-* «`foo` as a `type`»: «`foo`, ein `type`»
-* «FastAPI's X»: «FastAPIs X»
-* «Starlette's Y»: «Starlettes Y»
-* «X is case-sensitive»: «Groß-/Kleinschreibung ist relevant in X»
-* «X is case-insensitive»: «Groß-/Kleinschreibung ist nicht relevant in X»
-* «standard Python»: «Standard-Python»
-* «deprecated»: «deprecatet»
+* /// check: /// check | Testen
+* /// danger: /// danger | Gefahr
+* /// info: /// info | Info
+* /// note | Technical Details: /// note | Technische Details
+* /// note: /// note | Hinweis
+* /// tip: /// tip | Tipp
+* /// warning: /// warning | Achtung
+* you: Sie
+* your: Ihr
+* e.g: z. B.
+* etc.: usw.
+* ref: Ref.
+* the Tutorial - User guide: das Tutorial – Benutzerhandbuch
+* the Advanced User Guide: das Handbuch für fortgeschrittene Benutzer
+* the SQLModel docs: die SQLModel-Dokumentation
+* the docs: die Dokumentation (use singular case)
+* the env var: die Umgebungsvariable
+* the `PATH` environment variable: die `PATH`-Umgebungsvariable
+* the `PATH`: der `PATH`
+* the `requirements.txt`: die `requirements.txt`
+* the API Router: der API-Router
+* the Authorization-Header: der Autorisierungsheader
+* the `Authorization`-Header: der `Authorization`-Header
+* the background task: der Hintergrundtask
+* the button: der Button
+* the cloud provider: der Cloudanbieter
+* the CLI: Das CLI
+* the coverage: Die Testabdeckung
+* the command line interface: Das Kommandozeileninterface
+* the default value: der Defaultwert
+* the default value: NOT der Standardwert
+* the default declaration: die Default-Deklaration
+* the deployment: das Deployment
+* the dict: das Dict
+* the dictionary: das Dictionary
+* the enumeration: die Enumeration
+* the enum: das Enum
+* the engine: die Engine
+* the error response: die Error-Response
+* the event: das Event
+* the exception: die Exception
+* the exception handler: der Exceptionhandler
+* the form model: das Formularmodell
+* the form body: der Formularbody
+* the header: der Header
+* the headers (plural): die Header
+* in headers (plural): in Headern
+* the forwarded header: der Forwarded-Header
+* the lifespan event: das Lifespan-Event
+* the lock: der Lock
+* the locking: das Locking
+* the mobile application: die Mobile-Anwendung
+* the model object: das Modellobjekt
+* the mounting: das Mounten
+* mounted: gemountet
+* the origin: das Origin
+* the override: Die Überschreibung
+* the parameter: der Parameter
+* the parameters (plural): die Parameter
+* the function parameter: der Funktionsparameter
+* the default parameter: der Defaultparameter
+* the body parameter: der Body-Parameter
+* the request body parameter: der Requestbody-Parameter
+* the path parameter: der Pfad-Parameter
+* the query parameter: der Query-Parameter
+* the cookie parameter: der Cookie-Parameter
+* the header parameter: der Header-Parameter
+* the form parameter: der Formular-Parameter
+* the payload: die Payload
+* the performance: NOT die Performance
+* the query: die Query
+* the recap: die Zusammenfassung
+* the request (what the client sends to the server): der Request
+* the request body: der Requestbody
+* the request bodies (plural): die Requestbodys
+* the response (what the server sends back to the client): die Response
+* the return type: der Rückgabetyp
+* the return value: der Rückgabewert
+* the startup (the event of the app): der Startup
+* the shutdown (the event of the app): der Shutdown
+* the startup event: das Startup-Event
+* the shutdown event: das Shutdown-Event
+* the startup (of the server): das Hochfahren
+* the startup (the company): das Startup
+* the SDK: das SDK
+* the tag: der Tag
+* the type annotation: die Typannotation
+* the type hint: der Typhinweis
+* the wildcard: die Wildcard
+* the worker class: die Workerklasse
+* the worker class: NOT die Arbeiterklasse
+* the worker process: der Workerprozess
+* the worker process: NOT der Arbeiterprozess
+* to commit: committen
+* to deploy (in the cloud): deployen
+* to modify: ändern
+* to serve (an application): bereitstellen
+* to serve (a response): ausliefern
+* to serve: NOT bedienen
+* to upgrade: aktualisieren
+* to wrap: wrappen
+* to wrap: NOT hüllen
+* `foo` as a `type`: `foo` vom Typ `type`
+* `foo` as a `type`: `foo`, ein `type`
+* FastAPI's X: FastAPIs X
+* Starlette's Y: Starlettes Y
+* X is case-sensitive: Groß-/Kleinschreibung ist relevant in X
+* X is case-insensitive: Groß-/Kleinschreibung ist nicht relevant in X
+* standard Python: Standard-Python
+* deprecated: deprecatet
### Other rules
-Preserve indentation. Keep emoticons. Encode in utf-8. Use Linux line breaks (LF).
+Preserve indentation. Keep emojis. Encode in utf-8. Use Linux line breaks (LF).
diff --git a/docs/en/data/contributors.yml b/docs/en/data/contributors.yml
index 163dc68e3..0c144cd4c 100644
--- a/docs/en/data/contributors.yml
+++ b/docs/en/data/contributors.yml
@@ -1,6 +1,6 @@
tiangolo:
login: tiangolo
- count: 808
+ count: 857
avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4
url: https://github.com/tiangolo
dependabot:
@@ -10,7 +10,7 @@ dependabot:
url: https://github.com/apps/dependabot
alejsdev:
login: alejsdev
- count: 52
+ count: 53
avatarUrl: https://avatars.githubusercontent.com/u/90076947?u=85ceac49fb87138aebe8d663912e359447329090&v=4
url: https://github.com/alejsdev
pre-commit-ci:
@@ -18,6 +18,11 @@ pre-commit-ci:
count: 50
avatarUrl: https://avatars.githubusercontent.com/in/68672?v=4
url: https://github.com/apps/pre-commit-ci
+YuriiMotov:
+ login: YuriiMotov
+ count: 36
+ avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4
+ url: https://github.com/YuriiMotov
github-actions:
login: github-actions
count: 26
@@ -28,26 +33,21 @@ Kludex:
count: 25
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4
url: https://github.com/Kludex
-YuriiMotov:
- login: YuriiMotov
- count: 20
- avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4
- url: https://github.com/YuriiMotov
dmontagu:
login: dmontagu
count: 17
avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4
url: https://github.com/dmontagu
+svlandeg:
+ login: svlandeg
+ count: 16
+ avatarUrl: https://avatars.githubusercontent.com/u/8796347?u=556c97650c27021911b0b9447ec55e75987b0e8a&v=4
+ url: https://github.com/svlandeg
nilslindemann:
login: nilslindemann
count: 15
avatarUrl: https://avatars.githubusercontent.com/u/6892179?u=1dca6a22195d6cd1ab20737c0e19a4c55d639472&v=4
url: https://github.com/nilslindemann
-svlandeg:
- login: svlandeg
- count: 14
- avatarUrl: https://avatars.githubusercontent.com/u/8796347?u=556c97650c27021911b0b9447ec55e75987b0e8a&v=4
- url: https://github.com/svlandeg
euri10:
login: euri10
count: 13
@@ -553,6 +553,11 @@ DanielKusyDev:
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/36250676?u=2ea6114ff751fc48b55f231987a0e2582c6b1bd2&v=4
url: https://github.com/DanielKusyDev
+Viicos:
+ login: Viicos
+ count: 2
+ avatarUrl: https://avatars.githubusercontent.com/u/65306057?u=fcd677dc1b9bef12aa103613e5ccb3f8ce305af9&v=4
+ url: https://github.com/Viicos
DanielYang59:
login: DanielYang59
count: 2
diff --git a/docs/en/data/github_sponsors.yml b/docs/en/data/github_sponsors.yml
index 24780603d..971687d8a 100644
--- a/docs/en/data/github_sponsors.yml
+++ b/docs/en/data/github_sponsors.yml
@@ -2,57 +2,51 @@ sponsors:
- - login: renderinc
avatarUrl: https://avatars.githubusercontent.com/u/36424661?v=4
url: https://github.com/renderinc
- - login: andrew-propelauth
- avatarUrl: https://avatars.githubusercontent.com/u/89474256?u=c98993dec8553c09d424ede67bbe86e5c35f48c9&v=4
- url: https://github.com/andrew-propelauth
- - login: blockbee-io
- avatarUrl: https://avatars.githubusercontent.com/u/115143449?u=1b8620c2d6567c4df2111a371b85a51f448f9b85&v=4
- url: https://github.com/blockbee-io
- - login: zuplo
- avatarUrl: https://avatars.githubusercontent.com/u/85497839?v=4
- url: https://github.com/zuplo
- - login: coderabbitai
- avatarUrl: https://avatars.githubusercontent.com/u/132028505?v=4
- url: https://github.com/coderabbitai
- - login: greptileai
- avatarUrl: https://avatars.githubusercontent.com/u/140149887?v=4
- url: https://github.com/greptileai
- login: subtotal
avatarUrl: https://avatars.githubusercontent.com/u/176449348?v=4
url: https://github.com/subtotal
+ - login: greptileai
+ avatarUrl: https://avatars.githubusercontent.com/u/140149887?v=4
+ url: https://github.com/greptileai
+ - login: coderabbitai
+ avatarUrl: https://avatars.githubusercontent.com/u/132028505?v=4
+ url: https://github.com/coderabbitai
+ - login: zuplo
+ avatarUrl: https://avatars.githubusercontent.com/u/85497839?v=4
+ url: https://github.com/zuplo
+ - login: blockbee-io
+ avatarUrl: https://avatars.githubusercontent.com/u/115143449?u=1b8620c2d6567c4df2111a371b85a51f448f9b85&v=4
+ url: https://github.com/blockbee-io
+ - login: andrew-propelauth
+ avatarUrl: https://avatars.githubusercontent.com/u/89474256?u=c98993dec8553c09d424ede67bbe86e5c35f48c9&v=4
+ url: https://github.com/andrew-propelauth
- login: railwayapp
avatarUrl: https://avatars.githubusercontent.com/u/66716858?v=4
url: https://github.com/railwayapp
-- - login: dribia
- avatarUrl: https://avatars.githubusercontent.com/u/41189616?v=4
- url: https://github.com/dribia
- - login: svix
- avatarUrl: https://avatars.githubusercontent.com/u/80175132?v=4
- url: https://github.com/svix
+- - login: speakeasy-api
+ avatarUrl: https://avatars.githubusercontent.com/u/91446104?v=4
+ url: https://github.com/speakeasy-api
- login: stainless-api
avatarUrl: https://avatars.githubusercontent.com/u/88061651?v=4
url: https://github.com/stainless-api
- - login: speakeasy-api
- avatarUrl: https://avatars.githubusercontent.com/u/91446104?v=4
- url: https://github.com/speakeasy-api
- - login: databento
- avatarUrl: https://avatars.githubusercontent.com/u/64141749?v=4
- url: https://github.com/databento
+ - login: svix
+ avatarUrl: https://avatars.githubusercontent.com/u/80175132?v=4
+ url: https://github.com/svix
- login: permitio
avatarUrl: https://avatars.githubusercontent.com/u/71775833?v=4
url: https://github.com/permitio
-- - login: Ponte-Energy-Partners
- avatarUrl: https://avatars.githubusercontent.com/u/114745848?v=4
- url: https://github.com/Ponte-Energy-Partners
- - login: LambdaTest-Inc
+ - login: databento
+ avatarUrl: https://avatars.githubusercontent.com/u/64141749?v=4
+ url: https://github.com/databento
+- - login: LambdaTest-Inc
avatarUrl: https://avatars.githubusercontent.com/u/171592363?u=96606606a45fa170427206199014f2a5a2a4920b&v=4
url: https://github.com/LambdaTest-Inc
+ - login: Ponte-Energy-Partners
+ avatarUrl: https://avatars.githubusercontent.com/u/114745848?v=4
+ url: https://github.com/Ponte-Energy-Partners
- login: BoostryJP
avatarUrl: https://avatars.githubusercontent.com/u/57932412?v=4
url: https://github.com/BoostryJP
- - login: requestly
- avatarUrl: https://avatars.githubusercontent.com/u/12287519?v=4
- url: https://github.com/requestly
- login: acsone
avatarUrl: https://avatars.githubusercontent.com/u/7601056?v=4
url: https://github.com/acsone
@@ -68,9 +62,6 @@ sponsors:
- login: Doist
avatarUrl: https://avatars.githubusercontent.com/u/2565372?v=4
url: https://github.com/Doist
- - login: bholagabbar
- avatarUrl: https://avatars.githubusercontent.com/u/11693595?v=4
- url: https://github.com/bholagabbar
- - login: mainframeindustries
avatarUrl: https://avatars.githubusercontent.com/u/55092103?v=4
url: https://github.com/mainframeindustries
@@ -86,6 +77,9 @@ sponsors:
- login: ChargeStorm
avatarUrl: https://avatars.githubusercontent.com/u/26000165?v=4
url: https://github.com/ChargeStorm
+ - login: ibrahimpelumi6142
+ avatarUrl: https://avatars.githubusercontent.com/u/113442282?v=4
+ url: https://github.com/ibrahimpelumi6142
- login: nilslindemann
avatarUrl: https://avatars.githubusercontent.com/u/6892179?u=1dca6a22195d6cd1ab20737c0e19a4c55d639472&v=4
url: https://github.com/nilslindemann
@@ -116,124 +110,127 @@ sponsors:
- login: Leay15
avatarUrl: https://avatars.githubusercontent.com/u/32212558?u=c4aa9c1737e515959382a5515381757b1fd86c53&v=4
url: https://github.com/Leay15
- - login: Karine-Bauch
- avatarUrl: https://avatars.githubusercontent.com/u/90465103?u=7feb1018abb1a5631cfd9a91fea723d1ceb5f49b&v=4
- url: https://github.com/Karine-Bauch
- login: jugeeem
avatarUrl: https://avatars.githubusercontent.com/u/116043716?u=ae590d79c38ac79c91b9c5caa6887d061e865a3d&v=4
url: https://github.com/jugeeem
- - login: patsatsia
- avatarUrl: https://avatars.githubusercontent.com/u/61111267?u=3271b85f7a37b479c8d0ae0a235182e83c166edf&v=4
- url: https://github.com/patsatsia
- - login: anthonycepeda
- avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=60bdf46240cff8fca482ff0fc07d963fd5e1a27c&v=4
- url: https://github.com/anthonycepeda
- - login: patricioperezv
- avatarUrl: https://avatars.githubusercontent.com/u/73832292?u=5f471f156e19ee7920e62ae0f4a47b95580e61cf&v=4
- url: https://github.com/patricioperezv
- - login: chickenandstats
- avatarUrl: https://avatars.githubusercontent.com/u/79477966?u=ae2b894aa954070db1d7830dab99b49eba4e4567&v=4
- url: https://github.com/chickenandstats
+ - login: Karine-Bauch
+ avatarUrl: https://avatars.githubusercontent.com/u/90465103?u=7feb1018abb1a5631cfd9a91fea723d1ceb5f49b&v=4
+ url: https://github.com/Karine-Bauch
- login: kaoru0310
avatarUrl: https://avatars.githubusercontent.com/u/80977929?u=1b61d10142b490e56af932ddf08a390fae8ee94f&v=4
url: https://github.com/kaoru0310
- - login: jstanden
- avatarUrl: https://avatars.githubusercontent.com/u/63288?u=c3658d57d2862c607a0e19c2101c3c51876e36ad&v=4
- url: https://github.com/jstanden
- - login: knallgelb
- avatarUrl: https://avatars.githubusercontent.com/u/2358812?u=c48cb6362b309d74cbf144bd6ad3aed3eb443e82&v=4
- url: https://github.com/knallgelb
- - login: dblackrun
- avatarUrl: https://avatars.githubusercontent.com/u/3528486?v=4
- url: https://github.com/dblackrun
- - login: zsinx6
- avatarUrl: https://avatars.githubusercontent.com/u/3532625?u=ba75a5dc744d1116ccfeaaf30d41cb2fe81fe8dd&v=4
- url: https://github.com/zsinx6
- - login: kennywakeland
- avatarUrl: https://avatars.githubusercontent.com/u/3631417?u=7c8f743f1ae325dfadea7c62bbf1abd6a824fc55&v=4
- url: https://github.com/kennywakeland
- - login: aacayaco
- avatarUrl: https://avatars.githubusercontent.com/u/3634801?u=eaadda178c964178fcb64886f6c732172c8f8219&v=4
- url: https://github.com/aacayaco
- - login: anomaly
- avatarUrl: https://avatars.githubusercontent.com/u/3654837?v=4
- url: https://github.com/anomaly
- - login: mj0331
- avatarUrl: https://avatars.githubusercontent.com/u/3890353?u=1c627ac1a024515b4871de5c3ebbfaa1a57f65d4&v=4
- url: https://github.com/mj0331
- - login: gorhack
- avatarUrl: https://avatars.githubusercontent.com/u/4141690?u=ec119ebc4bdf00a7bc84657a71aa17834f4f27f3&v=4
- url: https://github.com/gorhack
- - login: Ryandaydev
- avatarUrl: https://avatars.githubusercontent.com/u/4292423?u=679ff84cb7b988c5795a5fa583857f574a055763&v=4
- url: https://github.com/Ryandaydev
- - login: jaredtrog
- avatarUrl: https://avatars.githubusercontent.com/u/4381365?v=4
- url: https://github.com/jaredtrog
+ - login: chickenandstats
+ avatarUrl: https://avatars.githubusercontent.com/u/79477966?u=ae2b894aa954070db1d7830dab99b49eba4e4567&v=4
+ url: https://github.com/chickenandstats
+ - login: patricioperezv
+ avatarUrl: https://avatars.githubusercontent.com/u/73832292?u=5f471f156e19ee7920e62ae0f4a47b95580e61cf&v=4
+ url: https://github.com/patricioperezv
+ - login: anthonycepeda
+ avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=60bdf46240cff8fca482ff0fc07d963fd5e1a27c&v=4
+ url: https://github.com/anthonycepeda
+ - login: AalbatrossGuy
+ avatarUrl: https://avatars.githubusercontent.com/u/68378354?u=0bdeea9356d24f638244131f6d8d1e2d2f3601ca&v=4
+ url: https://github.com/AalbatrossGuy
+ - login: patsatsia
+ avatarUrl: https://avatars.githubusercontent.com/u/61111267?u=3271b85f7a37b479c8d0ae0a235182e83c166edf&v=4
+ url: https://github.com/patsatsia
- login: oliverxchen
avatarUrl: https://avatars.githubusercontent.com/u/4471774?u=534191f25e32eeaadda22dfab4b0a428733d5489&v=4
url: https://github.com/oliverxchen
- - login: paulcwatts
- avatarUrl: https://avatars.githubusercontent.com/u/150269?u=1819e145d573b44f0ad74b87206d21cd60331d4e&v=4
- url: https://github.com/paulcwatts
- - login: robintw
- avatarUrl: https://avatars.githubusercontent.com/u/296686?v=4
- url: https://github.com/robintw
- - login: pamelafox
- avatarUrl: https://avatars.githubusercontent.com/u/297042?v=4
- url: https://github.com/pamelafox
- - login: wshayes
- avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4
- url: https://github.com/wshayes
- - login: koxudaxi
- avatarUrl: https://avatars.githubusercontent.com/u/630670?u=507d8577b4b3670546b449c4c2ccbc5af40d72f7&v=4
- url: https://github.com/koxudaxi
- - login: falkben
- avatarUrl: https://avatars.githubusercontent.com/u/653031?u=ad9838e089058c9e5a0bab94c0eec7cc181e0cd0&v=4
- url: https://github.com/falkben
- - login: mintuhouse
- avatarUrl: https://avatars.githubusercontent.com/u/769950?u=ecfbd79a97d33177e0d093ddb088283cf7fe8444&v=4
- url: https://github.com/mintuhouse
+ - login: jaredtrog
+ avatarUrl: https://avatars.githubusercontent.com/u/4381365?v=4
+ url: https://github.com/jaredtrog
+ - login: Ryandaydev
+ avatarUrl: https://avatars.githubusercontent.com/u/4292423?u=679ff84cb7b988c5795a5fa583857f574a055763&v=4
+ url: https://github.com/Ryandaydev
+ - login: gorhack
+ avatarUrl: https://avatars.githubusercontent.com/u/4141690?u=ec119ebc4bdf00a7bc84657a71aa17834f4f27f3&v=4
+ url: https://github.com/gorhack
+ - login: mj0331
+ avatarUrl: https://avatars.githubusercontent.com/u/3890353?u=1c627ac1a024515b4871de5c3ebbfaa1a57f65d4&v=4
+ url: https://github.com/mj0331
+ - login: anomaly
+ avatarUrl: https://avatars.githubusercontent.com/u/3654837?v=4
+ url: https://github.com/anomaly
+ - login: aacayaco
+ avatarUrl: https://avatars.githubusercontent.com/u/3634801?u=eaadda178c964178fcb64886f6c732172c8f8219&v=4
+ url: https://github.com/aacayaco
+ - login: kennywakeland
+ avatarUrl: https://avatars.githubusercontent.com/u/3631417?u=7c8f743f1ae325dfadea7c62bbf1abd6a824fc55&v=4
+ url: https://github.com/kennywakeland
+ - login: zsinx6
+ avatarUrl: https://avatars.githubusercontent.com/u/3532625?u=ba75a5dc744d1116ccfeaaf30d41cb2fe81fe8dd&v=4
+ url: https://github.com/zsinx6
+ - login: dblackrun
+ avatarUrl: https://avatars.githubusercontent.com/u/3528486?v=4
+ url: https://github.com/dblackrun
+ - login: knallgelb
+ avatarUrl: https://avatars.githubusercontent.com/u/2358812?u=c48cb6362b309d74cbf144bd6ad3aed3eb443e82&v=4
+ url: https://github.com/knallgelb
- login: dodo5522
avatarUrl: https://avatars.githubusercontent.com/u/1362607?u=9bf1e0e520cccc547c046610c468ce6115bbcf9f&v=4
url: https://github.com/dodo5522
- - login: wdwinslow
- avatarUrl: https://avatars.githubusercontent.com/u/11562137?u=371272f2c69e680e0559a7b0a57385e83a5dc728&v=4
- url: https://github.com/wdwinslow
- - login: jsoques
- avatarUrl: https://avatars.githubusercontent.com/u/12414216?u=620921d94196546cc8b9eae2cc4cbc3f95bab42f&v=4
- url: https://github.com/jsoques
- - login: dannywade
- avatarUrl: https://avatars.githubusercontent.com/u/13680237?u=418ee985bd41577b20fde81417fb2d901e875e8a&v=4
- url: https://github.com/dannywade
- - login: khadrawy
- avatarUrl: https://avatars.githubusercontent.com/u/13686061?u=59f25ef42ecf04c22657aac4238ce0e2d3d30304&v=4
- url: https://github.com/khadrawy
- - login: mjohnsey
- avatarUrl: https://avatars.githubusercontent.com/u/16784016?u=38fad2e6b411244560b3af99c5f5a4751bc81865&v=4
- url: https://github.com/mjohnsey
- - login: ashi-agrawal
- avatarUrl: https://avatars.githubusercontent.com/u/17105294?u=99c7a854035e5398d8e7b674f2d42baae6c957f8&v=4
- url: https://github.com/ashi-agrawal
+ - login: mintuhouse
+ avatarUrl: https://avatars.githubusercontent.com/u/769950?u=ecfbd79a97d33177e0d093ddb088283cf7fe8444&v=4
+ url: https://github.com/mintuhouse
+ - login: falkben
+ avatarUrl: https://avatars.githubusercontent.com/u/653031?u=ad9838e089058c9e5a0bab94c0eec7cc181e0cd0&v=4
+ url: https://github.com/falkben
+ - login: koxudaxi
+ avatarUrl: https://avatars.githubusercontent.com/u/630670?u=507d8577b4b3670546b449c4c2ccbc5af40d72f7&v=4
+ url: https://github.com/koxudaxi
+ - login: wshayes
+ avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4
+ url: https://github.com/wshayes
+ - login: pamelafox
+ avatarUrl: https://avatars.githubusercontent.com/u/297042?v=4
+ url: https://github.com/pamelafox
+ - login: robintw
+ avatarUrl: https://avatars.githubusercontent.com/u/296686?v=4
+ url: https://github.com/robintw
+ - login: jstanden
+ avatarUrl: https://avatars.githubusercontent.com/u/63288?u=c3658d57d2862c607a0e19c2101c3c51876e36ad&v=4
+ url: https://github.com/jstanden
- login: RaamEEIL
avatarUrl: https://avatars.githubusercontent.com/u/20320552?v=4
url: https://github.com/RaamEEIL
- - login: ternaus
- avatarUrl: https://avatars.githubusercontent.com/u/5481618?u=513a26b02a39e7a28d587cd37c6cc877ea368e6e&v=4
- url: https://github.com/ternaus
- - login: eseglem
- avatarUrl: https://avatars.githubusercontent.com/u/5920492?u=208d419cf667b8ac594c82a8db01932c7e50d057&v=4
- url: https://github.com/eseglem
- - login: FernandoCelmer
- avatarUrl: https://avatars.githubusercontent.com/u/6262214?u=58ba6d5888fa7f355934e52db19f950e20b38162&v=4
- url: https://github.com/FernandoCelmer
- - login: Rehket
- avatarUrl: https://avatars.githubusercontent.com/u/7015688?u=3afb0ba200feebbc7f958950e92db34df2a3c172&v=4
- url: https://github.com/Rehket
+ - login: ashi-agrawal
+ avatarUrl: https://avatars.githubusercontent.com/u/17105294?u=99c7a854035e5398d8e7b674f2d42baae6c957f8&v=4
+ url: https://github.com/ashi-agrawal
+ - login: mjohnsey
+ avatarUrl: https://avatars.githubusercontent.com/u/16784016?u=38fad2e6b411244560b3af99c5f5a4751bc81865&v=4
+ url: https://github.com/mjohnsey
+ - login: khadrawy
+ avatarUrl: https://avatars.githubusercontent.com/u/13686061?u=59f25ef42ecf04c22657aac4238ce0e2d3d30304&v=4
+ url: https://github.com/khadrawy
+ - login: dannywade
+ avatarUrl: https://avatars.githubusercontent.com/u/13680237?u=418ee985bd41577b20fde81417fb2d901e875e8a&v=4
+ url: https://github.com/dannywade
+ - login: jsoques
+ avatarUrl: https://avatars.githubusercontent.com/u/12414216?u=620921d94196546cc8b9eae2cc4cbc3f95bab42f&v=4
+ url: https://github.com/jsoques
+ - login: wdwinslow
+ avatarUrl: https://avatars.githubusercontent.com/u/11562137?u=371272f2c69e680e0559a7b0a57385e83a5dc728&v=4
+ url: https://github.com/wdwinslow
- login: hiancdtrsnm
avatarUrl: https://avatars.githubusercontent.com/u/7343177?v=4
url: https://github.com/hiancdtrsnm
-- - login: manoelpqueiroz
+ - login: Rehket
+ avatarUrl: https://avatars.githubusercontent.com/u/7015688?u=3afb0ba200feebbc7f958950e92db34df2a3c172&v=4
+ url: https://github.com/Rehket
+ - login: FernandoCelmer
+ avatarUrl: https://avatars.githubusercontent.com/u/6262214?u=58ba6d5888fa7f355934e52db19f950e20b38162&v=4
+ url: https://github.com/FernandoCelmer
+ - login: eseglem
+ avatarUrl: https://avatars.githubusercontent.com/u/5920492?u=208d419cf667b8ac594c82a8db01932c7e50d057&v=4
+ url: https://github.com/eseglem
+ - login: ternaus
+ avatarUrl: https://avatars.githubusercontent.com/u/5481618?u=513a26b02a39e7a28d587cd37c6cc877ea368e6e&v=4
+ url: https://github.com/ternaus
+- - login: Artur-Galstyan
+ avatarUrl: https://avatars.githubusercontent.com/u/63471891?u=e8691f386037e51a737cc0ba866cd8c89e5cf109&v=4
+ url: https://github.com/Artur-Galstyan
+ - login: manoelpqueiroz
avatarUrl: https://avatars.githubusercontent.com/u/23669137?u=b12e84b28a84369ab5b30bd5a79e5788df5a0756&v=4
url: https://github.com/manoelpqueiroz
- - login: pawamoy
@@ -254,9 +251,12 @@ sponsors:
- login: hgalytoby
avatarUrl: https://avatars.githubusercontent.com/u/50397689?u=6cc9028f3db63f8f60ad21c17b1ce4b88c4e2e60&v=4
url: https://github.com/hgalytoby
- - login: nisutec
- avatarUrl: https://avatars.githubusercontent.com/u/25281462?u=e562484c451fdfc59053163f64405f8eb262b8b0&v=4
- url: https://github.com/nisutec
+ - login: johnl28
+ avatarUrl: https://avatars.githubusercontent.com/u/54412955?u=47dd06082d1c39caa90c752eb55566e4f3813957&v=4
+ url: https://github.com/johnl28
+ - login: danielunderwood
+ avatarUrl: https://avatars.githubusercontent.com/u/4472301?v=4
+ url: https://github.com/danielunderwood
- login: hoenie-ams
avatarUrl: https://avatars.githubusercontent.com/u/25708487?u=cda07434f0509ac728d9edf5e681117c0f6b818b&v=4
url: https://github.com/hoenie-ams
@@ -267,93 +267,87 @@ sponsors:
avatarUrl: https://avatars.githubusercontent.com/u/33275230?u=eb223cad27017bb1e936ee9b429b450d092d0236&v=4
url: https://github.com/engineerjoe440
- login: bnkc
- avatarUrl: https://avatars.githubusercontent.com/u/34930566?u=db5e6f4f87836cad26c2aa90ce390ce49041c5a9&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/34930566?u=4771ac4e64066f0847d40e5b29910adabd9b2372&v=4
url: https://github.com/bnkc
- login: petercool
avatarUrl: https://avatars.githubusercontent.com/u/37613029?u=75aa8c6729e6e8f85a300561c4dbeef9d65c8797&v=4
url: https://github.com/petercool
- - login: johnl28
- avatarUrl: https://avatars.githubusercontent.com/u/54412955?u=47dd06082d1c39caa90c752eb55566e4f3813957&v=4
- url: https://github.com/johnl28
- - login: PunRabbit
- avatarUrl: https://avatars.githubusercontent.com/u/70463212?u=1a835cfbc99295a60c8282f6aa6199d1b42241a5&v=4
- url: https://github.com/PunRabbit
- login: PelicanQ
avatarUrl: https://avatars.githubusercontent.com/u/77930606?v=4
url: https://github.com/PelicanQ
- - login: WillHogan
- avatarUrl: https://avatars.githubusercontent.com/u/1661551?u=8a80356e3e7d5a417157aba7ea565dabc8678327&v=4
- url: https://github.com/WillHogan
+ - login: PunRabbit
+ avatarUrl: https://avatars.githubusercontent.com/u/70463212?u=1a835cfbc99295a60c8282f6aa6199d1b42241a5&v=4
+ url: https://github.com/PunRabbit
- login: my3
avatarUrl: https://avatars.githubusercontent.com/u/1825270?v=4
url: https://github.com/my3
- - login: danielunderwood
- avatarUrl: https://avatars.githubusercontent.com/u/4472301?v=4
- url: https://github.com/danielunderwood
- - login: ddanier
- avatarUrl: https://avatars.githubusercontent.com/u/113563?u=ed1dc79de72f93bd78581f88ebc6952b62f472da&v=4
- url: https://github.com/ddanier
- - login: bryanculbertson
- avatarUrl: https://avatars.githubusercontent.com/u/144028?u=defda4f90e93429221cc667500944abde60ebe4a&v=4
- url: https://github.com/bryanculbertson
- - login: slafs
- avatarUrl: https://avatars.githubusercontent.com/u/210173?v=4
- url: https://github.com/slafs
- - login: ceb10n
- avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4
- url: https://github.com/ceb10n
- - login: tochikuji
- avatarUrl: https://avatars.githubusercontent.com/u/851759?v=4
- url: https://github.com/tochikuji
+ - login: WillHogan
+ avatarUrl: https://avatars.githubusercontent.com/u/1661551?u=8a80356e3e7d5a417157aba7ea565dabc8678327&v=4
+ url: https://github.com/WillHogan
- login: miguelgr
avatarUrl: https://avatars.githubusercontent.com/u/1484589?u=54556072b8136efa12ae3b6902032ea2a39ace4b&v=4
url: https://github.com/miguelgr
- - login: xncbf
- avatarUrl: https://avatars.githubusercontent.com/u/9462045?u=a80a7bb349555b277645632ed66639ff43400614&v=4
- url: https://github.com/xncbf
- - login: DMantis
- avatarUrl: https://avatars.githubusercontent.com/u/9536869?u=652dd0d49717803c0cbcbf44f7740e53cf2d4892&v=4
- url: https://github.com/DMantis
- - login: hard-coders
- avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=95db33927bbff1ed1c07efddeb97ac2ff33068ed&v=4
- url: https://github.com/hard-coders
- - login: mntolia
- avatarUrl: https://avatars.githubusercontent.com/u/10390224?v=4
- url: https://github.com/mntolia
- - login: Zuzah
- avatarUrl: https://avatars.githubusercontent.com/u/10934846?u=1ef43e075ddc87bd1178372bf4d95ee6175cae27&v=4
- url: https://github.com/Zuzah
- - login: TheR1D
- avatarUrl: https://avatars.githubusercontent.com/u/16740832?u=b0dfdbdb27b79729430c71c6128962f77b7b53f7&v=4
- url: https://github.com/TheR1D
+ - login: tochikuji
+ avatarUrl: https://avatars.githubusercontent.com/u/851759?v=4
+ url: https://github.com/tochikuji
+ - login: ceb10n
+ avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4
+ url: https://github.com/ceb10n
+ - login: slafs
+ avatarUrl: https://avatars.githubusercontent.com/u/210173?v=4
+ url: https://github.com/slafs
+ - login: bryanculbertson
+ avatarUrl: https://avatars.githubusercontent.com/u/144028?u=defda4f90e93429221cc667500944abde60ebe4a&v=4
+ url: https://github.com/bryanculbertson
+ - login: ddanier
+ avatarUrl: https://avatars.githubusercontent.com/u/113563?u=ed1dc79de72f93bd78581f88ebc6952b62f472da&v=4
+ url: https://github.com/ddanier
+ - login: nisutec
+ avatarUrl: https://avatars.githubusercontent.com/u/25281462?u=e562484c451fdfc59053163f64405f8eb262b8b0&v=4
+ url: https://github.com/nisutec
- login: joshuatz
avatarUrl: https://avatars.githubusercontent.com/u/17817563?u=f1bf05b690d1fc164218f0b420cdd3acb7913e21&v=4
url: https://github.com/joshuatz
- - login: rangulvers
- avatarUrl: https://avatars.githubusercontent.com/u/5235430?u=e254d4af4ace5a05fa58372ae677c7d26f0d5a53&v=4
- url: https://github.com/rangulvers
- - login: sdevkota
- avatarUrl: https://avatars.githubusercontent.com/u/5250987?u=4ed9a120c89805a8aefda1cbdc0cf6512e64d1b4&v=4
- url: https://github.com/sdevkota
- - login: Baghdady92
- avatarUrl: https://avatars.githubusercontent.com/u/5708590?v=4
- url: https://github.com/Baghdady92
- - login: KentShikama
- avatarUrl: https://avatars.githubusercontent.com/u/6329898?u=8b236810db9b96333230430837e1f021f9246da1&v=4
- url: https://github.com/KentShikama
- - login: katnoria
- avatarUrl: https://avatars.githubusercontent.com/u/7674948?u=09767eb13e07e09496c5fee4e5ce21d9eac34a56&v=4
- url: https://github.com/katnoria
- - login: harsh183
- avatarUrl: https://avatars.githubusercontent.com/u/7780198?v=4
- url: https://github.com/harsh183
+ - login: TheR1D
+ avatarUrl: https://avatars.githubusercontent.com/u/16740832?u=b0dfdbdb27b79729430c71c6128962f77b7b53f7&v=4
+ url: https://github.com/TheR1D
+ - login: Zuzah
+ avatarUrl: https://avatars.githubusercontent.com/u/10934846?u=1ef43e075ddc87bd1178372bf4d95ee6175cae27&v=4
+ url: https://github.com/Zuzah
+ - login: mntolia
+ avatarUrl: https://avatars.githubusercontent.com/u/10390224?v=4
+ url: https://github.com/mntolia
+ - login: hard-coders
+ avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=78d12d1acdf853c817700145e73de7fd9e5d068b&v=4
+ url: https://github.com/hard-coders
+ - login: DMantis
+ avatarUrl: https://avatars.githubusercontent.com/u/9536869?u=652dd0d49717803c0cbcbf44f7740e53cf2d4892&v=4
+ url: https://github.com/DMantis
+ - login: xncbf
+ avatarUrl: https://avatars.githubusercontent.com/u/9462045?u=a80a7bb349555b277645632ed66639ff43400614&v=4
+ url: https://github.com/xncbf
- login: moonape1226
avatarUrl: https://avatars.githubusercontent.com/u/8532038?u=d9f8b855a429fff9397c3833c2ff83849ebf989d&v=4
url: https://github.com/moonape1226
-- - login: andrecorumba
- avatarUrl: https://avatars.githubusercontent.com/u/37807517?u=9b9be3b41da9bda60957da9ef37b50dbf65baa61&v=4
- url: https://github.com/andrecorumba
- - login: KOZ39
+ - login: harsh183
+ avatarUrl: https://avatars.githubusercontent.com/u/7780198?v=4
+ url: https://github.com/harsh183
+ - login: katnoria
+ avatarUrl: https://avatars.githubusercontent.com/u/7674948?u=09767eb13e07e09496c5fee4e5ce21d9eac34a56&v=4
+ url: https://github.com/katnoria
+ - login: KentShikama
+ avatarUrl: https://avatars.githubusercontent.com/u/6329898?u=8b236810db9b96333230430837e1f021f9246da1&v=4
+ url: https://github.com/KentShikama
+ - login: Baghdady92
+ avatarUrl: https://avatars.githubusercontent.com/u/5708590?v=4
+ url: https://github.com/Baghdady92
+ - login: sdevkota
+ avatarUrl: https://avatars.githubusercontent.com/u/5250987?u=4ed9a120c89805a8aefda1cbdc0cf6512e64d1b4&v=4
+ url: https://github.com/sdevkota
+ - login: rangulvers
+ avatarUrl: https://avatars.githubusercontent.com/u/5235430?u=e254d4af4ace5a05fa58372ae677c7d26f0d5a53&v=4
+ url: https://github.com/rangulvers
+- - login: KOZ39
avatarUrl: https://avatars.githubusercontent.com/u/38822500?u=9dfc0a697df1c9628f08e20dc3fb17b1afc4e5a7&v=4
url: https://github.com/KOZ39
- login: rwxd
@@ -365,27 +359,24 @@ sponsors:
- login: Olegt0rr
avatarUrl: https://avatars.githubusercontent.com/u/25399456?u=3e87b5239a2f4600975ba13be73054f8567c6060&v=4
url: https://github.com/Olegt0rr
- - login: dinoz0rg
- avatarUrl: https://avatars.githubusercontent.com/u/32940067?u=739cda1eb123a2dd5e1db45c361396f239e23f8b&v=4
- url: https://github.com/dinoz0rg
- login: larsyngvelundin
avatarUrl: https://avatars.githubusercontent.com/u/34173819?u=74958599695bf83ac9f1addd935a51548a10c6b0&v=4
url: https://github.com/larsyngvelundin
- - login: hippoley
- avatarUrl: https://avatars.githubusercontent.com/u/135493401?u=1164ef48a645a7c12664fabc1638fbb7e1c459b0&v=4
- url: https://github.com/hippoley
- - login: 4anklee
- avatarUrl: https://avatars.githubusercontent.com/u/144109238?u=a79c0d581b2a3d8f3897e7ef4c012640a6c1eb3a&v=4
- url: https://github.com/4anklee
+ - login: andrecorumba
+ avatarUrl: https://avatars.githubusercontent.com/u/37807517?u=9b9be3b41da9bda60957da9ef37b50dbf65baa61&v=4
+ url: https://github.com/andrecorumba
- login: CoderDeltaLAN
avatarUrl: https://avatars.githubusercontent.com/u/152043745?u=4ff541efffb7d134e60c5fcf2dd1e343f90bb782&v=4
url: https://github.com/CoderDeltaLAN
- - login: onestn
- avatarUrl: https://avatars.githubusercontent.com/u/62360849?u=746dd21c34e7e06eefb11b03e8bb01aaae3c2a4f&v=4
- url: https://github.com/onestn
+ - login: hippoley
+ avatarUrl: https://avatars.githubusercontent.com/u/135493401?u=1164ef48a645a7c12664fabc1638fbb7e1c459b0&v=4
+ url: https://github.com/hippoley
- login: nayasinghania
avatarUrl: https://avatars.githubusercontent.com/u/74111380?u=752e99a5e139389fdc0a0677122adc08438eb076&v=4
url: https://github.com/nayasinghania
+ - login: onestn
+ avatarUrl: https://avatars.githubusercontent.com/u/62360849?u=746dd21c34e7e06eefb11b03e8bb01aaae3c2a4f&v=4
+ url: https://github.com/onestn
- login: Toothwitch
avatarUrl: https://avatars.githubusercontent.com/u/1710406?u=5eebb23b46cd26e48643b9e5179536cad491c17a&v=4
url: https://github.com/Toothwitch
diff --git a/docs/en/data/topic_repos.yml b/docs/en/data/topic_repos.yml
index cb7e3c033..d089c7e5a 100644
--- a/docs/en/data/topic_repos.yml
+++ b/docs/en/data/topic_repos.yml
@@ -1,495 +1,495 @@
- name: full-stack-fastapi-template
html_url: https://github.com/fastapi/full-stack-fastapi-template
- stars: 39475
+ stars: 40334
owner_login: fastapi
owner_html_url: https://github.com/fastapi
- name: Hello-Python
html_url: https://github.com/mouredev/Hello-Python
- stars: 33090
+ stars: 33628
owner_login: mouredev
owner_html_url: https://github.com/mouredev
- name: serve
html_url: https://github.com/jina-ai/serve
- stars: 21798
+ stars: 21817
owner_login: jina-ai
owner_html_url: https://github.com/jina-ai
- name: HivisionIDPhotos
html_url: https://github.com/Zeyi-Lin/HivisionIDPhotos
- stars: 20258
+ stars: 20409
owner_login: Zeyi-Lin
owner_html_url: https://github.com/Zeyi-Lin
- name: sqlmodel
html_url: https://github.com/fastapi/sqlmodel
- stars: 17212
+ stars: 17415
owner_login: fastapi
owner_html_url: https://github.com/fastapi
-- name: Douyin_TikTok_Download_API
- html_url: https://github.com/Evil0ctal/Douyin_TikTok_Download_API
- stars: 15145
- owner_login: Evil0ctal
- owner_html_url: https://github.com/Evil0ctal
- name: fastapi-best-practices
html_url: https://github.com/zhanymkanov/fastapi-best-practices
- stars: 14644
+ stars: 15776
owner_login: zhanymkanov
owner_html_url: https://github.com/zhanymkanov
+- name: Douyin_TikTok_Download_API
+ html_url: https://github.com/Evil0ctal/Douyin_TikTok_Download_API
+ stars: 15588
+ owner_login: Evil0ctal
+ owner_html_url: https://github.com/Evil0ctal
- name: machine-learning-zoomcamp
html_url: https://github.com/DataTalksClub/machine-learning-zoomcamp
- stars: 12320
+ stars: 12447
owner_login: DataTalksClub
owner_html_url: https://github.com/DataTalksClub
-- name: fastapi_mcp
- html_url: https://github.com/tadata-org/fastapi_mcp
- stars: 11174
- owner_login: tadata-org
- owner_html_url: https://github.com/tadata-org
- name: SurfSense
html_url: https://github.com/MODSetter/SurfSense
- stars: 10858
+ stars: 12128
owner_login: MODSetter
owner_html_url: https://github.com/MODSetter
+- name: fastapi_mcp
+ html_url: https://github.com/tadata-org/fastapi_mcp
+ stars: 11326
+ owner_login: tadata-org
+ owner_html_url: https://github.com/tadata-org
- name: awesome-fastapi
html_url: https://github.com/mjhea0/awesome-fastapi
- stars: 10758
+ stars: 10901
owner_login: mjhea0
owner_html_url: https://github.com/mjhea0
- name: XHS-Downloader
html_url: https://github.com/JoeanAmier/XHS-Downloader
- stars: 9313
+ stars: 9584
owner_login: JoeanAmier
owner_html_url: https://github.com/JoeanAmier
-- name: FastUI
- html_url: https://github.com/pydantic/FastUI
- stars: 8915
- owner_login: pydantic
- owner_html_url: https://github.com/pydantic
- name: polar
html_url: https://github.com/polarsource/polar
- stars: 8339
+ stars: 8951
owner_login: polarsource
owner_html_url: https://github.com/polarsource
+- name: FastUI
+ html_url: https://github.com/pydantic/FastUI
+ stars: 8934
+ owner_login: pydantic
+ owner_html_url: https://github.com/pydantic
- name: FileCodeBox
html_url: https://github.com/vastsa/FileCodeBox
- stars: 7721
+ stars: 7934
owner_login: vastsa
owner_html_url: https://github.com/vastsa
- name: nonebot2
html_url: https://github.com/nonebot/nonebot2
- stars: 7170
+ stars: 7248
owner_login: nonebot
owner_html_url: https://github.com/nonebot
- name: hatchet
html_url: https://github.com/hatchet-dev/hatchet
- stars: 6253
+ stars: 6392
owner_login: hatchet-dev
owner_html_url: https://github.com/hatchet-dev
- name: fastapi-users
html_url: https://github.com/fastapi-users/fastapi-users
- stars: 5849
+ stars: 5899
owner_login: fastapi-users
owner_html_url: https://github.com/fastapi-users
- name: serge
html_url: https://github.com/serge-chat/serge
- stars: 5756
+ stars: 5754
owner_login: serge-chat
owner_html_url: https://github.com/serge-chat
- name: strawberry
html_url: https://github.com/strawberry-graphql/strawberry
- stars: 4569
+ stars: 4577
owner_login: strawberry-graphql
owner_html_url: https://github.com/strawberry-graphql
-- name: chatgpt-web-share
- html_url: https://github.com/chatpire/chatgpt-web-share
- stars: 4294
- owner_login: chatpire
- owner_html_url: https://github.com/chatpire
- name: poem
html_url: https://github.com/poem-web/poem
- stars: 4276
+ stars: 4303
owner_login: poem-web
owner_html_url: https://github.com/poem-web
+- name: chatgpt-web-share
+ html_url: https://github.com/chatpire/chatgpt-web-share
+ stars: 4287
+ owner_login: chatpire
+ owner_html_url: https://github.com/chatpire
- name: dynaconf
html_url: https://github.com/dynaconf/dynaconf
- stars: 4202
+ stars: 4221
owner_login: dynaconf
owner_html_url: https://github.com/dynaconf
-- name: atrilabs-engine
- html_url: https://github.com/Atri-Labs/atrilabs-engine
- stars: 4093
- owner_login: Atri-Labs
- owner_html_url: https://github.com/Atri-Labs
- name: Kokoro-FastAPI
html_url: https://github.com/remsky/Kokoro-FastAPI
- stars: 4019
+ stars: 4181
owner_login: remsky
owner_html_url: https://github.com/remsky
+- name: atrilabs-engine
+ html_url: https://github.com/Atri-Labs/atrilabs-engine
+ stars: 4090
+ owner_login: Atri-Labs
+ owner_html_url: https://github.com/Atri-Labs
+- name: devpush
+ html_url: https://github.com/hunvreus/devpush
+ stars: 4037
+ owner_login: hunvreus
+ owner_html_url: https://github.com/hunvreus
- name: logfire
html_url: https://github.com/pydantic/logfire
- stars: 3805
+ stars: 3896
owner_login: pydantic
owner_html_url: https://github.com/pydantic
- name: LitServe
html_url: https://github.com/Lightning-AI/LitServe
- stars: 3719
+ stars: 3756
owner_login: Lightning-AI
owner_html_url: https://github.com/Lightning-AI
-- name: fastapi-admin
- html_url: https://github.com/fastapi-admin/fastapi-admin
- stars: 3632
- owner_login: fastapi-admin
- owner_html_url: https://github.com/fastapi-admin
-- name: datamodel-code-generator
- html_url: https://github.com/koxudaxi/datamodel-code-generator
- stars: 3609
- owner_login: koxudaxi
- owner_html_url: https://github.com/koxudaxi
- name: huma
html_url: https://github.com/danielgtaylor/huma
- stars: 3603
+ stars: 3702
owner_login: danielgtaylor
owner_html_url: https://github.com/danielgtaylor
+- name: Yuxi-Know
+ html_url: https://github.com/xerrors/Yuxi-Know
+ stars: 3680
+ owner_login: xerrors
+ owner_html_url: https://github.com/xerrors
+- name: datamodel-code-generator
+ html_url: https://github.com/koxudaxi/datamodel-code-generator
+ stars: 3675
+ owner_login: koxudaxi
+ owner_html_url: https://github.com/koxudaxi
+- name: fastapi-admin
+ html_url: https://github.com/fastapi-admin/fastapi-admin
+ stars: 3659
+ owner_login: fastapi-admin
+ owner_html_url: https://github.com/fastapi-admin
- name: farfalle
html_url: https://github.com/rashadphz/farfalle
- stars: 3490
+ stars: 3497
owner_login: rashadphz
owner_html_url: https://github.com/rashadphz
- name: tracecat
html_url: https://github.com/TracecatHQ/tracecat
- stars: 3379
+ stars: 3421
owner_login: TracecatHQ
owner_html_url: https://github.com/TracecatHQ
- name: opyrator
html_url: https://github.com/ml-tooling/opyrator
- stars: 3135
+ stars: 3136
owner_login: ml-tooling
owner_html_url: https://github.com/ml-tooling
- name: docarray
html_url: https://github.com/docarray/docarray
- stars: 3114
+ stars: 3111
owner_login: docarray
owner_html_url: https://github.com/docarray
-- name: devpush
- html_url: https://github.com/hunvreus/devpush
- stars: 3097
- owner_login: hunvreus
- owner_html_url: https://github.com/hunvreus
- name: fastapi-realworld-example-app
html_url: https://github.com/nsidnev/fastapi-realworld-example-app
- stars: 3050
+ stars: 3051
owner_login: nsidnev
owner_html_url: https://github.com/nsidnev
-- name: uvicorn-gunicorn-fastapi-docker
- html_url: https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker
- stars: 2911
- owner_login: tiangolo
- owner_html_url: https://github.com/tiangolo
- name: mcp-context-forge
html_url: https://github.com/IBM/mcp-context-forge
- stars: 2899
+ stars: 3034
owner_login: IBM
owner_html_url: https://github.com/IBM
-- name: best-of-web-python
- html_url: https://github.com/ml-tooling/best-of-web-python
- stars: 2648
- owner_login: ml-tooling
- owner_html_url: https://github.com/ml-tooling
+- name: uvicorn-gunicorn-fastapi-docker
+ html_url: https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker
+ stars: 2904
+ owner_login: tiangolo
+ owner_html_url: https://github.com/tiangolo
- name: FastAPI-template
html_url: https://github.com/s3rius/FastAPI-template
- stars: 2637
+ stars: 2680
owner_login: s3rius
owner_html_url: https://github.com/s3rius
+- name: best-of-web-python
+ html_url: https://github.com/ml-tooling/best-of-web-python
+ stars: 2662
+ owner_login: ml-tooling
+ owner_html_url: https://github.com/ml-tooling
- name: YC-Killer
html_url: https://github.com/sahibzada-allahyar/YC-Killer
- stars: 2599
+ stars: 2614
owner_login: sahibzada-allahyar
owner_html_url: https://github.com/sahibzada-allahyar
-- name: fastapi-react
- html_url: https://github.com/Buuntu/fastapi-react
- stars: 2569
- owner_login: Buuntu
- owner_html_url: https://github.com/Buuntu
-- name: Yuxi-Know
- html_url: https://github.com/xerrors/Yuxi-Know
- stars: 2563
- owner_login: xerrors
- owner_html_url: https://github.com/xerrors
- name: sqladmin
html_url: https://github.com/aminalaee/sqladmin
- stars: 2558
+ stars: 2587
owner_login: aminalaee
owner_html_url: https://github.com/aminalaee
+- name: fastapi-react
+ html_url: https://github.com/Buuntu/fastapi-react
+ stars: 2566
+ owner_login: Buuntu
+ owner_html_url: https://github.com/Buuntu
- name: RasaGPT
html_url: https://github.com/paulpierre/RasaGPT
- stars: 2451
+ stars: 2456
owner_login: paulpierre
owner_html_url: https://github.com/paulpierre
- name: supabase-py
html_url: https://github.com/supabase/supabase-py
- stars: 2344
+ stars: 2394
owner_login: supabase
owner_html_url: https://github.com/supabase
- name: nextpy
html_url: https://github.com/dot-agent/nextpy
- stars: 2335
+ stars: 2338
owner_login: dot-agent
owner_html_url: https://github.com/dot-agent
- name: fastapi-utils
html_url: https://github.com/fastapiutils/fastapi-utils
- stars: 2291
+ stars: 2289
owner_login: fastapiutils
owner_html_url: https://github.com/fastapiutils
-- name: 30-Days-of-Python
- html_url: https://github.com/codingforentrepreneurs/30-Days-of-Python
- stars: 2220
- owner_login: codingforentrepreneurs
- owner_html_url: https://github.com/codingforentrepreneurs
- name: langserve
html_url: https://github.com/langchain-ai/langserve
- stars: 2215
+ stars: 2234
owner_login: langchain-ai
owner_html_url: https://github.com/langchain-ai
+- name: 30-Days-of-Python
+ html_url: https://github.com/codingforentrepreneurs/30-Days-of-Python
+ stars: 2232
+ owner_login: codingforentrepreneurs
+ owner_html_url: https://github.com/codingforentrepreneurs
- name: solara
html_url: https://github.com/widgetti/solara
- stars: 2122
+ stars: 2141
owner_login: widgetti
owner_html_url: https://github.com/widgetti
- name: mangum
html_url: https://github.com/Kludex/mangum
- stars: 2029
+ stars: 2046
owner_login: Kludex
owner_html_url: https://github.com/Kludex
+- name: fastapi_best_architecture
+ html_url: https://github.com/fastapi-practices/fastapi_best_architecture
+ stars: 1963
+ owner_login: fastapi-practices
+ owner_html_url: https://github.com/fastapi-practices
+- name: NoteDiscovery
+ html_url: https://github.com/gamosoft/NoteDiscovery
+ stars: 1943
+ owner_login: gamosoft
+ owner_html_url: https://github.com/gamosoft
- name: agentkit
html_url: https://github.com/BCG-X-Official/agentkit
- stars: 1912
+ stars: 1936
owner_login: BCG-X-Official
owner_html_url: https://github.com/BCG-X-Official
+- name: vue-fastapi-admin
+ html_url: https://github.com/mizhexiaoxiao/vue-fastapi-admin
+ stars: 1909
+ owner_login: mizhexiaoxiao
+ owner_html_url: https://github.com/mizhexiaoxiao
- name: manage-fastapi
html_url: https://github.com/ycd/manage-fastapi
- stars: 1885
+ stars: 1887
owner_login: ycd
owner_html_url: https://github.com/ycd
- name: openapi-python-client
html_url: https://github.com/openapi-generators/openapi-python-client
- stars: 1862
+ stars: 1879
owner_login: openapi-generators
owner_html_url: https://github.com/openapi-generators
-- name: piccolo
- html_url: https://github.com/piccolo-orm/piccolo
- stars: 1836
- owner_login: piccolo-orm
- owner_html_url: https://github.com/piccolo-orm
-- name: vue-fastapi-admin
- html_url: https://github.com/mizhexiaoxiao/vue-fastapi-admin
- stars: 1831
- owner_login: mizhexiaoxiao
- owner_html_url: https://github.com/mizhexiaoxiao
-- name: python-week-2022
- html_url: https://github.com/rochacbruno/python-week-2022
- stars: 1817
- owner_login: rochacbruno
- owner_html_url: https://github.com/rochacbruno
- name: slowapi
html_url: https://github.com/laurentS/slowapi
- stars: 1798
+ stars: 1845
owner_login: laurentS
owner_html_url: https://github.com/laurentS
+- name: piccolo
+ html_url: https://github.com/piccolo-orm/piccolo
+ stars: 1843
+ owner_login: piccolo-orm
+ owner_html_url: https://github.com/piccolo-orm
+- name: python-week-2022
+ html_url: https://github.com/rochacbruno/python-week-2022
+ stars: 1813
+ owner_login: rochacbruno
+ owner_html_url: https://github.com/rochacbruno
- name: fastapi-cache
html_url: https://github.com/long2ice/fastapi-cache
- stars: 1789
+ stars: 1805
owner_login: long2ice
owner_html_url: https://github.com/long2ice
- name: ormar
html_url: https://github.com/collerek/ormar
- stars: 1783
+ stars: 1785
owner_login: collerek
owner_html_url: https://github.com/collerek
-- name: termpair
- html_url: https://github.com/cs01/termpair
- stars: 1716
- owner_login: cs01
- owner_html_url: https://github.com/cs01
-- name: FastAPI-boilerplate
- html_url: https://github.com/benavlabs/FastAPI-boilerplate
- stars: 1660
- owner_login: benavlabs
- owner_html_url: https://github.com/benavlabs
- name: fastapi-langgraph-agent-production-ready-template
html_url: https://github.com/wassim249/fastapi-langgraph-agent-production-ready-template
- stars: 1638
+ stars: 1780
owner_login: wassim249
owner_html_url: https://github.com/wassim249
-- name: langchain-serve
- html_url: https://github.com/jina-ai/langchain-serve
- stars: 1635
- owner_login: jina-ai
- owner_html_url: https://github.com/jina-ai
-- name: awesome-fastapi-projects
- html_url: https://github.com/Kludex/awesome-fastapi-projects
- stars: 1589
- owner_login: Kludex
- owner_html_url: https://github.com/Kludex
-- name: fastapi-pagination
- html_url: https://github.com/uriyyo/fastapi-pagination
- stars: 1585
- owner_login: uriyyo
- owner_html_url: https://github.com/uriyyo
-- name: coronavirus-tracker-api
- html_url: https://github.com/ExpDev07/coronavirus-tracker-api
- stars: 1574
- owner_login: ExpDev07
- owner_html_url: https://github.com/ExpDev07
+- name: FastAPI-boilerplate
+ html_url: https://github.com/benavlabs/FastAPI-boilerplate
+ stars: 1734
+ owner_login: benavlabs
+ owner_html_url: https://github.com/benavlabs
+- name: termpair
+ html_url: https://github.com/cs01/termpair
+ stars: 1724
+ owner_login: cs01
+ owner_html_url: https://github.com/cs01
- name: fastapi-crudrouter
html_url: https://github.com/awtkns/fastapi-crudrouter
- stars: 1559
+ stars: 1671
owner_login: awtkns
owner_html_url: https://github.com/awtkns
+- name: langchain-serve
+ html_url: https://github.com/jina-ai/langchain-serve
+ stars: 1633
+ owner_login: jina-ai
+ owner_html_url: https://github.com/jina-ai
+- name: fastapi-pagination
+ html_url: https://github.com/uriyyo/fastapi-pagination
+ stars: 1588
+ owner_login: uriyyo
+ owner_html_url: https://github.com/uriyyo
+- name: awesome-fastapi-projects
+ html_url: https://github.com/Kludex/awesome-fastapi-projects
+ stars: 1583
+ owner_login: Kludex
+ owner_html_url: https://github.com/Kludex
+- name: coronavirus-tracker-api
+ html_url: https://github.com/ExpDev07/coronavirus-tracker-api
+ stars: 1571
+ owner_login: ExpDev07
+ owner_html_url: https://github.com/ExpDev07
- name: bracket
html_url: https://github.com/evroon/bracket
- stars: 1489
+ stars: 1549
owner_login: evroon
owner_html_url: https://github.com/evroon
- name: fastapi-amis-admin
html_url: https://github.com/amisadmin/fastapi-amis-admin
- stars: 1475
+ stars: 1491
owner_login: amisadmin
owner_html_url: https://github.com/amisadmin
- name: fastapi-boilerplate
html_url: https://github.com/teamhide/fastapi-boilerplate
- stars: 1436
+ stars: 1452
owner_login: teamhide
owner_html_url: https://github.com/teamhide
-- name: awesome-python-resources
- html_url: https://github.com/DjangoEx/awesome-python-resources
- stars: 1426
- owner_login: DjangoEx
- owner_html_url: https://github.com/DjangoEx
- name: fastcrud
html_url: https://github.com/benavlabs/fastcrud
- stars: 1414
+ stars: 1452
owner_login: benavlabs
owner_html_url: https://github.com/benavlabs
+- name: awesome-python-resources
+ html_url: https://github.com/DjangoEx/awesome-python-resources
+ stars: 1430
+ owner_login: DjangoEx
+ owner_html_url: https://github.com/DjangoEx
- name: prometheus-fastapi-instrumentator
html_url: https://github.com/trallnag/prometheus-fastapi-instrumentator
- stars: 1388
+ stars: 1399
owner_login: trallnag
owner_html_url: https://github.com/trallnag
-- name: fastapi_best_architecture
- html_url: https://github.com/fastapi-practices/fastapi_best_architecture
- stars: 1378
- owner_login: fastapi-practices
- owner_html_url: https://github.com/fastapi-practices
- name: fastapi-code-generator
html_url: https://github.com/koxudaxi/fastapi-code-generator
- stars: 1375
+ stars: 1371
owner_login: koxudaxi
owner_html_url: https://github.com/koxudaxi
+- name: fastapi-tutorial
+ html_url: https://github.com/liaogx/fastapi-tutorial
+ stars: 1346
+ owner_login: liaogx
+ owner_html_url: https://github.com/liaogx
- name: budgetml
html_url: https://github.com/ebhy/budgetml
stars: 1345
owner_login: ebhy
owner_html_url: https://github.com/ebhy
-- name: fastapi-tutorial
- html_url: https://github.com/liaogx/fastapi-tutorial
- stars: 1327
- owner_login: liaogx
- owner_html_url: https://github.com/liaogx
-- name: fastapi-alembic-sqlmodel-async
- html_url: https://github.com/jonra1993/fastapi-alembic-sqlmodel-async
- stars: 1259
- owner_login: jonra1993
- owner_html_url: https://github.com/jonra1993
- name: fastapi-scaff
html_url: https://github.com/atpuxiner/fastapi-scaff
- stars: 1255
+ stars: 1331
owner_login: atpuxiner
owner_html_url: https://github.com/atpuxiner
-- name: bedrock-chat
- html_url: https://github.com/aws-samples/bedrock-chat
- stars: 1254
- owner_login: aws-samples
- owner_html_url: https://github.com/aws-samples
- name: bolt-python
html_url: https://github.com/slackapi/bolt-python
- stars: 1253
+ stars: 1266
owner_login: slackapi
owner_html_url: https://github.com/slackapi
+- name: bedrock-chat
+ html_url: https://github.com/aws-samples/bedrock-chat
+ stars: 1266
+ owner_login: aws-samples
+ owner_html_url: https://github.com/aws-samples
+- name: fastapi-alembic-sqlmodel-async
+ html_url: https://github.com/jonra1993/fastapi-alembic-sqlmodel-async
+ stars: 1260
+ owner_login: jonra1993
+ owner_html_url: https://github.com/jonra1993
- name: fastapi_production_template
html_url: https://github.com/zhanymkanov/fastapi_production_template
- stars: 1217
+ stars: 1222
owner_login: zhanymkanov
owner_html_url: https://github.com/zhanymkanov
- name: langchain-extract
html_url: https://github.com/langchain-ai/langchain-extract
- stars: 1176
+ stars: 1179
owner_login: langchain-ai
owner_html_url: https://github.com/langchain-ai
- name: restish
html_url: https://github.com/rest-sh/restish
- stars: 1140
+ stars: 1152
owner_login: rest-sh
owner_html_url: https://github.com/rest-sh
- name: odmantic
html_url: https://github.com/art049/odmantic
- stars: 1138
+ stars: 1143
owner_login: art049
owner_html_url: https://github.com/art049
- name: authx
html_url: https://github.com/yezz123/authx
- stars: 1119
+ stars: 1128
owner_login: yezz123
owner_html_url: https://github.com/yezz123
-- name: NoteDiscovery
- html_url: https://github.com/gamosoft/NoteDiscovery
- stars: 1107
- owner_login: gamosoft
- owner_html_url: https://github.com/gamosoft
-- name: flock
- html_url: https://github.com/Onelevenvy/flock
- stars: 1055
- owner_login: Onelevenvy
- owner_html_url: https://github.com/Onelevenvy
-- name: fastapi-observability
- html_url: https://github.com/blueswen/fastapi-observability
- stars: 1038
- owner_login: blueswen
- owner_html_url: https://github.com/blueswen
+- name: SAG
+ html_url: https://github.com/Zleap-AI/SAG
+ stars: 1104
+ owner_login: Zleap-AI
+ owner_html_url: https://github.com/Zleap-AI
- name: aktools
html_url: https://github.com/akfamily/aktools
- stars: 1027
+ stars: 1072
owner_login: akfamily
owner_html_url: https://github.com/akfamily
- name: RuoYi-Vue3-FastAPI
html_url: https://github.com/insistence/RuoYi-Vue3-FastAPI
- stars: 1016
+ stars: 1063
owner_login: insistence
owner_html_url: https://github.com/insistence
-- name: autollm
- html_url: https://github.com/viddexa/autollm
- stars: 1002
- owner_login: viddexa
- owner_html_url: https://github.com/viddexa
-- name: titiler
- html_url: https://github.com/developmentseed/titiler
- stars: 999
- owner_login: developmentseed
- owner_html_url: https://github.com/developmentseed
-- name: lanarky
- html_url: https://github.com/ajndkr/lanarky
- stars: 994
- owner_login: ajndkr
- owner_html_url: https://github.com/ajndkr
-- name: every-pdf
- html_url: https://github.com/DDULDDUCK/every-pdf
- stars: 985
- owner_login: DDULDDUCK
- owner_html_url: https://github.com/DDULDDUCK
+- name: flock
+ html_url: https://github.com/Onelevenvy/flock
+ stars: 1059
+ owner_login: Onelevenvy
+ owner_html_url: https://github.com/Onelevenvy
+- name: fastapi-observability
+ html_url: https://github.com/blueswen/fastapi-observability
+ stars: 1046
+ owner_login: blueswen
+ owner_html_url: https://github.com/blueswen
- name: enterprise-deep-research
html_url: https://github.com/SalesforceAIResearch/enterprise-deep-research
- stars: 973
+ stars: 1019
owner_login: SalesforceAIResearch
owner_html_url: https://github.com/SalesforceAIResearch
-- name: fastapi-mail
- html_url: https://github.com/sabuhish/fastapi-mail
- stars: 964
- owner_login: sabuhish
- owner_html_url: https://github.com/sabuhish
+- name: titiler
+ html_url: https://github.com/developmentseed/titiler
+ stars: 1016
+ owner_login: developmentseed
+ owner_html_url: https://github.com/developmentseed
+- name: every-pdf
+ html_url: https://github.com/DDULDDUCK/every-pdf
+ stars: 1004
+ owner_login: DDULDDUCK
+ owner_html_url: https://github.com/DDULDDUCK
+- name: autollm
+ html_url: https://github.com/viddexa/autollm
+ stars: 1003
+ owner_login: viddexa
+ owner_html_url: https://github.com/viddexa
+- name: lanarky
+ html_url: https://github.com/ajndkr/lanarky
+ stars: 996
+ owner_login: ajndkr
+ owner_html_url: https://github.com/ajndkr
diff --git a/docs/en/data/translation_reviewers.yml b/docs/en/data/translation_reviewers.yml
index c3d3d0388..62db8e805 100644
--- a/docs/en/data/translation_reviewers.yml
+++ b/docs/en/data/translation_reviewers.yml
@@ -15,7 +15,7 @@ sodaMelon:
url: https://github.com/sodaMelon
ceb10n:
login: ceb10n
- count: 116
+ count: 117
avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4
url: https://github.com/ceb10n
tokusumi:
@@ -23,16 +23,16 @@ tokusumi:
count: 104
avatarUrl: https://avatars.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4
url: https://github.com/tokusumi
+hard-coders:
+ login: hard-coders
+ count: 96
+ avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=78d12d1acdf853c817700145e73de7fd9e5d068b&v=4
+ url: https://github.com/hard-coders
hasansezertasan:
login: hasansezertasan
count: 95
avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=99f0b0f0fc47e88e8abb337b4447357939ef93e7&v=4
url: https://github.com/hasansezertasan
-hard-coders:
- login: hard-coders
- count: 93
- avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=95db33927bbff1ed1c07efddeb97ac2ff33068ed&v=4
- url: https://github.com/hard-coders
alv2017:
login: alv2017
count: 88
@@ -40,7 +40,7 @@ alv2017:
url: https://github.com/alv2017
nazarepiedady:
login: nazarepiedady
- count: 86
+ count: 87
avatarUrl: https://avatars.githubusercontent.com/u/31008635?u=f69ddc4ea8bda3bdfac7aa0e2ea38de282e6ee2d&v=4
url: https://github.com/nazarepiedady
AlertRED:
@@ -48,6 +48,11 @@ AlertRED:
count: 81
avatarUrl: https://avatars.githubusercontent.com/u/15695000?u=f5a4944c6df443030409c88da7d7fa0b7ead985c&v=4
url: https://github.com/AlertRED
+tiangolo:
+ login: tiangolo
+ count: 73
+ avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4
+ url: https://github.com/tiangolo
Alexandrhub:
login: Alexandrhub
count: 68
@@ -63,21 +68,16 @@ cassiobotaro:
count: 62
avatarUrl: https://avatars.githubusercontent.com/u/3127847?u=a08022b191ddbd0a6159b2981d9d878b6d5bb71f&v=4
url: https://github.com/cassiobotaro
+mattwang44:
+ login: mattwang44
+ count: 61
+ avatarUrl: https://avatars.githubusercontent.com/u/24987826?u=58e37fb3927b9124b458945ac4c97aa0f1062d85&v=4
+ url: https://github.com/mattwang44
nilslindemann:
login: nilslindemann
count: 59
avatarUrl: https://avatars.githubusercontent.com/u/6892179?u=1dca6a22195d6cd1ab20737c0e19a4c55d639472&v=4
url: https://github.com/nilslindemann
-mattwang44:
- login: mattwang44
- count: 59
- avatarUrl: https://avatars.githubusercontent.com/u/24987826?u=58e37fb3927b9124b458945ac4c97aa0f1062d85&v=4
- url: https://github.com/mattwang44
-tiangolo:
- login: tiangolo
- count: 56
- avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4
- url: https://github.com/tiangolo
Laineyzhang55:
login: Laineyzhang55
count: 48
@@ -88,6 +88,11 @@ Kludex:
count: 47
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4
url: https://github.com/Kludex
+YuriiMotov:
+ login: YuriiMotov
+ count: 46
+ avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4
+ url: https://github.com/YuriiMotov
komtaki:
login: komtaki
count: 45
@@ -118,11 +123,6 @@ Winand:
count: 40
avatarUrl: https://avatars.githubusercontent.com/u/53390?u=bb0e71a2fc3910a8e0ee66da67c33de40ea695f8&v=4
url: https://github.com/Winand
-YuriiMotov:
- login: YuriiMotov
- count: 40
- avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4
- url: https://github.com/YuriiMotov
solomein-sv:
login: solomein-sv
count: 38
@@ -138,6 +138,11 @@ alejsdev:
count: 37
avatarUrl: https://avatars.githubusercontent.com/u/90076947?u=85ceac49fb87138aebe8d663912e359447329090&v=4
url: https://github.com/alejsdev
+mezgoodle:
+ login: mezgoodle
+ count: 37
+ avatarUrl: https://avatars.githubusercontent.com/u/41520940?u=4a9c765af688389d54296845d18b8f6cd6ddf09a&v=4
+ url: https://github.com/mezgoodle
stlucasgarcia:
login: stlucasgarcia
count: 36
@@ -153,11 +158,6 @@ timothy-jeong:
count: 36
avatarUrl: https://avatars.githubusercontent.com/u/53824764?u=db3d0cea2f5fab64d810113c5039a369699a2774&v=4
url: https://github.com/timothy-jeong
-mezgoodle:
- login: mezgoodle
- count: 35
- avatarUrl: https://avatars.githubusercontent.com/u/41520940?u=4a9c765af688389d54296845d18b8f6cd6ddf09a&v=4
- url: https://github.com/mezgoodle
rjNemo:
login: rjNemo
count: 34
@@ -173,6 +173,11 @@ akarev0:
count: 33
avatarUrl: https://avatars.githubusercontent.com/u/53393089?u=6e528bb4789d56af887ce6fe237bea4010885406&v=4
url: https://github.com/akarev0
+Vincy1230:
+ login: Vincy1230
+ count: 33
+ avatarUrl: https://avatars.githubusercontent.com/u/81342412?u=ab5e256a4077a4a91f3f9cd2115ba80780454cbe&v=4
+ url: https://github.com/Vincy1230
romashevchenko:
login: romashevchenko
count: 32
@@ -183,11 +188,6 @@ LorhanSohaky:
count: 30
avatarUrl: https://avatars.githubusercontent.com/u/16273730?u=095b66f243a2cd6a0aadba9a095009f8aaf18393&v=4
url: https://github.com/LorhanSohaky
-Vincy1230:
- login: Vincy1230
- count: 30
- avatarUrl: https://avatars.githubusercontent.com/u/81342412?u=ab5e256a4077a4a91f3f9cd2115ba80780454cbe&v=4
- url: https://github.com/Vincy1230
black-redoc:
login: black-redoc
count: 29
@@ -250,7 +250,7 @@ mycaule:
url: https://github.com/mycaule
Aruelius:
login: Aruelius
- count: 24
+ count: 25
avatarUrl: https://avatars.githubusercontent.com/u/25380989?u=574f8cfcda3ea77a3f81884f6b26a97068e36a9d&v=4
url: https://github.com/Aruelius
wisderfin:
@@ -263,6 +263,11 @@ OzgunCaglarArslan:
count: 24
avatarUrl: https://avatars.githubusercontent.com/u/86166426?v=4
url: https://github.com/OzgunCaglarArslan
+ycd:
+ login: ycd
+ count: 23
+ avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=f1e7bae394a315da950912c92dc861a8eaf95d4c&v=4
+ url: https://github.com/ycd
sh0nk:
login: sh0nk
count: 23
@@ -288,11 +293,6 @@ Attsun1031:
count: 20
avatarUrl: https://avatars.githubusercontent.com/u/1175560?v=4
url: https://github.com/Attsun1031
-ycd:
- login: ycd
- count: 20
- avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=f1e7bae394a315da950912c92dc861a8eaf95d4c&v=4
- url: https://github.com/ycd
delhi09:
login: delhi09
count: 20
@@ -418,6 +418,11 @@ mattkoehne:
count: 14
avatarUrl: https://avatars.githubusercontent.com/u/80362153?v=4
url: https://github.com/mattkoehne
+maru0123-2004:
+ login: maru0123-2004
+ count: 14
+ avatarUrl: https://avatars.githubusercontent.com/u/43961566?u=16ed8603a4d6a4665cb6c53a7aece6f31379b769&v=4
+ url: https://github.com/maru0123-2004
jovicon:
login: jovicon
count: 13
@@ -443,6 +448,11 @@ impocode:
count: 13
avatarUrl: https://avatars.githubusercontent.com/u/109408819?u=9cdfc5ccb31a2094c520f41b6087012fa9048982&v=4
url: https://github.com/impocode
+waketzheng:
+ login: waketzheng
+ count: 13
+ avatarUrl: https://avatars.githubusercontent.com/u/35413830?u=df19e4fd5bb928e7d086e053ef26a46aad23bf84&v=4
+ url: https://github.com/waketzheng
wesinalves:
login: wesinalves
count: 13
@@ -538,21 +548,16 @@ Lufa1u:
count: 11
avatarUrl: https://avatars.githubusercontent.com/u/112495876?u=087658920ed9e74311597bdd921d8d2de939d276&v=4
url: https://github.com/Lufa1u
-waketzheng:
- login: waketzheng
- count: 11
- avatarUrl: https://avatars.githubusercontent.com/u/35413830?u=df19e4fd5bb928e7d086e053ef26a46aad23bf84&v=4
- url: https://github.com/waketzheng
KNChiu:
login: KNChiu
count: 11
avatarUrl: https://avatars.githubusercontent.com/u/36751646?v=4
url: https://github.com/KNChiu
-maru0123-2004:
- login: maru0123-2004
+Zhongheng-Cheng:
+ login: Zhongheng-Cheng
count: 11
- avatarUrl: https://avatars.githubusercontent.com/u/43961566?u=16ed8603a4d6a4665cb6c53a7aece6f31379b769&v=4
- url: https://github.com/maru0123-2004
+ avatarUrl: https://avatars.githubusercontent.com/u/95612344?u=a0f7730a3cc7486827965e01a119ad610bda4b0a&v=4
+ url: https://github.com/Zhongheng-Cheng
mariacamilagl:
login: mariacamilagl
count: 10
@@ -608,16 +613,16 @@ nick-cjyx9:
count: 10
avatarUrl: https://avatars.githubusercontent.com/u/119087246?u=7227a2de948c68fb8396d5beff1ee5b0e057c42e&v=4
url: https://github.com/nick-cjyx9
+marcelomarkus:
+ login: marcelomarkus
+ count: 10
+ avatarUrl: https://avatars.githubusercontent.com/u/20115018?u=dda090ce9160ef0cd2ff69b1e5ea741283425cba&v=4
+ url: https://github.com/marcelomarkus
lucasbalieiro:
login: lucasbalieiro
count: 10
- avatarUrl: https://avatars.githubusercontent.com/u/37416577?u=dad91601ee4f40458d691774ec439aff308344d7&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/37416577?u=d144221c34c08adac8b20e1833d776ffa1c4b1d0&v=4
url: https://github.com/lucasbalieiro
-Zhongheng-Cheng:
- login: Zhongheng-Cheng
- count: 10
- avatarUrl: https://avatars.githubusercontent.com/u/95612344?u=a0f7730a3cc7486827965e01a119ad610bda4b0a&v=4
- url: https://github.com/Zhongheng-Cheng
RunningIkkyu:
login: RunningIkkyu
count: 9
@@ -668,11 +673,6 @@ yodai-yodai:
count: 9
avatarUrl: https://avatars.githubusercontent.com/u/7031039?u=4f3593f5931892b931a745cfab846eff6e9332e7&v=4
url: https://github.com/yodai-yodai
-marcelomarkus:
- login: marcelomarkus
- count: 9
- avatarUrl: https://avatars.githubusercontent.com/u/20115018?u=dda090ce9160ef0cd2ff69b1e5ea741283425cba&v=4
- url: https://github.com/marcelomarkus
JoaoGustavoRogel:
login: JoaoGustavoRogel
count: 9
@@ -683,6 +683,11 @@ Yarous:
count: 9
avatarUrl: https://avatars.githubusercontent.com/u/61277193?u=5b462347458a373b2d599c6f416d2b75eddbffad&v=4
url: https://github.com/Yarous
+Pyth3rEx:
+ login: Pyth3rEx
+ count: 9
+ avatarUrl: https://avatars.githubusercontent.com/u/26427764?u=087724f74d813c95925d51e354554bd4b6d6bb60&v=4
+ url: https://github.com/Pyth3rEx
dimaqq:
login: dimaqq
count: 8
@@ -1023,6 +1028,11 @@ devluisrodrigues:
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/21125286?v=4
url: https://github.com/11kkw
+EdmilsonRodrigues:
+ login: EdmilsonRodrigues
+ count: 5
+ avatarUrl: https://avatars.githubusercontent.com/u/62777025?u=217d6f3cd6cc750bb8818a3af7726c8d74eb7c2d&v=4
+ url: https://github.com/EdmilsonRodrigues
lpdswing:
login: lpdswing
count: 4
@@ -1163,6 +1173,11 @@ AbolfazlKameli:
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/120686133?u=af8f025278cce0d489007071254e4055df60b78c&v=4
url: https://github.com/AbolfazlKameli
+SBillion:
+ login: SBillion
+ count: 4
+ avatarUrl: https://avatars.githubusercontent.com/u/1070649?u=3ab493dfc88b39da0eb1600e3b8e7df1c90a5dee&v=4
+ url: https://github.com/SBillion
tyronedamasceno:
login: tyronedamasceno
count: 3
@@ -1211,7 +1226,7 @@ phamquanganh31101998:
peebbv6364:
login: peebbv6364
count: 3
- avatarUrl: https://avatars.githubusercontent.com/u/26784747?u=75583df215ee01a5cd2dc646aecb81e7dbd33d06&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/26784747?u=3bf07017eb4f4fa3639ba8d4ed19980a34bf8f90&v=4
url: https://github.com/peebbv6364
mrparalon:
login: mrparalon
@@ -1413,11 +1428,6 @@ Mohammad222PR:
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/116789737?u=25810a5fe049d2f1618e2e7417cea011cc353ce4&v=4
url: https://github.com/Mohammad222PR
-EdmilsonRodrigues:
- login: EdmilsonRodrigues
- count: 3
- avatarUrl: https://avatars.githubusercontent.com/u/62777025?u=217d6f3cd6cc750bb8818a3af7726c8d74eb7c2d&v=4
- url: https://github.com/EdmilsonRodrigues
blaisep:
login: blaisep
count: 2
@@ -1838,11 +1848,11 @@ NavesSapnis:
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/79222417?u=b5b10291b8e9130ca84fd20f0a641e04ed94b6b1&v=4
url: https://github.com/NavesSapnis
-eqsdxr:
- login: eqsdxr
+isgin01:
+ login: isgin01
count: 2
- avatarUrl: https://avatars.githubusercontent.com/u/157279130?u=7927dc0366995334f9a18c3204a41d3a34d6d96f&v=4
- url: https://github.com/eqsdxr
+ avatarUrl: https://avatars.githubusercontent.com/u/157279130?u=ddffde10876b50f35dc90d1337f507a630530a6a&v=4
+ url: https://github.com/isgin01
syedasamina56:
login: syedasamina56
count: 2
diff --git a/docs/en/data/translators.yml b/docs/en/data/translators.yml
index c66eff4d4..940b128da 100644
--- a/docs/en/data/translators.yml
+++ b/docs/en/data/translators.yml
@@ -1,6 +1,6 @@
nilslindemann:
login: nilslindemann
- count: 125
+ count: 130
avatarUrl: https://avatars.githubusercontent.com/u/6892179?u=1dca6a22195d6cd1ab20737c0e19a4c55d639472&v=4
url: https://github.com/nilslindemann
jaystone776:
@@ -28,6 +28,11 @@ SwftAlpc:
count: 23
avatarUrl: https://avatars.githubusercontent.com/u/52768429?u=6a3aa15277406520ad37f6236e89466ed44bc5b8&v=4
url: https://github.com/SwftAlpc
+tiangolo:
+ login: tiangolo
+ count: 22
+ avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4
+ url: https://github.com/tiangolo
hasansezertasan:
login: hasansezertasan
count: 22
@@ -46,7 +51,7 @@ AlertRED:
hard-coders:
login: hard-coders
count: 15
- avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=95db33927bbff1ed1c07efddeb97ac2ff33068ed&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=78d12d1acdf853c817700145e73de7fd9e5d068b&v=4
url: https://github.com/hard-coders
Joao-Pedro-P-Holanda:
login: Joao-Pedro-P-Holanda
@@ -103,11 +108,6 @@ pablocm83:
count: 8
avatarUrl: https://avatars.githubusercontent.com/u/28315068?u=3310fbb05bb8bfc50d2c48b6cb64ac9ee4a14549&v=4
url: https://github.com/pablocm83
-tiangolo:
- login: tiangolo
- count: 7
- avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4
- url: https://github.com/tiangolo
ptt3199:
login: ptt3199
count: 7
@@ -126,13 +126,18 @@ batlopes:
lucasbalieiro:
login: lucasbalieiro
count: 6
- avatarUrl: https://avatars.githubusercontent.com/u/37416577?u=dad91601ee4f40458d691774ec439aff308344d7&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/37416577?u=d144221c34c08adac8b20e1833d776ffa1c4b1d0&v=4
url: https://github.com/lucasbalieiro
Alexandrhub:
login: Alexandrhub
count: 6
avatarUrl: https://avatars.githubusercontent.com/u/119126536?u=9fc0d48f3307817bafecc5861eb2168401a6cb04&v=4
url: https://github.com/Alexandrhub
+YuriiMotov:
+ login: YuriiMotov
+ count: 6
+ avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4
+ url: https://github.com/YuriiMotov
Serrones:
login: Serrones
count: 5
@@ -358,11 +363,6 @@ ruzia:
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/24503?v=4
url: https://github.com/ruzia
-YuriiMotov:
- login: YuriiMotov
- count: 3
- avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4
- url: https://github.com/YuriiMotov
izaguerreiro:
login: izaguerreiro
count: 2
diff --git a/docs/en/docs/_llm-test.md b/docs/en/docs/_llm-test.md
index 9f216f9d7..d218f7c76 100644
--- a/docs/en/docs/_llm-test.md
+++ b/docs/en/docs/_llm-test.md
@@ -6,7 +6,7 @@ Tests added here will be seen by all designers of language specific prompts.
Use as follows:
-* Have a language specific prompt – `docs/{language code}/llm-prompt.md`.
+* Have a language specific prompt - `docs/{language code}/llm-prompt.md`.
* Do a fresh translation of this document into your desired target language (see e.g. the `translate-page` command of the `translate.py`). This will create the translation under `docs/{language code}/docs/_llm-test.md`.
* Check if things are okay in the translation.
* If necessary, improve your language specific prompt, the general prompt, or the English document.
diff --git a/docs/en/docs/advanced/dataclasses.md b/docs/en/docs/advanced/dataclasses.md
index 574beb65f..dbc91409a 100644
--- a/docs/en/docs/advanced/dataclasses.md
+++ b/docs/en/docs/advanced/dataclasses.md
@@ -4,7 +4,7 @@ FastAPI is built on top of **Pydantic**, and I have been showing you how to use
But FastAPI also supports using `dataclasses` the same way:
-{* ../../docs_src/dataclasses/tutorial001_py310.py hl[1,6:11,18:19] *}
+{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *}
This is still supported thanks to **Pydantic**, as it has internal support for `dataclasses`.
@@ -32,7 +32,7 @@ But if you have a bunch of dataclasses laying around, this is a nice trick to us
You can also use `dataclasses` in the `response_model` parameter:
-{* ../../docs_src/dataclasses/tutorial002_py310.py hl[1,6:12,18] *}
+{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *}
The dataclass will be automatically converted to a Pydantic dataclass.
@@ -48,7 +48,7 @@ In some cases, you might still have to use Pydantic's version of `dataclasses`.
In that case, you can simply swap the standard `dataclasses` with `pydantic.dataclasses`, which is a drop-in replacement:
-{* ../../docs_src/dataclasses/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
+{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
1. We still import `field` from standard `dataclasses`.
diff --git a/docs/en/docs/how-to/graphql.md b/docs/en/docs/how-to/graphql.md
index a002c08ca..666f819b0 100644
--- a/docs/en/docs/how-to/graphql.md
+++ b/docs/en/docs/how-to/graphql.md
@@ -35,7 +35,7 @@ Depending on your use case, you might prefer to use a different library, but if
Here's a small preview of how you could integrate Strawberry with FastAPI:
-{* ../../docs_src/graphql/tutorial001_py39.py hl[3,22,25] *}
+{* ../../docs_src/graphql_/tutorial001_py39.py hl[3,22,25] *}
You can learn more about Strawberry in the Strawberry documentation.
diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md
index bc2441f7f..9bd794f03 100644
--- a/docs/en/docs/release-notes.md
+++ b/docs/en/docs/release-notes.md
@@ -7,6 +7,39 @@ hide:
## Latest Changes
+### Translations
+
+* 🔧 Add LLM prompt file for Turkish, generated from the existing translations. PR [#14547](https://github.com/fastapi/fastapi/pull/14547) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Add LLM prompt file for Traditional Chinese, generated from the existing translations. PR [#14550](https://github.com/fastapi/fastapi/pull/14550) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Add LLM prompt file for Simplified Chinese, generated from the existing translations. PR [#14549](https://github.com/fastapi/fastapi/pull/14549) by [@tiangolo](https://github.com/tiangolo).
+
+### Internal
+
+* 👥 Update FastAPI People - Sponsors. PR [#14626](https://github.com/fastapi/fastapi/pull/14626) by [@tiangolo](https://github.com/tiangolo).
+* 👥 Update FastAPI GitHub topic repositories. PR [#14630](https://github.com/fastapi/fastapi/pull/14630) by [@tiangolo](https://github.com/tiangolo).
+* 👥 Update FastAPI People - Contributors and Translators. PR [#14625](https://github.com/fastapi/fastapi/pull/14625) by [@tiangolo](https://github.com/tiangolo).
+* 🌐 Update translation prompts. PR [#14619](https://github.com/fastapi/fastapi/pull/14619) by [@tiangolo](https://github.com/tiangolo).
+* 🔨 Update LLM translation script to guide reviewers to change the prompt. PR [#14614](https://github.com/fastapi/fastapi/pull/14614) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Do not run translations on cron while finishing updating existing languages. PR [#14613](https://github.com/fastapi/fastapi/pull/14613) by [@tiangolo](https://github.com/tiangolo).
+* 🔥 Remove test variants for Pydantic v1 in test_request_params. PR [#14612](https://github.com/fastapi/fastapi/pull/14612) by [@tiangolo](https://github.com/tiangolo).
+* 🔥 Remove Pydantic v1 specific test variants. PR [#14611](https://github.com/fastapi/fastapi/pull/14611) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.128.0
+
+### Breaking Changes
+
+* ➖ Drop support for `pydantic.v1`. PR [#14609](https://github.com/fastapi/fastapi/pull/14609) by [@tiangolo](https://github.com/tiangolo).
+
+### Internal
+
+* ✅ Run performance tests only on Pydantic v2. PR [#14608](https://github.com/fastapi/fastapi/pull/14608) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.127.1
+
+### Refactors
+
+* 🔊 Add a custom `FastAPIDeprecationWarning`. PR [#14605](https://github.com/fastapi/fastapi/pull/14605) by [@tiangolo](https://github.com/tiangolo).
+
### Docs
* 📝 Add documentary to website. PR [#14600](https://github.com/fastapi/fastapi/pull/14600) by [@tiangolo](https://github.com/tiangolo).
@@ -18,6 +51,8 @@ hide:
### Internal
+* 🔧 Update pre-commit to use local Ruff instead of hook. PR [#14604](https://github.com/fastapi/fastapi/pull/14604) by [@tiangolo](https://github.com/tiangolo).
+* ✅ Add missing tests for code examples. PR [#14569](https://github.com/fastapi/fastapi/pull/14569) by [@YuriiMotov](https://github.com/YuriiMotov).
* 👷 Remove `lint` job from `test` CI workflow. PR [#14593](https://github.com/fastapi/fastapi/pull/14593) by [@YuriiMotov](https://github.com/YuriiMotov).
* 👷 Update secrets check. PR [#14592](https://github.com/fastapi/fastapi/pull/14592) by [@tiangolo](https://github.com/tiangolo).
* 👷 Run CodSpeed tests in parallel to other tests to speed up CI. PR [#14586](https://github.com/fastapi/fastapi/pull/14586) by [@tiangolo](https://github.com/tiangolo).
diff --git a/docs/es/docs/advanced/dataclasses.md b/docs/es/docs/advanced/dataclasses.md
index 8d96171c7..3a07482ad 100644
--- a/docs/es/docs/advanced/dataclasses.md
+++ b/docs/es/docs/advanced/dataclasses.md
@@ -4,7 +4,7 @@ FastAPI está construido sobre **Pydantic**, y te he estado mostrando cómo usar
Pero FastAPI también soporta el uso de `dataclasses` de la misma manera:
-{* ../../docs_src/dataclasses/tutorial001_py310.py hl[1,6:11,18:19] *}
+{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *}
Esto sigue siendo soportado gracias a **Pydantic**, ya que tiene soporte interno para `dataclasses`.
@@ -32,7 +32,7 @@ Pero si tienes un montón de dataclasses por ahí, este es un buen truco para us
También puedes usar `dataclasses` en el parámetro `response_model`:
-{* ../../docs_src/dataclasses/tutorial002_py310.py hl[1,6:12,18] *}
+{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *}
El dataclass será automáticamente convertido a un dataclass de Pydantic.
@@ -48,7 +48,7 @@ En algunos casos, todavía podrías tener que usar la versión de `dataclasses`
En ese caso, simplemente puedes intercambiar los `dataclasses` estándar con `pydantic.dataclasses`, que es un reemplazo directo:
-{* ../../docs_src/dataclasses/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
+{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
1. Todavía importamos `field` de los `dataclasses` estándar.
diff --git a/docs/es/docs/how-to/graphql.md b/docs/es/docs/how-to/graphql.md
index 2ebfb3dd0..e50c1ae0a 100644
--- a/docs/es/docs/how-to/graphql.md
+++ b/docs/es/docs/how-to/graphql.md
@@ -35,7 +35,7 @@ Dependiendo de tu caso de uso, podrías preferir usar un paquete diferente, pero
Aquí tienes una pequeña vista previa de cómo podrías integrar Strawberry con FastAPI:
-{* ../../docs_src/graphql/tutorial001_py39.py hl[3,22,25] *}
+{* ../../docs_src/graphql_/tutorial001_py39.py hl[3,22,25] *}
Puedes aprender más sobre Strawberry en la documentación de Strawberry.
diff --git a/docs/ja/llm-prompt.md b/docs/ja/llm-prompt.md
index c47cc36df..18909cd59 100644
--- a/docs/ja/llm-prompt.md
+++ b/docs/ja/llm-prompt.md
@@ -6,23 +6,23 @@ Language code: ja.
### Grammar and tone
-1) Use polite, instructional Japanese (です/ます調).
-2) Keep the tone concise and technical (match existing Japanese FastAPI docs).
+- Use polite, instructional Japanese (です/ます調).
+- Keep the tone concise and technical (match existing Japanese FastAPI docs).
### Headings
-1) Follow the existing Japanese style: short, descriptive headings (often noun phrases), e.g. 「チェック」.
-2) Do not add a trailing period at the end of headings.
+- Follow the existing Japanese style: short, descriptive headings (often noun phrases), e.g. 「チェック」.
+- Do not add a trailing period at the end of headings.
### Quotes
-1) Prefer Japanese corner brackets 「」 in normal prose when quoting a term.
-2) Do not change quotes inside inline code, code blocks, URLs, or file paths.
+- Prefer Japanese corner brackets 「」 in normal prose when quoting a term.
+- Do not change quotes inside inline code, code blocks, URLs, or file paths.
### Ellipsis
-1) Keep ellipsis style consistent with existing Japanese docs (commonly `...`).
-2) Never change `...` in code, URLs, or CLI examples.
+- Keep ellipsis style consistent with existing Japanese docs (commonly `...`).
+- Never change `...` in code, URLs, or CLI examples.
### Preferred translations / glossary
diff --git a/docs/ko/llm-prompt.md b/docs/ko/llm-prompt.md
index 008511a5b..df807c949 100644
--- a/docs/ko/llm-prompt.md
+++ b/docs/ko/llm-prompt.md
@@ -6,23 +6,23 @@ Language code: ko.
### Grammar and tone
-1) Use polite, instructional Korean (e.g. 합니다/하세요 style).
-2) Keep the tone consistent with the existing Korean FastAPI docs.
+- Use polite, instructional Korean (e.g. 합니다/하세요 style).
+- Keep the tone consistent with the existing Korean FastAPI docs.
### Headings
-1) Follow existing Korean heading style (short, action-oriented headings like “확인하기”).
-2) Do not add trailing punctuation to headings.
+- Follow existing Korean heading style (short, action-oriented headings like “확인하기”).
+- Do not add trailing punctuation to headings.
### Quotes
-1) Keep quote style consistent with the existing Korean docs.
-2) Never change quotes inside inline code, code blocks, URLs, or file paths.
+- Keep quote style consistent with the existing Korean docs.
+- Never change quotes inside inline code, code blocks, URLs, or file paths.
### Ellipsis
-1) Keep ellipsis style consistent with existing Korean docs (often `...`).
-2) Never change `...` in code, URLs, or CLI examples.
+- Keep ellipsis style consistent with existing Korean docs (often `...`).
+- Never change `...` in code, URLs, or CLI examples.
### Preferred translations / glossary
diff --git a/docs/missing-translation.md b/docs/missing-translation.md
index bfff84766..71c0925c5 100644
--- a/docs/missing-translation.md
+++ b/docs/missing-translation.md
@@ -4,6 +4,6 @@ This page hasn’t been translated into your language yet. 🌍
We’re currently switching to an automated translation system 🤖, which will help keep all translations complete and up to date.
-Learn more: [Contributing – Translations](https://fastapi.tiangolo.com/contributing/#translations){.internal-link target=_blank}
+Learn more: [Contributing - Translations](https://fastapi.tiangolo.com/contributing/#translations){.internal-link target=_blank}
///
diff --git a/docs/pt/docs/advanced/dataclasses.md b/docs/pt/docs/advanced/dataclasses.md
index 646737696..6dc9feb29 100644
--- a/docs/pt/docs/advanced/dataclasses.md
+++ b/docs/pt/docs/advanced/dataclasses.md
@@ -4,7 +4,7 @@ FastAPI é construído em cima do **Pydantic**, e eu tenho mostrado como usar mo
Mas o FastAPI também suporta o uso de `dataclasses` da mesma forma:
-{* ../../docs_src/dataclasses/tutorial001_py310.py hl[1,6:11,18:19] *}
+{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *}
Isso ainda é suportado graças ao **Pydantic**, pois ele tem suporte interno para `dataclasses`.
@@ -32,7 +32,7 @@ Mas se você tem um monte de dataclasses por aí, este é um truque legal para u
Você também pode usar `dataclasses` no parâmetro `response_model`:
-{* ../../docs_src/dataclasses/tutorial002_py310.py hl[1,6:12,18] *}
+{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *}
A dataclass será automaticamente convertida para uma dataclass Pydantic.
@@ -48,7 +48,7 @@ Em alguns casos, você ainda pode ter que usar a versão do Pydantic das `datacl
Nesse caso, você pode simplesmente trocar as `dataclasses` padrão por `pydantic.dataclasses`, que é um substituto direto:
-{* ../../docs_src/dataclasses/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
+{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
1. Ainda importamos `field` das `dataclasses` padrão.
diff --git a/docs/pt/docs/how-to/graphql.md b/docs/pt/docs/how-to/graphql.md
index 7af4c6b75..98266cc28 100644
--- a/docs/pt/docs/how-to/graphql.md
+++ b/docs/pt/docs/how-to/graphql.md
@@ -35,7 +35,7 @@ Dependendo do seu caso de uso, você pode preferir usar uma biblioteca diferente
Aqui está uma pequena prévia de como você poderia integrar Strawberry com FastAPI:
-{* ../../docs_src/graphql/tutorial001_py39.py hl[3,22,25] *}
+{* ../../docs_src/graphql_/tutorial001_py39.py hl[3,22,25] *}
Você pode aprender mais sobre Strawberry na documentação do Strawberry.
diff --git a/docs/pt/llm-prompt.md b/docs/pt/llm-prompt.md
index 2374070ce..3f5208e91 100644
--- a/docs/pt/llm-prompt.md
+++ b/docs/pt/llm-prompt.md
@@ -14,15 +14,15 @@ When translating documentation into Portuguese, use neutral and widely understan
For the next terms, use the following translations:
-* «/// check»: «/// check | Verifique»
-* «/// danger»: «/// danger | Cuidado»
-* «/// info»: «/// info | Informação»
-* «/// note | Technical Details»: «/// note | Detalhes Técnicos»
-* «/// info | Very Technical Details»: «/// note | Detalhes Técnicos Avançados»
-* «/// note»: «/// note | Nota»
-* «/// tip»: «/// tip | Dica»
-* «/// warning»: «/// warning | Atenção»
-* «(you should)»: «(você deveria)»
+* /// check: /// check | Verifique
+* /// danger: /// danger | Cuidado
+* /// info: /// info | Informação
+* /// note | Technical Details: /// note | Detalhes Técnicos
+* /// info | Very Technical Details: /// note | Detalhes Técnicos Avançados
+* /// note: /// note | Nota
+* /// tip: /// tip | Dica
+* /// warning: /// warning | Atenção
+* (you should): (você deveria)
* async context manager: gerenciador de contexto assíncrono
* autocomplete: autocompletar
* autocompletion: preenchimento automático
diff --git a/docs/ru/docs/advanced/dataclasses.md b/docs/ru/docs/advanced/dataclasses.md
index c37ce3023..b3ced37c1 100644
--- a/docs/ru/docs/advanced/dataclasses.md
+++ b/docs/ru/docs/advanced/dataclasses.md
@@ -4,7 +4,7 @@ FastAPI построен поверх **Pydantic**, и я показывал в
Но FastAPI также поддерживает использование `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] *}
Это по-прежнему поддерживается благодаря **Pydantic**, так как в нём есть встроенная поддержка `dataclasses`.
@@ -32,7 +32,7 @@ FastAPI построен поверх **Pydantic**, и я показывал в
Вы также можете использовать `dataclasses` в параметре `response_model`:
-{* ../../docs_src/dataclasses/tutorial002_py310.py hl[1,6:12,18] *}
+{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *}
Этот dataclass будет автоматически преобразован в Pydantic dataclass.
@@ -48,7 +48,7 @@ FastAPI построен поверх **Pydantic**, и я показывал в
В таком случае вы можете просто заменить стандартные `dataclasses` на `pydantic.dataclasses`, которая является полностью совместимой заменой (drop-in replacement):
-{* ../../docs_src/dataclasses/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
+{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
1. Мы по-прежнему импортируем `field` из стандартных `dataclasses`.
diff --git a/docs/ru/docs/how-to/graphql.md b/docs/ru/docs/how-to/graphql.md
index 97278069a..50c321e7d 100644
--- a/docs/ru/docs/how-to/graphql.md
+++ b/docs/ru/docs/how-to/graphql.md
@@ -35,7 +35,7 @@
Вот небольшой пример того, как можно интегрировать Strawberry с FastAPI:
-{* ../../docs_src/graphql/tutorial001_py39.py hl[3,22,25] *}
+{* ../../docs_src/graphql_/tutorial001_py39.py hl[3,22,25] *}
Подробнее о Strawberry можно узнать в документации Strawberry.
diff --git a/docs/tr/llm-prompt.md b/docs/tr/llm-prompt.md
new file mode 100644
index 000000000..297b0a0e6
--- /dev/null
+++ b/docs/tr/llm-prompt.md
@@ -0,0 +1,52 @@
+### Target language
+
+Translate to Turkish (Türkçe).
+
+Language code: tr.
+
+### Grammar and tone
+
+- Use instructional Turkish, consistent with existing Turkish docs.
+- Use imperative/guide language when appropriate (e.g. “açalım”, “gidin”, “kopyalayalım”).
+
+### Headings
+
+- Follow existing Turkish heading style (Title Case where used; no trailing period).
+
+### Quotes
+
+- Alıntı stili mevcut Türkçe dokümanlarla tutarlı tutun (genellikle metin içinde ASCII tırnak işaretleri kullanılır).
+- Satır içi kod, kod blokları, URL'ler veya dosya yolları içindeki tırnak işaretlerini asla değiştirmeyin.
+
+### Ellipsis
+
+- Üç nokta (...) stili mevcut Türkçe dokümanlarla tutarlı tutun.
+- Kod, URL veya CLI örneklerindeki `...` ifadesini asla değiştirmeyin.
+
+### Preferred translations / glossary
+
+Do not translate technical terms like path, route, request, response, query, body, cookie, and header, keep them as is.
+
+- Suffixing is very important, when adding Turkish suffixes to the English words, do that based on the pronunciation of the word and with an apostrophe.
+
+- Suffixes also changes based on what word comes next in Turkish too, here is an example:
+
+"Server'a gelen request'leri intercept... " or this could have been "request'e", "request'i" etc.
+
+- Some words are tricky like "path'e" can't be used like "path'a" but it could have been "path'i" "path'leri" etc.
+
+- You can use a more instructional style, that is consistent with the document, you can add the Turkish version of the term in parenthesis if it is not something very obvious, or an advanced concept, but do not over do it, do it only the first time it is mentioned, but keep the English term as the primary word.
+
+### `///` admonitions
+
+- Keep the admonition keyword in English (do not translate `note`, `tip`, etc.).
+- If a title is present, prefer these canonical titles:
+
+- `/// note | Not`
+- `/// note | Teknik Detaylar`
+- `/// tip | İpucu`
+- `/// warning | Uyarı`
+- `/// info | Bilgi`
+- `/// check | Ek bilgi`
+
+Prefer `İpucu` over `Ipucu`.
diff --git a/docs/uk/llm-prompt.md b/docs/uk/llm-prompt.md
index d55d36ab5..f1c5377a4 100644
--- a/docs/uk/llm-prompt.md
+++ b/docs/uk/llm-prompt.md
@@ -6,23 +6,23 @@ Language code: uk.
### Grammar and tone
-1) Use polite/formal address consistent with existing Ukrainian docs (use “ви/ваш”).
-2) Keep the tone concise and technical.
+- Use polite/formal address consistent with existing Ukrainian docs (use “ви/ваш”).
+- Keep the tone concise and technical.
### Headings
-1) Follow existing Ukrainian heading style; keep headings short and instructional.
-2) Do not add trailing punctuation to headings.
+- Follow existing Ukrainian heading style; keep headings short and instructional.
+- Do not add trailing punctuation to headings.
### Quotes
-1) Prefer Ukrainian guillemets «…» for quoted terms in prose, matching existing Ukrainian docs.
-2) Never change quotes inside inline code, code blocks, URLs, or file paths.
+- Prefer Ukrainian guillemets «…» for quoted terms in prose, matching existing Ukrainian docs.
+- Never change quotes inside inline code, code blocks, URLs, or file paths.
### Ellipsis
-1) Keep ellipsis style consistent with existing Ukrainian docs.
-2) Never change `...` in code, URLs, or CLI examples.
+- Keep ellipsis style consistent with existing Ukrainian docs.
+- Never change `...` in code, URLs, or CLI examples.
### Preferred translations / glossary
@@ -35,8 +35,8 @@ Use the following preferred translations when they apply in documentation prose:
### `///` admonitions
-1) Keep the admonition keyword in English (do not translate `note`, `tip`, etc.).
-2) If a title is present, prefer these canonical titles (choose one canonical form where variants exist):
+- Keep the admonition keyword in English (do not translate `note`, `tip`, etc.).
+- If a title is present, prefer these canonical titles (choose one canonical form where variants exist):
- `/// note | Примітка`
- `/// note | Технічні деталі`
diff --git a/docs/zh-hant/llm-prompt.md b/docs/zh-hant/llm-prompt.md
new file mode 100644
index 000000000..d44709015
--- /dev/null
+++ b/docs/zh-hant/llm-prompt.md
@@ -0,0 +1,60 @@
+### Target language
+
+Translate to Traditional Chinese (繁體中文).
+
+Language code: zh-hant.
+
+### Grammar and tone
+
+- Use clear, concise technical Traditional Chinese consistent with existing docs.
+- Address the reader naturally (commonly using “你/你的”).
+
+### Headings
+
+- Follow existing Traditional Chinese heading style (short and descriptive).
+- Do not add trailing punctuation to headings.
+
+### Quotes and punctuation
+
+- Keep punctuation style consistent with existing Traditional Chinese docs (they often mix English terms like “FastAPI” with Chinese text).
+- Never change punctuation inside inline code, code blocks, URLs, or file paths.
+- For more details, please follow the [Chinese Copywriting Guidelines](https://github.com/sparanoid/chinese-copywriting-guidelines).
+
+### Ellipsis
+
+- Keep ellipsis style consistent within each document, prefer `...` over `……`.
+- Never change ellipsis in code, URLs, or CLI examples.
+
+### Preferred translations / glossary
+
+- Should avoid using simplified Chinese characters and terms. Always examine if the translation can be easily comprehended by the Traditional Chinese readers.
+- For some Python-specific terms like "pickle", "list", "dict" etc, we don't have to translate them.
+- Use the following preferred translations when they apply in documentation prose:
+
+- request (HTTP): 請求
+- response (HTTP): 回應
+- path operation: 路徑操作
+- path operation function: 路徑操作函式
+
+The translation can optionally include the original English text only in the first occurrence of each page (e.g. "路徑操作 (path operation)") if the translation is hard to be comprehended by most of the Chinese readers.
+
+### `///` admonitions
+
+1) Keep the admonition keyword in English (do not translate `note`, `tip`, etc.).
+2) Many Traditional Chinese docs currently omit titles in `///` blocks; that is OK.
+3) If a generic title is present, prefer these canonical titles:
+
+- `/// note | 注意`
+
+Notes:
+
+- `details` blocks exist; keep `/// details` as-is and translate only the title after `|`.
+- Example canonical titles used in existing docs:
+
+```
+/// details | 上述指令的含義
+```
+
+```
+/// details | 關於 `requirements.txt`
+```
diff --git a/docs/zh/docs/advanced/dataclasses.md b/docs/zh/docs/advanced/dataclasses.md
index c74ce65c3..4e8e77d2a 100644
--- a/docs/zh/docs/advanced/dataclasses.md
+++ b/docs/zh/docs/advanced/dataclasses.md
@@ -4,7 +4,7 @@ FastAPI 基于 **Pydantic** 构建,前文已经介绍过如何使用 Pydantic
但 FastAPI 还可以使用数据类(`dataclasses`):
-{* ../../docs_src/dataclasses/tutorial001.py hl[1,7:12,19:20] *}
+{* ../../docs_src/dataclasses_/tutorial001.py hl[1,7:12,19:20] *}
这还是借助于 **Pydantic** 及其内置的 `dataclasses`。
@@ -32,7 +32,7 @@ FastAPI 基于 **Pydantic** 构建,前文已经介绍过如何使用 Pydantic
在 `response_model` 参数中使用 `dataclasses`:
-{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *}
+{* ../../docs_src/dataclasses_/tutorial002.py hl[1,7:13,19] *}
本例把数据类自动转换为 Pydantic 数据类。
@@ -49,7 +49,7 @@ API 文档中也会显示相关概图:
本例把标准的 `dataclasses` 直接替换为 `pydantic.dataclasses`:
```{ .python .annotate hl_lines="1 5 8-11 14-17 23-25 28" }
-{!../../docs_src/dataclasses/tutorial003.py!}
+{!../../docs_src/dataclasses_/tutorial003.py!}
```
1. 本例依然要从标准的 `dataclasses` 中导入 `field`;
diff --git a/docs/zh/llm-prompt.md b/docs/zh/llm-prompt.md
new file mode 100644
index 000000000..7ce6f96a4
--- /dev/null
+++ b/docs/zh/llm-prompt.md
@@ -0,0 +1,46 @@
+### Target language
+
+Translate to Simplified Chinese (简体中文).
+
+Language code: zh.
+
+### Grammar and tone
+
+- Use clear, concise technical Chinese consistent with existing docs.
+- Address the reader naturally (commonly using “你/你的”).
+
+### Headings
+
+- Follow existing Simplified Chinese heading style (short and descriptive).
+- Do not add trailing punctuation to headings.
+- If a heading contains only the name of a FastAPI feature, do not translate it.
+
+### Quotes and punctuation
+
+- Keep punctuation style consistent with existing Simplified Chinese docs (they often mix English terms like “FastAPI” with Chinese text).
+- Never change punctuation inside inline code, code blocks, URLs, or file paths.
+
+### Ellipsis
+
+- Keep ellipsis style consistent within each document, prefer `...` over `……`.
+- Never change ellipsis in code, URLs, or CLI examples.
+
+### Preferred translations / glossary
+
+Use the following preferred translations when they apply in documentation prose:
+
+- request (HTTP): 请求
+- response (HTTP): 响应
+- path operation: 路径操作
+- path operation function: 路径操作函数
+
+### `///` admonitions
+
+- Keep the admonition keyword in English (do not translate `note`, `tip`, etc.).
+- If a title is present, prefer these canonical titles:
+
+- `/// tip | 提示`
+- `/// note | 注意`
+- `/// warning | 警告`
+- `/// info | 信息`
+- `/// danger | 危险`
diff --git a/tests/test_filter_pydantic_sub_model/__init__.py b/docs_src/additional_responses/__init__.py
similarity index 100%
rename from tests/test_filter_pydantic_sub_model/__init__.py
rename to docs_src/additional_responses/__init__.py
diff --git a/tests/test_pydantic_v1_v2_multifile/__init__.py b/docs_src/additional_status_codes/__init__.py
similarity index 100%
rename from tests/test_pydantic_v1_v2_multifile/__init__.py
rename to docs_src/additional_status_codes/__init__.py
diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/__init__.py b/docs_src/advanced_middleware/__init__.py
similarity index 100%
rename from tests/test_tutorial/test_pydantic_v1_in_v2/__init__.py
rename to docs_src/advanced_middleware/__init__.py
diff --git a/docs_src/authentication_error_status_code/__init__.py b/docs_src/authentication_error_status_code/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/background_tasks/__init__.py b/docs_src/background_tasks/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/behind_a_proxy/__init__.py b/docs_src/behind_a_proxy/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/body/__init__.py b/docs_src/body/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/body_fields/__init__.py b/docs_src/body_fields/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/body_multiple_params/__init__.py b/docs_src/body_multiple_params/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/body_nested_models/__init__.py b/docs_src/body_nested_models/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/body_updates/__init__.py b/docs_src/body_updates/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/conditional_openapi/__init__.py b/docs_src/conditional_openapi/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/configure_swagger_ui/__init__.py b/docs_src/configure_swagger_ui/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/cookie_param_models/__init__.py b/docs_src/cookie_param_models/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/cookie_params/__init__.py b/docs_src/cookie_params/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/cors/__init__.py b/docs_src/cors/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/custom_docs_ui/__init__.py b/docs_src/custom_docs_ui/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/custom_request_and_route/__init__.py b/docs_src/custom_request_and_route/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/custom_response/__init__.py b/docs_src/custom_response/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/dataclasses_/__init__.py b/docs_src/dataclasses_/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/dataclasses/tutorial001_py310.py b/docs_src/dataclasses_/tutorial001_py310.py
similarity index 100%
rename from docs_src/dataclasses/tutorial001_py310.py
rename to docs_src/dataclasses_/tutorial001_py310.py
diff --git a/docs_src/dataclasses/tutorial001_py39.py b/docs_src/dataclasses_/tutorial001_py39.py
similarity index 100%
rename from docs_src/dataclasses/tutorial001_py39.py
rename to docs_src/dataclasses_/tutorial001_py39.py
diff --git a/docs_src/dataclasses/tutorial002_py310.py b/docs_src/dataclasses_/tutorial002_py310.py
similarity index 100%
rename from docs_src/dataclasses/tutorial002_py310.py
rename to docs_src/dataclasses_/tutorial002_py310.py
diff --git a/docs_src/dataclasses/tutorial002_py39.py b/docs_src/dataclasses_/tutorial002_py39.py
similarity index 100%
rename from docs_src/dataclasses/tutorial002_py39.py
rename to docs_src/dataclasses_/tutorial002_py39.py
diff --git a/docs_src/dataclasses/tutorial003_py310.py b/docs_src/dataclasses_/tutorial003_py310.py
similarity index 100%
rename from docs_src/dataclasses/tutorial003_py310.py
rename to docs_src/dataclasses_/tutorial003_py310.py
diff --git a/docs_src/dataclasses/tutorial003_py39.py b/docs_src/dataclasses_/tutorial003_py39.py
similarity index 100%
rename from docs_src/dataclasses/tutorial003_py39.py
rename to docs_src/dataclasses_/tutorial003_py39.py
diff --git a/docs_src/debugging/__init__.py b/docs_src/debugging/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/dependencies/__init__.py b/docs_src/dependencies/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/dependency_testing/__init__.py b/docs_src/dependency_testing/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/encoder/__init__.py b/docs_src/encoder/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/events/__init__.py b/docs_src/events/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/extending_openapi/__init__.py b/docs_src/extending_openapi/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/extra_data_types/__init__.py b/docs_src/extra_data_types/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/extra_models/__init__.py b/docs_src/extra_models/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/first_steps/__init__.py b/docs_src/first_steps/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/generate_clients/__init__.py b/docs_src/generate_clients/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/graphql_/__init__.py b/docs_src/graphql_/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/graphql/tutorial001_py39.py b/docs_src/graphql_/tutorial001_py39.py
similarity index 100%
rename from docs_src/graphql/tutorial001_py39.py
rename to docs_src/graphql_/tutorial001_py39.py
diff --git a/docs_src/handling_errors/__init__.py b/docs_src/handling_errors/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/header_param_models/__init__.py b/docs_src/header_param_models/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/header_params/__init__.py b/docs_src/header_params/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/metadata/__init__.py b/docs_src/metadata/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/middleware/__init__.py b/docs_src/middleware/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/openapi_callbacks/__init__.py b/docs_src/openapi_callbacks/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/openapi_webhooks/__init__.py b/docs_src/openapi_webhooks/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/path_operation_advanced_configuration/__init__.py b/docs_src/path_operation_advanced_configuration/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/path_operation_configuration/__init__.py b/docs_src/path_operation_configuration/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/path_params/__init__.py b/docs_src/path_params/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/path_params_numeric_validations/__init__.py b/docs_src/path_params_numeric_validations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/pydantic_v1_in_v2/__init__.py b/docs_src/pydantic_v1_in_v2/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/python_types/__init__.py b/docs_src/python_types/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/query_param_models/__init__.py b/docs_src/query_param_models/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/query_params/__init__.py b/docs_src/query_params/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/query_params_str_validations/__init__.py b/docs_src/query_params_str_validations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/request_files/__init__.py b/docs_src/request_files/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/request_form_models/__init__.py b/docs_src/request_form_models/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/request_form_models/tutorial002_pv1_an_py39.py b/docs_src/request_form_models/tutorial002_pv1_an_py39.py
deleted file mode 100644
index 392e6873c..000000000
--- a/docs_src/request_form_models/tutorial002_pv1_an_py39.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from typing import Annotated
-
-from fastapi import FastAPI
-from fastapi.temp_pydantic_v1_params import Form
-from pydantic.v1 import BaseModel
-
-app = FastAPI()
-
-
-class FormData(BaseModel):
- username: str
- password: str
-
- class Config:
- extra = "forbid"
-
-
-@app.post("/login/")
-async def login(data: Annotated[FormData, Form()]):
- return data
diff --git a/docs_src/request_form_models/tutorial002_pv1_py39.py b/docs_src/request_form_models/tutorial002_pv1_py39.py
deleted file mode 100644
index da160b3a5..000000000
--- a/docs_src/request_form_models/tutorial002_pv1_py39.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from fastapi import FastAPI
-from fastapi.temp_pydantic_v1_params import Form
-from pydantic.v1 import BaseModel
-
-app = FastAPI()
-
-
-class FormData(BaseModel):
- username: str
- password: str
-
- class Config:
- extra = "forbid"
-
-
-@app.post("/login/")
-async def login(data: FormData = Form()):
- return data
diff --git a/docs_src/request_forms/__init__.py b/docs_src/request_forms/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/request_forms_and_files/__init__.py b/docs_src/request_forms_and_files/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/response_change_status_code/__init__.py b/docs_src/response_change_status_code/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/response_cookies/__init__.py b/docs_src/response_cookies/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/response_directly/__init__.py b/docs_src/response_directly/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/response_headers/__init__.py b/docs_src/response_headers/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/response_model/__init__.py b/docs_src/response_model/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/response_status_code/__init__.py b/docs_src/response_status_code/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/schema_extra_example/__init__.py b/docs_src/schema_extra_example/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/security/__init__.py b/docs_src/security/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/separate_openapi_schemas/__init__.py b/docs_src/separate_openapi_schemas/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/settings/__init__.py b/docs_src/settings/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/static_files/__init__.py b/docs_src/static_files/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/sub_applications/__init__.py b/docs_src/sub_applications/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/templates/__init__.py b/docs_src/templates/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/templates/static/__init__.py b/docs_src/templates/static/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/templates/templates/__init__.py b/docs_src/templates/templates/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/using_request_directly/__init__.py b/docs_src/using_request_directly/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/wsgi/__init__.py b/docs_src/wsgi/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/fastapi/__init__.py b/fastapi/__init__.py
index 73df6dc6c..6133787b0 100644
--- a/fastapi/__init__.py
+++ b/fastapi/__init__.py
@@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
-__version__ = "0.127.0"
+__version__ = "0.128.0"
from starlette import status as status
diff --git a/fastapi/_compat/__init__.py b/fastapi/_compat/__init__.py
index fd1df8c6a..3dfaf9b71 100644
--- a/fastapi/_compat/__init__.py
+++ b/fastapi/_compat/__init__.py
@@ -1,43 +1,9 @@
-from .main import BaseConfig as BaseConfig
-from .main import PydanticSchemaGenerationError as PydanticSchemaGenerationError
-from .main import RequiredParam as RequiredParam
-from .main import Undefined as Undefined
-from .main import UndefinedType as UndefinedType
-from .main import Url as Url
-from .main import Validator as Validator
-from .main import _get_model_config as _get_model_config
-from .main import _is_error_wrapper as _is_error_wrapper
-from .main import _is_model_class as _is_model_class
-from .main import _is_model_field as _is_model_field
-from .main import _is_undefined as _is_undefined
-from .main import _model_dump as _model_dump
-from .main import copy_field_info as copy_field_info
-from .main import create_body_model as create_body_model
-from .main import evaluate_forwardref as evaluate_forwardref
-from .main import get_annotation_from_field_info as get_annotation_from_field_info
-from .main import get_cached_model_fields as get_cached_model_fields
-from .main import get_compat_model_name_map as get_compat_model_name_map
-from .main import get_definitions as get_definitions
-from .main import get_missing_field_error as get_missing_field_error
-from .main import get_schema_from_model_field as get_schema_from_model_field
-from .main import is_bytes_field as is_bytes_field
-from .main import is_bytes_sequence_field as is_bytes_sequence_field
-from .main import is_scalar_field as is_scalar_field
-from .main import is_scalar_sequence_field as is_scalar_sequence_field
-from .main import is_sequence_field as is_sequence_field
-from .main import serialize_sequence_value as serialize_sequence_value
-from .main import (
- with_info_plain_validator_function as with_info_plain_validator_function,
-)
-from .may_v1 import CoreSchema as CoreSchema
-from .may_v1 import GetJsonSchemaHandler as GetJsonSchemaHandler
-from .may_v1 import JsonSchemaValue as JsonSchemaValue
-from .may_v1 import _normalize_errors as _normalize_errors
-from .model_field import ModelField as ModelField
from .shared import PYDANTIC_V2 as PYDANTIC_V2
from .shared import PYDANTIC_VERSION_MINOR_TUPLE as PYDANTIC_VERSION_MINOR_TUPLE
from .shared import annotation_is_pydantic_v1 as annotation_is_pydantic_v1
from .shared import field_annotation_is_scalar as field_annotation_is_scalar
+from .shared import is_pydantic_v1_model_class as is_pydantic_v1_model_class
+from .shared import is_pydantic_v1_model_instance as is_pydantic_v1_model_instance
from .shared import (
is_uploadfile_or_nonable_uploadfile_annotation as is_uploadfile_or_nonable_uploadfile_annotation,
)
@@ -47,3 +13,29 @@ from .shared import (
from .shared import lenient_issubclass as lenient_issubclass
from .shared import sequence_types as sequence_types
from .shared import value_is_sequence as value_is_sequence
+from .v2 import BaseConfig as BaseConfig
+from .v2 import ModelField as ModelField
+from .v2 import PydanticSchemaGenerationError as PydanticSchemaGenerationError
+from .v2 import RequiredParam as RequiredParam
+from .v2 import Undefined as Undefined
+from .v2 import UndefinedType as UndefinedType
+from .v2 import Url as Url
+from .v2 import Validator as Validator
+from .v2 import _regenerate_error_with_loc as _regenerate_error_with_loc
+from .v2 import copy_field_info as copy_field_info
+from .v2 import create_body_model as create_body_model
+from .v2 import evaluate_forwardref as evaluate_forwardref
+from .v2 import get_cached_model_fields as get_cached_model_fields
+from .v2 import get_compat_model_name_map as get_compat_model_name_map
+from .v2 import get_definitions as get_definitions
+from .v2 import get_missing_field_error as get_missing_field_error
+from .v2 import get_schema_from_model_field as get_schema_from_model_field
+from .v2 import is_bytes_field as is_bytes_field
+from .v2 import is_bytes_sequence_field as is_bytes_sequence_field
+from .v2 import is_scalar_field as is_scalar_field
+from .v2 import is_scalar_sequence_field as is_scalar_sequence_field
+from .v2 import is_sequence_field as is_sequence_field
+from .v2 import serialize_sequence_value as serialize_sequence_value
+from .v2 import (
+ with_info_plain_validator_function as with_info_plain_validator_function,
+)
diff --git a/fastapi/_compat/main.py b/fastapi/_compat/main.py
deleted file mode 100644
index 95053a237..000000000
--- a/fastapi/_compat/main.py
+++ /dev/null
@@ -1,264 +0,0 @@
-import sys
-from collections.abc import Sequence
-from functools import lru_cache
-from typing import (
- Any,
-)
-
-from fastapi._compat import may_v1
-from fastapi._compat.shared import lenient_issubclass
-from fastapi.types import ModelNameMap
-from pydantic import BaseModel
-from typing_extensions import Literal
-
-from . import v2
-from .model_field import ModelField
-from .v2 import BaseConfig as BaseConfig
-from .v2 import FieldInfo as FieldInfo
-from .v2 import PydanticSchemaGenerationError as PydanticSchemaGenerationError
-from .v2 import RequiredParam as RequiredParam
-from .v2 import Undefined as Undefined
-from .v2 import UndefinedType as UndefinedType
-from .v2 import Url as Url
-from .v2 import Validator as Validator
-from .v2 import evaluate_forwardref as evaluate_forwardref
-from .v2 import get_missing_field_error as get_missing_field_error
-from .v2 import (
- with_info_plain_validator_function as with_info_plain_validator_function,
-)
-
-
-@lru_cache
-def get_cached_model_fields(model: type[BaseModel]) -> list[ModelField]:
- if lenient_issubclass(model, may_v1.BaseModel):
- from fastapi._compat import v1
-
- return v1.get_model_fields(model) # type: ignore[arg-type,return-value]
- else:
- from . import v2
-
- return v2.get_model_fields(model) # type: ignore[return-value]
-
-
-def _is_undefined(value: object) -> bool:
- if isinstance(value, may_v1.UndefinedType):
- return True
-
- return isinstance(value, v2.UndefinedType)
-
-
-def _get_model_config(model: BaseModel) -> Any:
- if isinstance(model, may_v1.BaseModel):
- from fastapi._compat import v1
-
- return v1._get_model_config(model)
-
- return v2._get_model_config(model)
-
-
-def _model_dump(
- model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any
-) -> Any:
- if isinstance(model, may_v1.BaseModel):
- from fastapi._compat import v1
-
- return v1._model_dump(model, mode=mode, **kwargs)
-
- return v2._model_dump(model, mode=mode, **kwargs)
-
-
-def _is_error_wrapper(exc: Exception) -> bool:
- if isinstance(exc, may_v1.ErrorWrapper):
- return True
-
- return isinstance(exc, v2.ErrorWrapper)
-
-
-def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo:
- if isinstance(field_info, may_v1.FieldInfo):
- from fastapi._compat import v1
-
- return v1.copy_field_info(field_info=field_info, annotation=annotation)
-
- return v2.copy_field_info(field_info=field_info, annotation=annotation)
-
-
-def create_body_model(
- *, fields: Sequence[ModelField], model_name: str
-) -> type[BaseModel]:
- if fields and isinstance(fields[0], may_v1.ModelField):
- from fastapi._compat import v1
-
- return v1.create_body_model(fields=fields, model_name=model_name)
-
- return v2.create_body_model(fields=fields, model_name=model_name) # type: ignore[arg-type]
-
-
-def get_annotation_from_field_info(
- annotation: Any, field_info: FieldInfo, field_name: str
-) -> Any:
- if isinstance(field_info, may_v1.FieldInfo):
- from fastapi._compat import v1
-
- return v1.get_annotation_from_field_info(
- annotation=annotation, field_info=field_info, field_name=field_name
- )
-
- return v2.get_annotation_from_field_info(
- annotation=annotation, field_info=field_info, field_name=field_name
- )
-
-
-def is_bytes_field(field: ModelField) -> bool:
- if isinstance(field, may_v1.ModelField):
- from fastapi._compat import v1
-
- return v1.is_bytes_field(field)
-
- return v2.is_bytes_field(field) # type: ignore[arg-type]
-
-
-def is_bytes_sequence_field(field: ModelField) -> bool:
- if isinstance(field, may_v1.ModelField):
- from fastapi._compat import v1
-
- return v1.is_bytes_sequence_field(field)
-
- return v2.is_bytes_sequence_field(field) # type: ignore[arg-type]
-
-
-def is_scalar_field(field: ModelField) -> bool:
- if isinstance(field, may_v1.ModelField):
- from fastapi._compat import v1
-
- return v1.is_scalar_field(field)
-
- return v2.is_scalar_field(field) # type: ignore[arg-type]
-
-
-def is_scalar_sequence_field(field: ModelField) -> bool:
- return v2.is_scalar_sequence_field(field) # type: ignore[arg-type]
-
-
-def is_sequence_field(field: ModelField) -> bool:
- if isinstance(field, may_v1.ModelField):
- from fastapi._compat import v1
-
- return v1.is_sequence_field(field)
-
- return v2.is_sequence_field(field) # type: ignore[arg-type]
-
-
-def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]:
- if isinstance(field, may_v1.ModelField):
- from fastapi._compat import v1
-
- return v1.serialize_sequence_value(field=field, value=value)
-
- return v2.serialize_sequence_value(field=field, value=value) # type: ignore[arg-type]
-
-
-def get_compat_model_name_map(fields: list[ModelField]) -> ModelNameMap:
- v1_model_fields = [
- field for field in fields if isinstance(field, may_v1.ModelField)
- ]
- if v1_model_fields:
- from fastapi._compat import v1
-
- v1_flat_models = v1.get_flat_models_from_fields(
- v1_model_fields, # type: ignore[arg-type]
- known_models=set(),
- )
- all_flat_models = v1_flat_models
- else:
- all_flat_models = set()
-
- v2_model_fields = [field for field in fields if isinstance(field, v2.ModelField)]
- v2_flat_models = v2.get_flat_models_from_fields(v2_model_fields, known_models=set())
- all_flat_models = all_flat_models.union(v2_flat_models) # type: ignore[arg-type]
-
- model_name_map = v2.get_model_name_map(all_flat_models) # type: ignore[arg-type]
- return model_name_map
-
-
-def get_definitions(
- *,
- fields: list[ModelField],
- model_name_map: ModelNameMap,
- separate_input_output_schemas: bool = True,
-) -> tuple[
- dict[
- tuple[ModelField, Literal["validation", "serialization"]],
- may_v1.JsonSchemaValue,
- ],
- dict[str, dict[str, Any]],
-]:
- if sys.version_info < (3, 14):
- v1_fields = [field for field in fields if isinstance(field, may_v1.ModelField)]
- v1_field_maps, v1_definitions = may_v1.get_definitions(
- fields=v1_fields, # type: ignore[arg-type]
- model_name_map=model_name_map,
- separate_input_output_schemas=separate_input_output_schemas,
- )
-
- v2_fields = [field for field in fields if isinstance(field, v2.ModelField)]
- v2_field_maps, v2_definitions = v2.get_definitions(
- fields=v2_fields,
- model_name_map=model_name_map,
- separate_input_output_schemas=separate_input_output_schemas,
- )
- all_definitions = {**v1_definitions, **v2_definitions}
- all_field_maps = {**v1_field_maps, **v2_field_maps} # type: ignore[misc]
- return all_field_maps, all_definitions
-
- # Pydantic v1 is not supported since Python 3.14
- else:
- v2_fields = [field for field in fields if isinstance(field, v2.ModelField)]
- v2_field_maps, v2_definitions = v2.get_definitions(
- fields=v2_fields,
- model_name_map=model_name_map,
- separate_input_output_schemas=separate_input_output_schemas,
- )
- return v2_field_maps, v2_definitions
-
-
-def get_schema_from_model_field(
- *,
- field: ModelField,
- model_name_map: ModelNameMap,
- field_mapping: dict[
- tuple[ModelField, Literal["validation", "serialization"]],
- may_v1.JsonSchemaValue,
- ],
- separate_input_output_schemas: bool = True,
-) -> dict[str, Any]:
- if isinstance(field, may_v1.ModelField):
- from fastapi._compat import v1
-
- return v1.get_schema_from_model_field(
- field=field,
- model_name_map=model_name_map,
- field_mapping=field_mapping,
- separate_input_output_schemas=separate_input_output_schemas,
- )
-
- return v2.get_schema_from_model_field(
- field=field, # type: ignore[arg-type]
- model_name_map=model_name_map,
- field_mapping=field_mapping, # type: ignore[arg-type]
- separate_input_output_schemas=separate_input_output_schemas,
- )
-
-
-def _is_model_field(value: Any) -> bool:
- if isinstance(value, may_v1.ModelField):
- return True
-
- return isinstance(value, v2.ModelField)
-
-
-def _is_model_class(value: Any) -> bool:
- if lenient_issubclass(value, may_v1.BaseModel):
- return True
-
- return lenient_issubclass(value, v2.BaseModel) # type: ignore[attr-defined]
diff --git a/fastapi/_compat/may_v1.py b/fastapi/_compat/may_v1.py
deleted file mode 100644
index 3ac86aa98..000000000
--- a/fastapi/_compat/may_v1.py
+++ /dev/null
@@ -1,124 +0,0 @@
-import sys
-from collections.abc import Sequence
-from typing import Any, Literal, Union
-
-from fastapi.types import ModelNameMap
-
-if sys.version_info >= (3, 14):
-
- class AnyUrl:
- pass
-
- class BaseConfig:
- pass
-
- class BaseModel:
- pass
-
- class Color:
- pass
-
- class CoreSchema:
- pass
-
- class ErrorWrapper:
- pass
-
- class FieldInfo:
- pass
-
- class GetJsonSchemaHandler:
- pass
-
- class JsonSchemaValue:
- pass
-
- class ModelField:
- pass
-
- class NameEmail:
- pass
-
- class RequiredParam:
- pass
-
- class SecretBytes:
- pass
-
- class SecretStr:
- pass
-
- class Undefined:
- pass
-
- class UndefinedType:
- pass
-
- class Url:
- pass
-
- from .v2 import ValidationError, create_model
-
- def get_definitions(
- *,
- fields: list[ModelField],
- model_name_map: ModelNameMap,
- separate_input_output_schemas: bool = True,
- ) -> tuple[
- dict[
- tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue
- ],
- dict[str, dict[str, Any]],
- ]:
- return {}, {} # pragma: no cover
-
-
-else:
- from .v1 import AnyUrl as AnyUrl
- from .v1 import BaseConfig as BaseConfig
- from .v1 import BaseModel as BaseModel
- from .v1 import Color as Color
- from .v1 import CoreSchema as CoreSchema
- from .v1 import ErrorWrapper as ErrorWrapper
- from .v1 import FieldInfo as FieldInfo
- from .v1 import GetJsonSchemaHandler as GetJsonSchemaHandler
- from .v1 import JsonSchemaValue as JsonSchemaValue
- from .v1 import ModelField as ModelField
- from .v1 import NameEmail as NameEmail
- from .v1 import RequiredParam as RequiredParam
- from .v1 import SecretBytes as SecretBytes
- from .v1 import SecretStr as SecretStr
- from .v1 import Undefined as Undefined
- from .v1 import UndefinedType as UndefinedType
- from .v1 import Url as Url
- from .v1 import ValidationError, create_model
- from .v1 import get_definitions as get_definitions
-
-
-RequestErrorModel: type[BaseModel] = create_model("Request")
-
-
-def _normalize_errors(errors: Sequence[Any]) -> list[dict[str, Any]]:
- use_errors: list[Any] = []
- for error in errors:
- if isinstance(error, ErrorWrapper):
- new_errors = ValidationError(
- errors=[error], model=RequestErrorModel
- ).errors()
- use_errors.extend(new_errors)
- elif isinstance(error, list):
- use_errors.extend(_normalize_errors(error))
- else:
- use_errors.append(error)
- return use_errors
-
-
-def _regenerate_error_with_loc(
- *, errors: Sequence[Any], loc_prefix: tuple[Union[str, int], ...]
-) -> list[dict[str, Any]]:
- updated_loc_errors: list[Any] = [
- {**err, "loc": loc_prefix + err.get("loc", ())}
- for err in _normalize_errors(errors)
- ]
-
- return updated_loc_errors
diff --git a/fastapi/_compat/model_field.py b/fastapi/_compat/model_field.py
deleted file mode 100644
index 47d05cb94..000000000
--- a/fastapi/_compat/model_field.py
+++ /dev/null
@@ -1,50 +0,0 @@
-from typing import (
- Any,
- Union,
-)
-
-from fastapi.types import IncEx
-from pydantic.fields import FieldInfo
-from typing_extensions import Literal, Protocol
-
-
-class ModelField(Protocol):
- field_info: "FieldInfo"
- name: str
- mode: Literal["validation", "serialization"] = "validation"
- _version: Literal["v1", "v2"] = "v1"
-
- @property
- def alias(self) -> str: ...
-
- @property
- def required(self) -> bool: ...
-
- @property
- def default(self) -> Any: ...
-
- @property
- def type_(self) -> Any: ...
-
- def get_default(self) -> Any: ...
-
- def validate(
- self,
- value: Any,
- values: dict[str, Any] = {}, # noqa: B006
- *,
- loc: tuple[Union[int, str], ...] = (),
- ) -> tuple[Any, Union[list[dict[str, Any]], None]]: ...
-
- def serialize(
- self,
- value: Any,
- *,
- mode: Literal["json", "python"] = "json",
- include: Union[IncEx, None] = None,
- exclude: Union[IncEx, None] = None,
- by_alias: bool = True,
- exclude_unset: bool = False,
- exclude_defaults: bool = False,
- exclude_none: bool = False,
- ) -> Any: ...
diff --git a/fastapi/_compat/shared.py b/fastapi/_compat/shared.py
index 3a11e88ac..419b58f7f 100644
--- a/fastapi/_compat/shared.py
+++ b/fastapi/_compat/shared.py
@@ -1,6 +1,7 @@
import sys
import types
import typing
+import warnings
from collections import deque
from collections.abc import Mapping, Sequence
from dataclasses import is_dataclass
@@ -10,7 +11,6 @@ from typing import (
Union,
)
-from fastapi._compat import may_v1
from fastapi.types import UnionType
from pydantic import BaseModel
from pydantic.version import VERSION as PYDANTIC_VERSION
@@ -81,9 +81,7 @@ def value_is_sequence(value: Any) -> bool:
def _annotation_is_complex(annotation: Union[type[Any], None]) -> bool:
return (
- lenient_issubclass(
- annotation, (BaseModel, may_v1.BaseModel, Mapping, UploadFile)
- )
+ lenient_issubclass(annotation, (BaseModel, Mapping, UploadFile))
or _annotation_is_sequence(annotation)
or is_dataclass(annotation)
)
@@ -179,13 +177,27 @@ def is_uploadfile_sequence_annotation(annotation: Any) -> bool:
)
+def is_pydantic_v1_model_instance(obj: Any) -> bool:
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", UserWarning)
+ from pydantic import v1
+ return isinstance(obj, v1.BaseModel)
+
+
+def is_pydantic_v1_model_class(cls: Any) -> bool:
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", UserWarning)
+ from pydantic import v1
+ return lenient_issubclass(cls, v1.BaseModel)
+
+
def annotation_is_pydantic_v1(annotation: Any) -> bool:
- if lenient_issubclass(annotation, may_v1.BaseModel):
+ if is_pydantic_v1_model_class(annotation):
return True
origin = get_origin(annotation)
if origin is Union or origin is UnionType:
for arg in get_args(annotation):
- if lenient_issubclass(arg, may_v1.BaseModel):
+ if is_pydantic_v1_model_class(arg):
return True
if field_annotation_is_sequence(annotation):
for sub_annotation in get_args(annotation):
diff --git a/fastapi/_compat/v1.py b/fastapi/_compat/v1.py
deleted file mode 100644
index b0a9dd35f..000000000
--- a/fastapi/_compat/v1.py
+++ /dev/null
@@ -1,222 +0,0 @@
-from collections.abc import Sequence
-from copy import copy
-from dataclasses import dataclass, is_dataclass
-from enum import Enum
-from typing import (
- Any,
- Callable,
- Union,
-)
-
-from fastapi._compat import shared
-from fastapi.openapi.constants import REF_PREFIX as REF_PREFIX
-from fastapi.types import ModelNameMap
-from pydantic.v1 import BaseConfig as BaseConfig
-from pydantic.v1 import BaseModel as BaseModel
-from pydantic.v1 import ValidationError as ValidationError
-from pydantic.v1 import create_model as create_model
-from pydantic.v1.class_validators import Validator as Validator
-from pydantic.v1.color import Color as Color
-from pydantic.v1.error_wrappers import ErrorWrapper as ErrorWrapper
-from pydantic.v1.fields import (
- SHAPE_FROZENSET,
- SHAPE_LIST,
- SHAPE_SEQUENCE,
- SHAPE_SET,
- SHAPE_SINGLETON,
- SHAPE_TUPLE,
- SHAPE_TUPLE_ELLIPSIS,
-)
-from pydantic.v1.fields import FieldInfo as FieldInfo
-from pydantic.v1.fields import ModelField as ModelField
-from pydantic.v1.fields import Undefined as Undefined
-from pydantic.v1.fields import UndefinedType as UndefinedType
-from pydantic.v1.networks import AnyUrl as AnyUrl
-from pydantic.v1.networks import NameEmail as NameEmail
-from pydantic.v1.schema import TypeModelSet as TypeModelSet
-from pydantic.v1.schema import field_schema, model_process_schema
-from pydantic.v1.schema import (
- get_annotation_from_field_info as get_annotation_from_field_info,
-)
-from pydantic.v1.schema import (
- get_flat_models_from_field as get_flat_models_from_field,
-)
-from pydantic.v1.schema import (
- get_flat_models_from_fields as get_flat_models_from_fields,
-)
-from pydantic.v1.schema import get_model_name_map as get_model_name_map
-from pydantic.v1.types import SecretBytes as SecretBytes
-from pydantic.v1.types import SecretStr as SecretStr
-from pydantic.v1.typing import evaluate_forwardref as evaluate_forwardref
-from pydantic.v1.utils import lenient_issubclass as lenient_issubclass
-from pydantic.version import VERSION as PYDANTIC_VERSION
-from typing_extensions import Literal
-
-PYDANTIC_VERSION_MINOR_TUPLE = tuple(int(x) for x in PYDANTIC_VERSION.split(".")[:2])
-PYDANTIC_V2 = PYDANTIC_VERSION_MINOR_TUPLE[0] == 2
-# Keeping old "Required" functionality from Pydantic V1, without
-# shadowing typing.Required.
-RequiredParam: Any = Ellipsis
-
-
-GetJsonSchemaHandler = Any
-JsonSchemaValue = dict[str, Any]
-CoreSchema = Any
-Url = AnyUrl
-
-sequence_shapes = {
- SHAPE_LIST,
- SHAPE_SET,
- SHAPE_FROZENSET,
- SHAPE_TUPLE,
- SHAPE_SEQUENCE,
- SHAPE_TUPLE_ELLIPSIS,
-}
-sequence_shape_to_type = {
- SHAPE_LIST: list,
- SHAPE_SET: set,
- SHAPE_TUPLE: tuple,
- SHAPE_SEQUENCE: list,
- SHAPE_TUPLE_ELLIPSIS: list,
-}
-
-
-@dataclass
-class GenerateJsonSchema:
- ref_template: str
-
-
-class PydanticSchemaGenerationError(Exception):
- pass
-
-
-RequestErrorModel: type[BaseModel] = create_model("Request")
-
-
-def with_info_plain_validator_function(
- function: Callable[..., Any],
- *,
- ref: Union[str, None] = None,
- metadata: Any = None,
- serialization: Any = None,
-) -> Any:
- return {}
-
-
-def get_model_definitions(
- *,
- flat_models: set[Union[type[BaseModel], type[Enum]]],
- model_name_map: dict[Union[type[BaseModel], type[Enum]], str],
-) -> dict[str, Any]:
- definitions: dict[str, dict[str, Any]] = {}
- for model in flat_models:
- m_schema, m_definitions, m_nested_models = model_process_schema(
- model, model_name_map=model_name_map, ref_prefix=REF_PREFIX
- )
- definitions.update(m_definitions)
- model_name = model_name_map[model]
- definitions[model_name] = m_schema
- for m_schema in definitions.values():
- if "description" in m_schema:
- m_schema["description"] = m_schema["description"].split("\f")[0]
- return definitions
-
-
-def is_pv1_scalar_field(field: ModelField) -> bool:
- from fastapi import params
-
- field_info = field.field_info
- if not (
- field.shape == SHAPE_SINGLETON
- and not lenient_issubclass(field.type_, BaseModel)
- and not lenient_issubclass(field.type_, dict)
- and not shared.field_annotation_is_sequence(field.type_)
- and not is_dataclass(field.type_)
- and not isinstance(field_info, params.Body)
- ):
- return False
- if field.sub_fields:
- if not all(is_pv1_scalar_field(f) for f in field.sub_fields):
- return False
- return True
-
-
-def _model_dump(
- model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any
-) -> Any:
- return model.dict(**kwargs)
-
-
-def _get_model_config(model: BaseModel) -> Any:
- return model.__config__
-
-
-def get_schema_from_model_field(
- *,
- field: ModelField,
- model_name_map: ModelNameMap,
- field_mapping: dict[
- tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue
- ],
- separate_input_output_schemas: bool = True,
-) -> dict[str, Any]:
- return field_schema(
- field,
- model_name_map=model_name_map, # type: ignore[arg-type]
- ref_prefix=REF_PREFIX,
- )[0]
-
-
-# def get_compat_model_name_map(fields: List[ModelField]) -> ModelNameMap:
-# models = get_flat_models_from_fields(fields, known_models=set())
-# return get_model_name_map(models) # type: ignore[no-any-return]
-
-
-def get_definitions(
- *,
- fields: list[ModelField],
- model_name_map: ModelNameMap,
- separate_input_output_schemas: bool = True,
-) -> tuple[
- dict[tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue],
- dict[str, dict[str, Any]],
-]:
- models = get_flat_models_from_fields(fields, known_models=set())
- return {}, get_model_definitions(flat_models=models, model_name_map=model_name_map) # type: ignore[arg-type]
-
-
-def is_scalar_field(field: ModelField) -> bool:
- return is_pv1_scalar_field(field)
-
-
-def is_sequence_field(field: ModelField) -> bool:
- return field.shape in sequence_shapes or shared._annotation_is_sequence(field.type_)
-
-
-def is_bytes_field(field: ModelField) -> bool:
- return lenient_issubclass(field.type_, bytes)
-
-
-def is_bytes_sequence_field(field: ModelField) -> bool:
- return field.shape in sequence_shapes and lenient_issubclass(field.type_, bytes)
-
-
-def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo:
- return copy(field_info)
-
-
-def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]:
- return sequence_shape_to_type[field.shape](value) # type: ignore[no-any-return]
-
-
-def create_body_model(
- *, fields: Sequence[ModelField], model_name: str
-) -> type[BaseModel]:
- BodyModel = create_model(model_name)
- for f in fields:
- BodyModel.__fields__[f.name] = f
- return BodyModel
-
-
-def get_model_fields(model: type[BaseModel]) -> list[ModelField]:
- return list(model.__fields__.values())
diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py
index cbcb98e1a..25b681453 100644
--- a/fastapi/_compat/v2.py
+++ b/fastapi/_compat/v2.py
@@ -4,6 +4,7 @@ from collections.abc import Sequence
from copy import copy, deepcopy
from dataclasses import dataclass, is_dataclass
from enum import Enum
+from functools import lru_cache
from typing import (
Annotated,
Any,
@@ -11,7 +12,7 @@ from typing import (
cast,
)
-from fastapi._compat import may_v1, shared
+from fastapi._compat import shared
from fastapi.openapi.constants import REF_TEMPLATE
from fastapi.types import IncEx, ModelNameMap, UnionType
from pydantic import BaseModel, ConfigDict, Field, TypeAdapter, create_model
@@ -175,7 +176,7 @@ class ModelField:
None,
)
except ValidationError as exc:
- return None, may_v1._regenerate_error_with_loc(
+ return None, _regenerate_error_with_loc(
errors=exc.errors(include_url=False), loc_prefix=loc
)
@@ -210,22 +211,6 @@ class ModelField:
return id(self)
-def get_annotation_from_field_info(
- annotation: Any, field_info: FieldInfo, field_name: str
-) -> Any:
- return annotation
-
-
-def _model_dump(
- model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any
-) -> Any:
- return model.model_dump(mode=mode, **kwargs)
-
-
-def _get_model_config(model: BaseModel) -> Any:
- return model.model_config
-
-
def _has_computed_fields(field: ModelField) -> bool:
computed_fields = field._type_adapter.core_schema.get("schema", {}).get(
"computed_fields", []
@@ -490,6 +475,11 @@ def get_model_fields(model: type[BaseModel]) -> list[ModelField]:
return model_fields
+@lru_cache
+def get_cached_model_fields(model: type[BaseModel]) -> list[ModelField]:
+ return get_model_fields(model) # type: ignore[return-value]
+
+
# Duplicate of several schema functions from Pydantic v1 to make them compatible with
# Pydantic v2 and allow mixing the models
@@ -503,22 +493,23 @@ def normalize_name(name: str) -> str:
def get_model_name_map(unique_models: TypeModelSet) -> dict[TypeModelOrEnum, str]:
name_model_map = {}
- conflicting_names: set[str] = set()
for model in unique_models:
model_name = normalize_name(model.__name__)
- if model_name in conflicting_names:
- model_name = get_long_model_name(model)
- name_model_map[model_name] = model
- elif model_name in name_model_map:
- conflicting_names.add(model_name)
- conflicting_model = name_model_map.pop(model_name)
- name_model_map[get_long_model_name(conflicting_model)] = conflicting_model
- name_model_map[get_long_model_name(model)] = model
- else:
- name_model_map[model_name] = model
+ name_model_map[model_name] = model
return {v: k for k, v in name_model_map.items()}
+def get_compat_model_name_map(fields: list[ModelField]) -> ModelNameMap:
+ all_flat_models = set()
+
+ v2_model_fields = [field for field in fields if isinstance(field, ModelField)]
+ v2_flat_models = get_flat_models_from_fields(v2_model_fields, known_models=set())
+ all_flat_models = all_flat_models.union(v2_flat_models) # type: ignore[arg-type]
+
+ model_name_map = get_model_name_map(all_flat_models) # type: ignore[arg-type]
+ return model_name_map
+
+
def get_flat_models_from_model(
model: type["BaseModel"], known_models: Union[TypeModelSet, None] = None
) -> TypeModelSet:
@@ -567,5 +558,11 @@ def get_flat_models_from_fields(
return known_models
-def get_long_model_name(model: TypeModelOrEnum) -> str:
- return f"{model.__module__}__{model.__qualname__}".replace(".", "__")
+def _regenerate_error_with_loc(
+ *, errors: Sequence[Any], loc_prefix: tuple[Union[str, int], ...]
+) -> list[dict[str, Any]]:
+ updated_loc_errors: list[Any] = [
+ {**err, "loc": loc_prefix + err.get("loc", ())} for err in errors
+ ]
+
+ return updated_loc_errors
diff --git a/fastapi/datastructures.py b/fastapi/datastructures.py
index 492cbfccc..2bf5fdb26 100644
--- a/fastapi/datastructures.py
+++ b/fastapi/datastructures.py
@@ -1,3 +1,4 @@
+from collections.abc import Mapping
from typing import (
Annotated,
Any,
@@ -9,11 +10,7 @@ from typing import (
)
from annotated_doc import Doc
-from fastapi._compat import (
- CoreSchema,
- GetJsonSchemaHandler,
- JsonSchemaValue,
-)
+from pydantic import GetJsonSchemaHandler
from starlette.datastructures import URL as URL # noqa: F401
from starlette.datastructures import Address as Address # noqa: F401
from starlette.datastructures import FormData as FormData # noqa: F401
@@ -142,14 +139,14 @@ class UploadFile(StarletteUploadFile):
@classmethod
def __get_pydantic_json_schema__(
- cls, core_schema: CoreSchema, handler: GetJsonSchemaHandler
- ) -> JsonSchemaValue:
+ cls, core_schema: Mapping[str, Any], handler: GetJsonSchemaHandler
+ ) -> dict[str, Any]:
return {"type": "string", "format": "binary"}
@classmethod
def __get_pydantic_core_schema__(
- cls, source: type[Any], handler: Callable[[Any], CoreSchema]
- ) -> CoreSchema:
+ cls, source: type[Any], handler: Callable[[Any], Mapping[str, Any]]
+ ) -> Mapping[str, Any]:
from ._compat.v2 import with_info_plain_validator_function
return with_info_plain_validator_function(cls._validate)
diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py
index 39d0bd89c..45e1ff3ed 100644
--- a/fastapi/dependencies/utils.py
+++ b/fastapi/dependencies/utils.py
@@ -1,7 +1,6 @@
import dataclasses
import inspect
import sys
-import warnings
from collections.abc import Coroutine, Mapping, Sequence
from contextlib import AsyncExitStack, contextmanager
from copy import copy, deepcopy
@@ -22,13 +21,11 @@ from fastapi._compat import (
ModelField,
RequiredParam,
Undefined,
- _is_error_wrapper,
- _is_model_class,
+ _regenerate_error_with_loc,
copy_field_info,
create_body_model,
evaluate_forwardref,
field_annotation_is_scalar,
- get_annotation_from_field_info,
get_cached_model_fields,
get_missing_field_error,
is_bytes_field,
@@ -39,12 +36,10 @@ from fastapi._compat import (
is_uploadfile_or_nonable_uploadfile_annotation,
is_uploadfile_sequence_annotation,
lenient_issubclass,
- may_v1,
sequence_types,
serialize_sequence_value,
value_is_sequence,
)
-from fastapi._compat.shared import annotation_is_pydantic_v1
from fastapi.background import BackgroundTasks
from fastapi.concurrency import (
asynccontextmanager,
@@ -72,8 +67,6 @@ from starlette.responses import Response
from starlette.websockets import WebSocket
from typing_extensions import Literal, get_args, get_origin
-from .. import temp_pydantic_v1_params
-
multipart_not_installed_error = (
'Form data requires "python-multipart" to be installed. \n'
'You can install "python-multipart" with: \n\n'
@@ -189,7 +182,7 @@ def _get_flat_fields_from_params(fields: list[ModelField]) -> list[ModelField]:
if not fields:
return fields
first_field = fields[0]
- if len(fields) == 1 and _is_model_class(first_field.type_):
+ if len(fields) == 1 and lenient_issubclass(first_field.type_, BaseModel):
fields_to_extract = get_cached_model_fields(first_field.type_)
return fields_to_extract
return fields
@@ -323,16 +316,7 @@ def get_dependant(
)
continue
assert param_details.field is not None
- if isinstance(param_details.field, may_v1.ModelField):
- warnings.warn(
- "pydantic.v1 is deprecated and will soon stop being supported by FastAPI."
- f" Please update the param {param_name}: {param_details.type_annotation!r}.",
- category=DeprecationWarning,
- stacklevel=5,
- )
- if isinstance(
- param_details.field.field_info, (params.Body, temp_pydantic_v1_params.Body)
- ):
+ if isinstance(param_details.field.field_info, params.Body):
dependant.body_params.append(param_details.field)
else:
add_param_to_fields(field=param_details.field, dependant=dependant)
@@ -391,7 +375,7 @@ def analyze_param(
fastapi_annotations = [
arg
for arg in annotated_args[1:]
- if isinstance(arg, (FieldInfo, may_v1.FieldInfo, params.Depends))
+ if isinstance(arg, (FieldInfo, params.Depends))
]
fastapi_specific_annotations = [
arg
@@ -400,30 +384,27 @@ def analyze_param(
arg,
(
params.Param,
- temp_pydantic_v1_params.Param,
params.Body,
- temp_pydantic_v1_params.Body,
params.Depends,
),
)
]
if fastapi_specific_annotations:
- fastapi_annotation: Union[
- FieldInfo, may_v1.FieldInfo, params.Depends, None
- ] = fastapi_specific_annotations[-1]
+ fastapi_annotation: Union[FieldInfo, params.Depends, None] = (
+ fastapi_specific_annotations[-1]
+ )
else:
fastapi_annotation = None
# Set default for Annotated FieldInfo
- if isinstance(fastapi_annotation, (FieldInfo, may_v1.FieldInfo)):
+ if isinstance(fastapi_annotation, FieldInfo):
# Copy `field_info` because we mutate `field_info.default` below.
field_info = copy_field_info(
field_info=fastapi_annotation, # type: ignore[arg-type]
annotation=use_annotation,
)
- assert field_info.default in {
- Undefined,
- may_v1.Undefined,
- } or field_info.default in {RequiredParam, may_v1.RequiredParam}, (
+ assert (
+ field_info.default == Undefined or field_info.default == RequiredParam
+ ), (
f"`{field_info.__class__.__name__}` default value cannot be set in"
f" `Annotated` for {param_name!r}. Set the default value with `=` instead."
)
@@ -447,7 +428,7 @@ def analyze_param(
)
depends = value
# Get FieldInfo from default value
- elif isinstance(value, (FieldInfo, may_v1.FieldInfo)):
+ elif isinstance(value, FieldInfo):
assert field_info is None, (
"Cannot specify FastAPI annotations in `Annotated` and default value"
f" together for {param_name!r}"
@@ -491,14 +472,7 @@ def analyze_param(
) or is_uploadfile_sequence_annotation(type_annotation):
field_info = params.File(annotation=use_annotation, default=default_value)
elif not field_annotation_is_scalar(annotation=type_annotation):
- if annotation_is_pydantic_v1(use_annotation):
- field_info = temp_pydantic_v1_params.Body( # type: ignore[assignment]
- annotation=use_annotation, default=default_value
- )
- else:
- field_info = params.Body(
- annotation=use_annotation, default=default_value
- )
+ field_info = params.Body(annotation=use_annotation, default=default_value)
else:
field_info = params.Query(annotation=use_annotation, default=default_value)
@@ -507,23 +481,17 @@ def analyze_param(
if field_info is not None:
# Handle field_info.in_
if is_path_param:
- assert isinstance(
- field_info, (params.Path, temp_pydantic_v1_params.Path)
- ), (
+ assert isinstance(field_info, params.Path), (
f"Cannot use `{field_info.__class__.__name__}` for path param"
f" {param_name!r}"
)
elif (
- isinstance(field_info, (params.Param, temp_pydantic_v1_params.Param))
+ isinstance(field_info, params.Param)
and getattr(field_info, "in_", None) is None
):
field_info.in_ = params.ParamTypes.query
- use_annotation_from_field_info = get_annotation_from_field_info(
- use_annotation,
- field_info,
- param_name,
- )
- if isinstance(field_info, (params.Form, temp_pydantic_v1_params.Form)):
+ use_annotation_from_field_info = use_annotation
+ if isinstance(field_info, params.Form):
ensure_multipart_is_installed()
if not field_info.alias and getattr(field_info, "convert_underscores", None):
alias = param_name.replace("_", "-")
@@ -535,20 +503,19 @@ def analyze_param(
type_=use_annotation_from_field_info,
default=field_info.default,
alias=alias,
- required=field_info.default
- in (RequiredParam, may_v1.RequiredParam, Undefined),
+ required=field_info.default in (RequiredParam, Undefined),
field_info=field_info,
)
if is_path_param:
assert is_scalar_field(field=field), (
"Path params must be of one of the supported types"
)
- elif isinstance(field_info, (params.Query, temp_pydantic_v1_params.Query)):
+ elif isinstance(field_info, params.Query):
assert (
is_scalar_field(field)
or is_scalar_sequence_field(field)
or (
- _is_model_class(field.type_)
+ lenient_issubclass(field.type_, BaseModel)
# For Pydantic v1
and getattr(field, "shape", 1) == 1
)
@@ -742,10 +709,8 @@ def _validate_value_with_model_field(
else:
return deepcopy(field.default), []
v_, errors_ = field.validate(value, values, loc=loc)
- if _is_error_wrapper(errors_): # type: ignore[arg-type]
- return None, [errors_]
- elif isinstance(errors_, list):
- new_errors = may_v1._regenerate_error_with_loc(errors=errors_, loc_prefix=())
+ if isinstance(errors_, list):
+ new_errors = _regenerate_error_with_loc(errors=errors_, loc_prefix=())
return None, new_errors
else:
return v_, []
@@ -762,7 +727,7 @@ def _get_multidict_value(
if (
value is None
or (
- isinstance(field.field_info, (params.Form, temp_pydantic_v1_params.Form))
+ isinstance(field.field_info, params.Form)
and isinstance(value, str) # For type checks
and value == ""
)
@@ -832,7 +797,7 @@ def request_params_to_args(
if single_not_embedded_field:
field_info = first_field.field_info
- assert isinstance(field_info, (params.Param, temp_pydantic_v1_params.Param)), (
+ assert isinstance(field_info, params.Param), (
"Params must be subclasses of Param"
)
loc: tuple[str, ...] = (field_info.in_.value,)
@@ -844,7 +809,7 @@ def request_params_to_args(
for field in fields:
value = _get_multidict_value(field, received_params)
field_info = field.field_info
- assert isinstance(field_info, (params.Param, temp_pydantic_v1_params.Param)), (
+ assert isinstance(field_info, params.Param), (
"Params must be subclasses of Param"
)
loc = (field_info.in_.value, get_validation_alias(field))
@@ -871,7 +836,7 @@ def is_union_of_base_models(field_type: Any) -> bool:
union_args = get_args(field_type)
for arg in union_args:
- if not _is_model_class(arg):
+ if not lenient_issubclass(arg, BaseModel):
return False
return True
@@ -893,8 +858,8 @@ def _should_embed_body_fields(fields: list[ModelField]) -> bool:
# If it's a Form (or File) field, it has to be a BaseModel (or a union of BaseModels) to be top level
# otherwise it has to be embedded, so that the key value pair can be extracted
if (
- isinstance(first_field.field_info, (params.Form, temp_pydantic_v1_params.Form))
- and not _is_model_class(first_field.type_)
+ isinstance(first_field.field_info, params.Form)
+ and not lenient_issubclass(first_field.type_, BaseModel)
and not is_union_of_base_models(first_field.type_)
):
return True
@@ -911,14 +876,14 @@ async def _extract_form_body(
value = _get_multidict_value(field, received_body)
field_info = field.field_info
if (
- isinstance(field_info, (params.File, temp_pydantic_v1_params.File))
+ isinstance(field_info, params.File)
and is_bytes_field(field)
and isinstance(value, UploadFile)
):
value = await value.read()
elif (
is_bytes_sequence_field(field)
- and isinstance(field_info, (params.File, temp_pydantic_v1_params.File))
+ and isinstance(field_info, params.File)
and value_is_sequence(value)
):
# For types
@@ -964,7 +929,7 @@ async def request_body_to_args(
if (
single_not_embedded_field
- and _is_model_class(first_field.type_)
+ and lenient_issubclass(first_field.type_, BaseModel)
and isinstance(received_body, FormData)
):
fields_to_extract = get_cached_model_fields(first_field.type_)
@@ -1029,28 +994,15 @@ def get_body_field(
BodyFieldInfo_kwargs["default"] = None
if any(isinstance(f.field_info, params.File) for f in flat_dependant.body_params):
BodyFieldInfo: type[params.Body] = params.File
- elif any(
- isinstance(f.field_info, temp_pydantic_v1_params.File)
- for f in flat_dependant.body_params
- ):
- BodyFieldInfo: type[temp_pydantic_v1_params.Body] = temp_pydantic_v1_params.File # type: ignore[no-redef]
elif any(isinstance(f.field_info, params.Form) for f in flat_dependant.body_params):
BodyFieldInfo = params.Form
- elif any(
- isinstance(f.field_info, temp_pydantic_v1_params.Form)
- for f in flat_dependant.body_params
- ):
- BodyFieldInfo = temp_pydantic_v1_params.Form # type: ignore[assignment]
else:
- if annotation_is_pydantic_v1(BodyModel):
- BodyFieldInfo = temp_pydantic_v1_params.Body # type: ignore[assignment]
- else:
- BodyFieldInfo = params.Body
+ BodyFieldInfo = params.Body
body_param_media_types = [
f.field_info.media_type
for f in flat_dependant.body_params
- if isinstance(f.field_info, (params.Body, temp_pydantic_v1_params.Body))
+ if isinstance(f.field_info, params.Body)
]
if len(set(body_param_media_types)) == 1:
BodyFieldInfo_kwargs["media_type"] = body_param_media_types[0]
diff --git a/fastapi/encoders.py b/fastapi/encoders.py
index 549da3279..e8610c983 100644
--- a/fastapi/encoders.py
+++ b/fastapi/encoders.py
@@ -18,14 +18,18 @@ from typing import Annotated, Any, Callable, Optional, Union
from uuid import UUID
from annotated_doc import Doc
-from fastapi._compat import may_v1
+from fastapi.exceptions import PydanticV1NotSupportedError
from fastapi.types import IncEx
from pydantic import BaseModel
from pydantic.color import Color
from pydantic.networks import AnyUrl, NameEmail
from pydantic.types import SecretBytes, SecretStr
+from pydantic_core import PydanticUndefinedType
-from ._compat import Url, _is_undefined, _model_dump
+from ._compat import (
+ Url,
+ is_pydantic_v1_model_instance,
+)
# Taken from Pydantic v1 as is
@@ -63,7 +67,6 @@ def decimal_encoder(dec_value: Decimal) -> Union[int, float]:
ENCODERS_BY_TYPE: dict[type[Any], Callable[[Any], Any]] = {
bytes: lambda o: o.decode(),
Color: str,
- may_v1.Color: str,
datetime.date: isoformat,
datetime.datetime: isoformat,
datetime.time: isoformat,
@@ -80,19 +83,14 @@ ENCODERS_BY_TYPE: dict[type[Any], Callable[[Any], Any]] = {
IPv6Interface: str,
IPv6Network: str,
NameEmail: str,
- may_v1.NameEmail: str,
Path: str,
Pattern: lambda o: o.pattern,
SecretBytes: str,
- may_v1.SecretBytes: str,
SecretStr: str,
- may_v1.SecretStr: str,
set: list,
UUID: str,
Url: str,
- may_v1.Url: str,
AnyUrl: str,
- may_v1.AnyUrl: str,
}
@@ -224,15 +222,8 @@ def jsonable_encoder(
include = set(include)
if exclude is not None and not isinstance(exclude, (set, dict)):
exclude = set(exclude)
- if isinstance(obj, (BaseModel, may_v1.BaseModel)):
- # TODO: remove when deprecating Pydantic v1
- encoders: dict[Any, Any] = {}
- if isinstance(obj, may_v1.BaseModel):
- encoders = getattr(obj.__config__, "json_encoders", {})
- if custom_encoder:
- encoders = {**encoders, **custom_encoder}
- obj_dict = _model_dump(
- obj, # type: ignore[arg-type]
+ if isinstance(obj, BaseModel):
+ obj_dict = obj.model_dump(
mode="json",
include=include,
exclude=exclude,
@@ -241,14 +232,10 @@ def jsonable_encoder(
exclude_none=exclude_none,
exclude_defaults=exclude_defaults,
)
- if "__root__" in obj_dict:
- obj_dict = obj_dict["__root__"]
return jsonable_encoder(
obj_dict,
exclude_none=exclude_none,
exclude_defaults=exclude_defaults,
- # TODO: remove when deprecating Pydantic v1
- custom_encoder=encoders,
sqlalchemy_safe=sqlalchemy_safe,
)
if dataclasses.is_dataclass(obj):
@@ -271,7 +258,7 @@ def jsonable_encoder(
return str(obj)
if isinstance(obj, (str, int, float, type(None))):
return obj
- if _is_undefined(obj):
+ if isinstance(obj, PydanticUndefinedType):
return None
if isinstance(obj, dict):
encoded_dict = {}
@@ -331,7 +318,11 @@ def jsonable_encoder(
for encoder, classes_tuple in encoders_by_class_tuples.items():
if isinstance(obj, classes_tuple):
return encoder(obj)
-
+ if is_pydantic_v1_model_instance(obj):
+ raise PydanticV1NotSupportedError(
+ "pydantic.v1 models are no longer supported by FastAPI."
+ f" Please update the model {obj!r}."
+ )
try:
data = dict(obj)
except Exception as e:
diff --git a/fastapi/exceptions.py b/fastapi/exceptions.py
index 8e0c55902..1a3abd80c 100644
--- a/fastapi/exceptions.py
+++ b/fastapi/exceptions.py
@@ -231,3 +231,16 @@ class ResponseValidationError(ValidationException):
) -> None:
super().__init__(errors, endpoint_ctx=endpoint_ctx)
self.body = body
+
+
+class PydanticV1NotSupportedError(FastAPIError):
+ """
+ A pydantic.v1 model is used, which is no longer supported.
+ """
+
+
+class FastAPIDeprecationWarning(UserWarning):
+ """
+ A custom deprecation warning as DeprecationWarning is ignored
+ Ref: https://sethmlarson.dev/deprecations-via-warnings-dont-work-for-python-libraries
+ """
diff --git a/fastapi/openapi/models.py b/fastapi/openapi/models.py
index 680f67832..ac6a6d52c 100644
--- a/fastapi/openapi/models.py
+++ b/fastapi/openapi/models.py
@@ -1,15 +1,15 @@
-from collections.abc import Iterable
+from collections.abc import Iterable, Mapping
from enum import Enum
from typing import Annotated, Any, Callable, Optional, Union
-from fastapi._compat import (
- CoreSchema,
- GetJsonSchemaHandler,
- JsonSchemaValue,
- with_info_plain_validator_function,
-)
+from fastapi._compat import with_info_plain_validator_function
from fastapi.logger import logger
-from pydantic import AnyUrl, BaseModel, Field
+from pydantic import (
+ AnyUrl,
+ BaseModel,
+ Field,
+ GetJsonSchemaHandler,
+)
from typing_extensions import Literal, TypedDict
from typing_extensions import deprecated as typing_deprecated
@@ -43,14 +43,14 @@ except ImportError: # pragma: no cover
@classmethod
def __get_pydantic_json_schema__(
- cls, core_schema: CoreSchema, handler: GetJsonSchemaHandler
- ) -> JsonSchemaValue:
+ cls, core_schema: Mapping[str, Any], handler: GetJsonSchemaHandler
+ ) -> dict[str, Any]:
return {"type": "string", "format": "email"}
@classmethod
def __get_pydantic_core_schema__(
- cls, source: type[Any], handler: Callable[[Any], CoreSchema]
- ) -> CoreSchema:
+ cls, source: type[Any], handler: Callable[[Any], Mapping[str, Any]]
+ ) -> Mapping[str, Any]:
return with_info_plain_validator_function(cls._validate)
diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py
index a99d4188e..75ff26102 100644
--- a/fastapi/openapi/utils.py
+++ b/fastapi/openapi/utils.py
@@ -6,7 +6,6 @@ from typing import Any, Optional, Union, cast
from fastapi import routing
from fastapi._compat import (
- JsonSchemaValue,
ModelField,
Undefined,
get_compat_model_name_map,
@@ -23,6 +22,7 @@ from fastapi.dependencies.utils import (
get_validation_alias,
)
from fastapi.encoders import jsonable_encoder
+from fastapi.exceptions import FastAPIDeprecationWarning
from fastapi.openapi.constants import METHODS_WITH_BODY, REF_PREFIX
from fastapi.openapi.models import OpenAPI
from fastapi.params import Body, ParamTypes
@@ -38,8 +38,6 @@ from starlette.responses import JSONResponse
from starlette.routing import BaseRoute
from typing_extensions import Literal
-from .._compat import _is_model_field
-
validation_error_definition = {
"title": "ValidationError",
"type": "object",
@@ -108,7 +106,7 @@ def _get_openapi_operation_parameters(
dependant: Dependant,
model_name_map: ModelNameMap,
field_mapping: dict[
- tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue
+ tuple[ModelField, Literal["validation", "serialization"]], dict[str, Any]
],
separate_input_output_schemas: bool = True,
) -> list[dict[str, Any]]:
@@ -181,13 +179,13 @@ def get_openapi_operation_request_body(
body_field: Optional[ModelField],
model_name_map: ModelNameMap,
field_mapping: dict[
- tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue
+ tuple[ModelField, Literal["validation", "serialization"]], dict[str, Any]
],
separate_input_output_schemas: bool = True,
) -> Optional[dict[str, Any]]:
if not body_field:
return None
- assert _is_model_field(body_field)
+ assert isinstance(body_field, ModelField)
body_schema = get_schema_from_model_field(
field=body_field,
model_name_map=model_name_map,
@@ -215,9 +213,9 @@ def generate_operation_id(
*, route: routing.APIRoute, method: str
) -> str: # pragma: nocover
warnings.warn(
- "fastapi.openapi.utils.generate_operation_id() was deprecated, "
+ message="fastapi.openapi.utils.generate_operation_id() was deprecated, "
"it is not used internally, and will be removed soon",
- DeprecationWarning,
+ category=FastAPIDeprecationWarning,
stacklevel=2,
)
if route.operation_id:
@@ -264,7 +262,7 @@ def get_openapi_path(
operation_ids: set[str],
model_name_map: ModelNameMap,
field_mapping: dict[
- tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue
+ tuple[ModelField, Literal["validation", "serialization"]], dict[str, Any]
],
separate_input_output_schemas: bool = True,
) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]:
@@ -456,7 +454,7 @@ def get_fields_from_routes(
route, routing.APIRoute
):
if route.body_field:
- assert _is_model_field(route.body_field), (
+ assert isinstance(route.body_field, ModelField), (
"A request body must be a Pydantic Field"
)
body_fields_from_routes.append(route.body_field)
diff --git a/fastapi/param_functions.py b/fastapi/param_functions.py
index 844542594..0834fd741 100644
--- a/fastapi/param_functions.py
+++ b/fastapi/param_functions.py
@@ -5,6 +5,7 @@ from annotated_doc import Doc
from fastapi import params
from fastapi._compat import Undefined
from fastapi.openapi.models import Example
+from pydantic import AliasChoices, AliasPath
from typing_extensions import Literal, deprecated
_Unset: Any = Undefined
@@ -54,10 +55,8 @@ def Path( # noqa: N802
"""
),
] = _Unset,
- # TODO: update when deprecating Pydantic v1, import these types
- # validation_alias: str | AliasPath | AliasChoices | None
validation_alias: Annotated[
- Union[str, None],
+ Union[str, AliasPath, AliasChoices, None],
Doc(
"""
'Whitelist' validation step. The parameter field will be the single one
@@ -379,10 +378,8 @@ def Query( # noqa: N802
"""
),
] = _Unset,
- # TODO: update when deprecating Pydantic v1, import these types
- # validation_alias: str | AliasPath | AliasChoices | None
validation_alias: Annotated[
- Union[str, None],
+ Union[str, AliasPath, AliasChoices, None],
Doc(
"""
'Whitelist' validation step. The parameter field will be the single one
@@ -683,10 +680,8 @@ def Header( # noqa: N802
"""
),
] = _Unset,
- # TODO: update when deprecating Pydantic v1, import these types
- # validation_alias: str | AliasPath | AliasChoices | None
validation_alias: Annotated[
- Union[str, None],
+ Union[str, AliasPath, AliasChoices, None],
Doc(
"""
'Whitelist' validation step. The parameter field will be the single one
@@ -999,10 +994,8 @@ def Cookie( # noqa: N802
"""
),
] = _Unset,
- # TODO: update when deprecating Pydantic v1, import these types
- # validation_alias: str | AliasPath | AliasChoices | None
validation_alias: Annotated[
- Union[str, None],
+ Union[str, AliasPath, AliasChoices, None],
Doc(
"""
'Whitelist' validation step. The parameter field will be the single one
@@ -1326,10 +1319,8 @@ def Body( # noqa: N802
"""
),
] = _Unset,
- # TODO: update when deprecating Pydantic v1, import these types
- # validation_alias: str | AliasPath | AliasChoices | None
validation_alias: Annotated[
- Union[str, None],
+ Union[str, AliasPath, AliasChoices, None],
Doc(
"""
'Whitelist' validation step. The parameter field will be the single one
@@ -1641,10 +1632,8 @@ def Form( # noqa: N802
"""
),
] = _Unset,
- # TODO: update when deprecating Pydantic v1, import these types
- # validation_alias: str | AliasPath | AliasChoices | None
validation_alias: Annotated[
- Union[str, None],
+ Union[str, AliasPath, AliasChoices, None],
Doc(
"""
'Whitelist' validation step. The parameter field will be the single one
@@ -1955,10 +1944,8 @@ def File( # noqa: N802
"""
),
] = _Unset,
- # TODO: update when deprecating Pydantic v1, import these types
- # validation_alias: str | AliasPath | AliasChoices | None
validation_alias: Annotated[
- Union[str, None],
+ Union[str, AliasPath, AliasChoices, None],
Doc(
"""
'Whitelist' validation step. The parameter field will be the single one
diff --git a/fastapi/params.py b/fastapi/params.py
index c776c4a59..72e797f83 100644
--- a/fastapi/params.py
+++ b/fastapi/params.py
@@ -4,7 +4,9 @@ from dataclasses import dataclass
from enum import Enum
from typing import Annotated, Any, Callable, Optional, Union
+from fastapi.exceptions import FastAPIDeprecationWarning
from fastapi.openapi.models import Example
+from pydantic import AliasChoices, AliasPath
from pydantic.fields import FieldInfo
from typing_extensions import Literal, deprecated
@@ -33,9 +35,7 @@ class Param(FieldInfo): # type: ignore[misc]
annotation: Optional[Any] = None,
alias: Optional[str] = None,
alias_priority: Union[int, None] = _Unset,
- # TODO: update when deprecating Pydantic v1, import these types
- # validation_alias: str | AliasPath | AliasChoices | None
- validation_alias: Union[str, None] = None,
+ validation_alias: Union[str, AliasPath, AliasChoices, None] = None,
serialization_alias: Union[str, None] = None,
title: Optional[str] = None,
description: Optional[str] = None,
@@ -75,7 +75,7 @@ class Param(FieldInfo): # type: ignore[misc]
if example is not _Unset:
warnings.warn(
"`example` has been deprecated, please use `examples` instead",
- category=DeprecationWarning,
+ category=FastAPIDeprecationWarning,
stacklevel=4,
)
self.example = example
@@ -105,7 +105,7 @@ class Param(FieldInfo): # type: ignore[misc]
if regex is not None:
warnings.warn(
"`regex` has been deprecated, please use `pattern` instead",
- category=DeprecationWarning,
+ category=FastAPIDeprecationWarning,
stacklevel=4,
)
current_json_schema_extra = json_schema_extra or extra
@@ -146,9 +146,7 @@ class Path(Param): # type: ignore[misc]
annotation: Optional[Any] = None,
alias: Optional[str] = None,
alias_priority: Union[int, None] = _Unset,
- # TODO: update when deprecating Pydantic v1, import these types
- # validation_alias: str | AliasPath | AliasChoices | None
- validation_alias: Union[str, None] = None,
+ validation_alias: Union[str, AliasPath, AliasChoices, None] = None,
serialization_alias: Union[str, None] = None,
title: Optional[str] = None,
description: Optional[str] = None,
@@ -232,9 +230,7 @@ class Query(Param): # type: ignore[misc]
annotation: Optional[Any] = None,
alias: Optional[str] = None,
alias_priority: Union[int, None] = _Unset,
- # TODO: update when deprecating Pydantic v1, import these types
- # validation_alias: str | AliasPath | AliasChoices | None
- validation_alias: Union[str, None] = None,
+ validation_alias: Union[str, AliasPath, AliasChoices, None] = None,
serialization_alias: Union[str, None] = None,
title: Optional[str] = None,
description: Optional[str] = None,
@@ -316,9 +312,7 @@ class Header(Param): # type: ignore[misc]
annotation: Optional[Any] = None,
alias: Optional[str] = None,
alias_priority: Union[int, None] = _Unset,
- # TODO: update when deprecating Pydantic v1, import these types
- # validation_alias: str | AliasPath | AliasChoices | None
- validation_alias: Union[str, None] = None,
+ validation_alias: Union[str, AliasPath, AliasChoices, None] = None,
serialization_alias: Union[str, None] = None,
convert_underscores: bool = True,
title: Optional[str] = None,
@@ -402,9 +396,7 @@ class Cookie(Param): # type: ignore[misc]
annotation: Optional[Any] = None,
alias: Optional[str] = None,
alias_priority: Union[int, None] = _Unset,
- # TODO: update when deprecating Pydantic v1, import these types
- # validation_alias: str | AliasPath | AliasChoices | None
- validation_alias: Union[str, None] = None,
+ validation_alias: Union[str, AliasPath, AliasChoices, None] = None,
serialization_alias: Union[str, None] = None,
title: Optional[str] = None,
description: Optional[str] = None,
@@ -486,9 +478,7 @@ class Body(FieldInfo): # type: ignore[misc]
media_type: str = "application/json",
alias: Optional[str] = None,
alias_priority: Union[int, None] = _Unset,
- # TODO: update when deprecating Pydantic v1, import these types
- # validation_alias: str | AliasPath | AliasChoices | None
- validation_alias: Union[str, None] = None,
+ validation_alias: Union[str, AliasPath, AliasChoices, None] = None,
serialization_alias: Union[str, None] = None,
title: Optional[str] = None,
description: Optional[str] = None,
@@ -530,7 +520,7 @@ class Body(FieldInfo): # type: ignore[misc]
if example is not _Unset:
warnings.warn(
"`example` has been deprecated, please use `examples` instead",
- category=DeprecationWarning,
+ category=FastAPIDeprecationWarning,
stacklevel=4,
)
self.example = example
@@ -560,7 +550,7 @@ class Body(FieldInfo): # type: ignore[misc]
if regex is not None:
warnings.warn(
"`regex` has been deprecated, please use `pattern` instead",
- category=DeprecationWarning,
+ category=FastAPIDeprecationWarning,
stacklevel=4,
)
current_json_schema_extra = json_schema_extra or extra
@@ -599,9 +589,7 @@ class Form(Body): # type: ignore[misc]
media_type: str = "application/x-www-form-urlencoded",
alias: Optional[str] = None,
alias_priority: Union[int, None] = _Unset,
- # TODO: update when deprecating Pydantic v1, import these types
- # validation_alias: str | AliasPath | AliasChoices | None
- validation_alias: Union[str, None] = None,
+ validation_alias: Union[str, AliasPath, AliasChoices, None] = None,
serialization_alias: Union[str, None] = None,
title: Optional[str] = None,
description: Optional[str] = None,
@@ -683,9 +671,7 @@ class File(Form): # type: ignore[misc]
media_type: str = "multipart/form-data",
alias: Optional[str] = None,
alias_priority: Union[int, None] = _Unset,
- # TODO: update when deprecating Pydantic v1, import these types
- # validation_alias: str | AliasPath | AliasChoices | None
- validation_alias: Union[str, None] = None,
+ validation_alias: Union[str, AliasPath, AliasChoices, None] = None,
serialization_alias: Union[str, None] = None,
title: Optional[str] = None,
description: Optional[str] = None,
diff --git a/fastapi/routing.py b/fastapi/routing.py
index 2770e3253..9ca2f4673 100644
--- a/fastapi/routing.py
+++ b/fastapi/routing.py
@@ -2,7 +2,6 @@ import email.message
import functools
import inspect
import json
-import warnings
from collections.abc import (
AsyncIterator,
Awaitable,
@@ -22,16 +21,12 @@ from typing import (
)
from annotated_doc import Doc
-from fastapi import params, temp_pydantic_v1_params
+from fastapi import params
from fastapi._compat import (
ModelField,
Undefined,
- _get_model_config,
- _model_dump,
- _normalize_errors,
annotation_is_pydantic_v1,
lenient_issubclass,
- may_v1,
)
from fastapi.datastructures import Default, DefaultPlaceholder
from fastapi.dependencies.models import Dependant
@@ -48,6 +43,7 @@ from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import (
EndpointContext,
FastAPIError,
+ PydanticV1NotSupportedError,
RequestValidationError,
ResponseValidationError,
WebSocketRequestValidationError,
@@ -147,51 +143,6 @@ def websocket_session(
return app
-def _prepare_response_content(
- res: Any,
- *,
- exclude_unset: bool,
- exclude_defaults: bool = False,
- exclude_none: bool = False,
-) -> Any:
- if isinstance(res, may_v1.BaseModel):
- read_with_orm_mode = getattr(_get_model_config(res), "read_with_orm_mode", None) # type: ignore[arg-type]
- if read_with_orm_mode:
- # Let from_orm extract the data from this model instead of converting
- # it now to a dict.
- # Otherwise, there's no way to extract lazy data that requires attribute
- # access instead of dict iteration, e.g. lazy relationships.
- return res
- return _model_dump(
- res, # type: ignore[arg-type]
- by_alias=True,
- exclude_unset=exclude_unset,
- exclude_defaults=exclude_defaults,
- exclude_none=exclude_none,
- )
- elif isinstance(res, list):
- return [
- _prepare_response_content(
- item,
- exclude_unset=exclude_unset,
- exclude_defaults=exclude_defaults,
- exclude_none=exclude_none,
- )
- for item in res
- ]
- elif isinstance(res, dict):
- return {
- k: _prepare_response_content(
- v,
- exclude_unset=exclude_unset,
- exclude_defaults=exclude_defaults,
- exclude_none=exclude_none,
- )
- for k, v in res.items()
- }
- return res
-
-
def _merge_lifespan_context(
original_context: Lifespan[Any], nested_context: Lifespan[Any]
) -> Lifespan[Any]:
@@ -251,14 +202,6 @@ async def serialize_response(
) -> Any:
if field:
errors = []
- if not hasattr(field, "serialize"):
- # pydantic v1
- response_content = _prepare_response_content(
- response_content,
- exclude_unset=exclude_unset,
- exclude_defaults=exclude_defaults,
- exclude_none=exclude_none,
- )
if is_coroutine:
value, errors_ = field.validate(response_content, {}, loc=("response",))
else:
@@ -267,28 +210,15 @@ async def serialize_response(
)
if isinstance(errors_, list):
errors.extend(errors_)
- elif errors_:
- errors.append(errors_)
if errors:
ctx = endpoint_ctx or EndpointContext()
raise ResponseValidationError(
- errors=_normalize_errors(errors),
+ errors=errors,
body=response_content,
endpoint_ctx=ctx,
)
- if hasattr(field, "serialize"):
- return field.serialize(
- value,
- include=include,
- exclude=exclude,
- by_alias=by_alias,
- exclude_unset=exclude_unset,
- exclude_defaults=exclude_defaults,
- exclude_none=exclude_none,
- )
-
- return jsonable_encoder(
+ return field.serialize(
value,
include=include,
exclude=exclude,
@@ -297,6 +227,7 @@ async def serialize_response(
exclude_defaults=exclude_defaults,
exclude_none=exclude_none,
)
+
else:
return jsonable_encoder(response_content)
@@ -331,9 +262,7 @@ def get_request_handler(
) -> Callable[[Request], Coroutine[Any, Any, Response]]:
assert dependant.call is not None, "dependant.call must be a function"
is_coroutine = dependant.is_coroutine_callable
- is_body_form = body_field and isinstance(
- body_field.field_info, (params.Form, temp_pydantic_v1_params.Form)
- )
+ is_body_form = body_field and isinstance(body_field.field_info, params.Form)
if isinstance(response_class, DefaultPlaceholder):
actual_response_class: type[Response] = response_class.value
else:
@@ -463,7 +392,7 @@ def get_request_handler(
response.headers.raw.extend(solved_result.response.headers.raw)
if errors:
validation_error = RequestValidationError(
- _normalize_errors(errors), body=body, endpoint_ctx=endpoint_ctx
+ errors, body=body, endpoint_ctx=endpoint_ctx
)
raise validation_error
@@ -502,7 +431,7 @@ def get_websocket_app(
)
if solved_result.errors:
raise WebSocketRequestValidationError(
- _normalize_errors(solved_result.errors),
+ solved_result.errors,
endpoint_ctx=endpoint_ctx,
)
assert dependant.call is not None, "dependant.call must be a function"
@@ -637,11 +566,9 @@ class APIRoute(routing.Route):
)
response_name = "Response_" + self.unique_id
if annotation_is_pydantic_v1(self.response_model):
- warnings.warn(
- "pydantic.v1 is deprecated and will soon stop being supported by FastAPI."
- f" Please update the response model {self.response_model!r}.",
- category=DeprecationWarning,
- stacklevel=4,
+ raise PydanticV1NotSupportedError(
+ "pydantic.v1 models are no longer supported by FastAPI."
+ f" Please update the response model {self.response_model!r}."
)
self.response_field = create_model_field(
name=response_name,
@@ -677,11 +604,9 @@ class APIRoute(routing.Route):
)
response_name = f"Response_{additional_status_code}_{self.unique_id}"
if annotation_is_pydantic_v1(model):
- warnings.warn(
- "pydantic.v1 is deprecated and will soon stop being supported by FastAPI."
- f" In responses={{}}, please update {model}.",
- category=DeprecationWarning,
- stacklevel=4,
+ raise PydanticV1NotSupportedError(
+ "pydantic.v1 models are no longer supported by FastAPI."
+ f" In responses={{}}, please update {model}."
)
response_field = create_model_field(
name=response_name, type_=model, mode="serialization"
diff --git a/fastapi/temp_pydantic_v1_params.py b/fastapi/temp_pydantic_v1_params.py
deleted file mode 100644
index 1bda0ea9b..000000000
--- a/fastapi/temp_pydantic_v1_params.py
+++ /dev/null
@@ -1,717 +0,0 @@
-import warnings
-from typing import Annotated, Any, Callable, Optional, Union
-
-from fastapi.openapi.models import Example
-from fastapi.params import ParamTypes
-from typing_extensions import deprecated
-
-from ._compat.may_v1 import FieldInfo, Undefined
-
-_Unset: Any = Undefined
-
-
-class Param(FieldInfo):
- in_: ParamTypes
-
- def __init__(
- self,
- default: Any = Undefined,
- *,
- default_factory: Union[Callable[[], Any], None] = _Unset,
- annotation: Optional[Any] = None,
- alias: Optional[str] = None,
- alias_priority: Union[int, None] = _Unset,
- # TODO: update when deprecating Pydantic v1, import these types
- # validation_alias: str | AliasPath | AliasChoices | None
- validation_alias: Union[str, None] = None,
- serialization_alias: Union[str, None] = None,
- title: Optional[str] = None,
- description: Optional[str] = None,
- gt: Optional[float] = None,
- ge: Optional[float] = None,
- lt: Optional[float] = None,
- le: Optional[float] = None,
- min_length: Optional[int] = None,
- max_length: Optional[int] = None,
- pattern: Optional[str] = None,
- regex: Annotated[
- Optional[str],
- deprecated(
- "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
- ),
- ] = None,
- discriminator: Union[str, None] = None,
- strict: Union[bool, None] = _Unset,
- multiple_of: Union[float, None] = _Unset,
- allow_inf_nan: Union[bool, None] = _Unset,
- max_digits: Union[int, None] = _Unset,
- decimal_places: Union[int, None] = _Unset,
- examples: Optional[list[Any]] = None,
- example: Annotated[
- Optional[Any],
- deprecated(
- "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
- "although still supported. Use examples instead."
- ),
- ] = _Unset,
- openapi_examples: Optional[dict[str, Example]] = None,
- deprecated: Union[deprecated, str, bool, None] = None,
- include_in_schema: bool = True,
- json_schema_extra: Union[dict[str, Any], None] = None,
- **extra: Any,
- ):
- if example is not _Unset:
- warnings.warn(
- "`example` has been deprecated, please use `examples` instead",
- category=DeprecationWarning,
- stacklevel=4,
- )
- self.example = example
- self.include_in_schema = include_in_schema
- self.openapi_examples = openapi_examples
- kwargs = dict(
- default=default,
- default_factory=default_factory,
- alias=alias,
- title=title,
- description=description,
- gt=gt,
- ge=ge,
- lt=lt,
- le=le,
- min_length=min_length,
- max_length=max_length,
- discriminator=discriminator,
- multiple_of=multiple_of,
- allow_inf_nan=allow_inf_nan,
- max_digits=max_digits,
- decimal_places=decimal_places,
- **extra,
- )
- if examples is not None:
- kwargs["examples"] = examples
- if regex is not None:
- warnings.warn(
- "`regex` has been deprecated, please use `pattern` instead",
- category=DeprecationWarning,
- stacklevel=4,
- )
- current_json_schema_extra = json_schema_extra or extra
- kwargs["deprecated"] = deprecated
- kwargs["regex"] = pattern or regex
- kwargs.update(**current_json_schema_extra)
- use_kwargs = {k: v for k, v in kwargs.items() if v is not _Unset}
-
- super().__init__(**use_kwargs)
-
- def __repr__(self) -> str:
- return f"{self.__class__.__name__}({self.default})"
-
-
-class Path(Param):
- in_ = ParamTypes.path
-
- def __init__(
- self,
- default: Any = ...,
- *,
- default_factory: Union[Callable[[], Any], None] = _Unset,
- annotation: Optional[Any] = None,
- alias: Optional[str] = None,
- alias_priority: Union[int, None] = _Unset,
- # TODO: update when deprecating Pydantic v1, import these types
- # validation_alias: str | AliasPath | AliasChoices | None
- validation_alias: Union[str, None] = None,
- serialization_alias: Union[str, None] = None,
- title: Optional[str] = None,
- description: Optional[str] = None,
- gt: Optional[float] = None,
- ge: Optional[float] = None,
- lt: Optional[float] = None,
- le: Optional[float] = None,
- min_length: Optional[int] = None,
- max_length: Optional[int] = None,
- pattern: Optional[str] = None,
- regex: Annotated[
- Optional[str],
- deprecated(
- "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
- ),
- ] = None,
- discriminator: Union[str, None] = None,
- strict: Union[bool, None] = _Unset,
- multiple_of: Union[float, None] = _Unset,
- allow_inf_nan: Union[bool, None] = _Unset,
- max_digits: Union[int, None] = _Unset,
- decimal_places: Union[int, None] = _Unset,
- examples: Optional[list[Any]] = None,
- example: Annotated[
- Optional[Any],
- deprecated(
- "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
- "although still supported. Use examples instead."
- ),
- ] = _Unset,
- openapi_examples: Optional[dict[str, Example]] = None,
- deprecated: Union[deprecated, str, bool, None] = None,
- include_in_schema: bool = True,
- json_schema_extra: Union[dict[str, Any], None] = None,
- **extra: Any,
- ):
- assert default is ..., "Path parameters cannot have a default value"
- self.in_ = self.in_
- super().__init__(
- default=default,
- default_factory=default_factory,
- annotation=annotation,
- alias=alias,
- alias_priority=alias_priority,
- validation_alias=validation_alias,
- serialization_alias=serialization_alias,
- title=title,
- description=description,
- gt=gt,
- ge=ge,
- lt=lt,
- le=le,
- min_length=min_length,
- max_length=max_length,
- pattern=pattern,
- regex=regex,
- discriminator=discriminator,
- strict=strict,
- multiple_of=multiple_of,
- allow_inf_nan=allow_inf_nan,
- max_digits=max_digits,
- decimal_places=decimal_places,
- deprecated=deprecated,
- example=example,
- examples=examples,
- openapi_examples=openapi_examples,
- include_in_schema=include_in_schema,
- json_schema_extra=json_schema_extra,
- **extra,
- )
-
-
-class Query(Param):
- in_ = ParamTypes.query
-
- def __init__(
- self,
- default: Any = Undefined,
- *,
- default_factory: Union[Callable[[], Any], None] = _Unset,
- annotation: Optional[Any] = None,
- alias: Optional[str] = None,
- alias_priority: Union[int, None] = _Unset,
- # TODO: update when deprecating Pydantic v1, import these types
- # validation_alias: str | AliasPath | AliasChoices | None
- validation_alias: Union[str, None] = None,
- serialization_alias: Union[str, None] = None,
- title: Optional[str] = None,
- description: Optional[str] = None,
- gt: Optional[float] = None,
- ge: Optional[float] = None,
- lt: Optional[float] = None,
- le: Optional[float] = None,
- min_length: Optional[int] = None,
- max_length: Optional[int] = None,
- pattern: Optional[str] = None,
- regex: Annotated[
- Optional[str],
- deprecated(
- "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
- ),
- ] = None,
- discriminator: Union[str, None] = None,
- strict: Union[bool, None] = _Unset,
- multiple_of: Union[float, None] = _Unset,
- allow_inf_nan: Union[bool, None] = _Unset,
- max_digits: Union[int, None] = _Unset,
- decimal_places: Union[int, None] = _Unset,
- examples: Optional[list[Any]] = None,
- example: Annotated[
- Optional[Any],
- deprecated(
- "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
- "although still supported. Use examples instead."
- ),
- ] = _Unset,
- openapi_examples: Optional[dict[str, Example]] = None,
- deprecated: Union[deprecated, str, bool, None] = None,
- include_in_schema: bool = True,
- json_schema_extra: Union[dict[str, Any], None] = None,
- **extra: Any,
- ):
- super().__init__(
- default=default,
- default_factory=default_factory,
- annotation=annotation,
- alias=alias,
- alias_priority=alias_priority,
- validation_alias=validation_alias,
- serialization_alias=serialization_alias,
- title=title,
- description=description,
- gt=gt,
- ge=ge,
- lt=lt,
- le=le,
- min_length=min_length,
- max_length=max_length,
- pattern=pattern,
- regex=regex,
- discriminator=discriminator,
- strict=strict,
- multiple_of=multiple_of,
- allow_inf_nan=allow_inf_nan,
- max_digits=max_digits,
- decimal_places=decimal_places,
- deprecated=deprecated,
- example=example,
- examples=examples,
- openapi_examples=openapi_examples,
- include_in_schema=include_in_schema,
- json_schema_extra=json_schema_extra,
- **extra,
- )
-
-
-class Header(Param):
- in_ = ParamTypes.header
-
- def __init__(
- self,
- default: Any = Undefined,
- *,
- default_factory: Union[Callable[[], Any], None] = _Unset,
- annotation: Optional[Any] = None,
- alias: Optional[str] = None,
- alias_priority: Union[int, None] = _Unset,
- # TODO: update when deprecating Pydantic v1, import these types
- # validation_alias: str | AliasPath | AliasChoices | None
- validation_alias: Union[str, None] = None,
- serialization_alias: Union[str, None] = None,
- convert_underscores: bool = True,
- title: Optional[str] = None,
- description: Optional[str] = None,
- gt: Optional[float] = None,
- ge: Optional[float] = None,
- lt: Optional[float] = None,
- le: Optional[float] = None,
- min_length: Optional[int] = None,
- max_length: Optional[int] = None,
- pattern: Optional[str] = None,
- regex: Annotated[
- Optional[str],
- deprecated(
- "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
- ),
- ] = None,
- discriminator: Union[str, None] = None,
- strict: Union[bool, None] = _Unset,
- multiple_of: Union[float, None] = _Unset,
- allow_inf_nan: Union[bool, None] = _Unset,
- max_digits: Union[int, None] = _Unset,
- decimal_places: Union[int, None] = _Unset,
- examples: Optional[list[Any]] = None,
- example: Annotated[
- Optional[Any],
- deprecated(
- "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
- "although still supported. Use examples instead."
- ),
- ] = _Unset,
- openapi_examples: Optional[dict[str, Example]] = None,
- deprecated: Union[deprecated, str, bool, None] = None,
- include_in_schema: bool = True,
- json_schema_extra: Union[dict[str, Any], None] = None,
- **extra: Any,
- ):
- self.convert_underscores = convert_underscores
- super().__init__(
- default=default,
- default_factory=default_factory,
- annotation=annotation,
- alias=alias,
- alias_priority=alias_priority,
- validation_alias=validation_alias,
- serialization_alias=serialization_alias,
- title=title,
- description=description,
- gt=gt,
- ge=ge,
- lt=lt,
- le=le,
- min_length=min_length,
- max_length=max_length,
- pattern=pattern,
- regex=regex,
- discriminator=discriminator,
- strict=strict,
- multiple_of=multiple_of,
- allow_inf_nan=allow_inf_nan,
- max_digits=max_digits,
- decimal_places=decimal_places,
- deprecated=deprecated,
- example=example,
- examples=examples,
- openapi_examples=openapi_examples,
- include_in_schema=include_in_schema,
- json_schema_extra=json_schema_extra,
- **extra,
- )
-
-
-class Cookie(Param):
- in_ = ParamTypes.cookie
-
- def __init__(
- self,
- default: Any = Undefined,
- *,
- default_factory: Union[Callable[[], Any], None] = _Unset,
- annotation: Optional[Any] = None,
- alias: Optional[str] = None,
- alias_priority: Union[int, None] = _Unset,
- # TODO: update when deprecating Pydantic v1, import these types
- # validation_alias: str | AliasPath | AliasChoices | None
- validation_alias: Union[str, None] = None,
- serialization_alias: Union[str, None] = None,
- title: Optional[str] = None,
- description: Optional[str] = None,
- gt: Optional[float] = None,
- ge: Optional[float] = None,
- lt: Optional[float] = None,
- le: Optional[float] = None,
- min_length: Optional[int] = None,
- max_length: Optional[int] = None,
- pattern: Optional[str] = None,
- regex: Annotated[
- Optional[str],
- deprecated(
- "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
- ),
- ] = None,
- discriminator: Union[str, None] = None,
- strict: Union[bool, None] = _Unset,
- multiple_of: Union[float, None] = _Unset,
- allow_inf_nan: Union[bool, None] = _Unset,
- max_digits: Union[int, None] = _Unset,
- decimal_places: Union[int, None] = _Unset,
- examples: Optional[list[Any]] = None,
- example: Annotated[
- Optional[Any],
- deprecated(
- "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
- "although still supported. Use examples instead."
- ),
- ] = _Unset,
- openapi_examples: Optional[dict[str, Example]] = None,
- deprecated: Union[deprecated, str, bool, None] = None,
- include_in_schema: bool = True,
- json_schema_extra: Union[dict[str, Any], None] = None,
- **extra: Any,
- ):
- super().__init__(
- default=default,
- default_factory=default_factory,
- annotation=annotation,
- alias=alias,
- alias_priority=alias_priority,
- validation_alias=validation_alias,
- serialization_alias=serialization_alias,
- title=title,
- description=description,
- gt=gt,
- ge=ge,
- lt=lt,
- le=le,
- min_length=min_length,
- max_length=max_length,
- pattern=pattern,
- regex=regex,
- discriminator=discriminator,
- strict=strict,
- multiple_of=multiple_of,
- allow_inf_nan=allow_inf_nan,
- max_digits=max_digits,
- decimal_places=decimal_places,
- deprecated=deprecated,
- example=example,
- examples=examples,
- openapi_examples=openapi_examples,
- include_in_schema=include_in_schema,
- json_schema_extra=json_schema_extra,
- **extra,
- )
-
-
-class Body(FieldInfo):
- def __init__(
- self,
- default: Any = Undefined,
- *,
- default_factory: Union[Callable[[], Any], None] = _Unset,
- annotation: Optional[Any] = None,
- embed: Union[bool, None] = None,
- media_type: str = "application/json",
- alias: Optional[str] = None,
- alias_priority: Union[int, None] = _Unset,
- # TODO: update when deprecating Pydantic v1, import these types
- # validation_alias: str | AliasPath | AliasChoices | None
- validation_alias: Union[str, None] = None,
- serialization_alias: Union[str, None] = None,
- title: Optional[str] = None,
- description: Optional[str] = None,
- gt: Optional[float] = None,
- ge: Optional[float] = None,
- lt: Optional[float] = None,
- le: Optional[float] = None,
- min_length: Optional[int] = None,
- max_length: Optional[int] = None,
- pattern: Optional[str] = None,
- regex: Annotated[
- Optional[str],
- deprecated(
- "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
- ),
- ] = None,
- discriminator: Union[str, None] = None,
- strict: Union[bool, None] = _Unset,
- multiple_of: Union[float, None] = _Unset,
- allow_inf_nan: Union[bool, None] = _Unset,
- max_digits: Union[int, None] = _Unset,
- decimal_places: Union[int, None] = _Unset,
- examples: Optional[list[Any]] = None,
- example: Annotated[
- Optional[Any],
- deprecated(
- "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
- "although still supported. Use examples instead."
- ),
- ] = _Unset,
- openapi_examples: Optional[dict[str, Example]] = None,
- deprecated: Union[deprecated, str, bool, None] = None,
- include_in_schema: bool = True,
- json_schema_extra: Union[dict[str, Any], None] = None,
- **extra: Any,
- ):
- self.embed = embed
- self.media_type = media_type
- if example is not _Unset:
- warnings.warn(
- "`example` has been deprecated, please use `examples` instead",
- category=DeprecationWarning,
- stacklevel=4,
- )
- self.example = example
- self.include_in_schema = include_in_schema
- self.openapi_examples = openapi_examples
- kwargs = dict(
- default=default,
- default_factory=default_factory,
- alias=alias,
- title=title,
- description=description,
- gt=gt,
- ge=ge,
- lt=lt,
- le=le,
- min_length=min_length,
- max_length=max_length,
- discriminator=discriminator,
- multiple_of=multiple_of,
- allow_inf_nan=allow_inf_nan,
- max_digits=max_digits,
- decimal_places=decimal_places,
- **extra,
- )
- if examples is not None:
- kwargs["examples"] = examples
- if regex is not None:
- warnings.warn(
- "`regex` has been deprecated, please use `pattern` instead",
- category=DeprecationWarning,
- stacklevel=4,
- )
- current_json_schema_extra = json_schema_extra or extra
- kwargs["deprecated"] = deprecated
- kwargs["regex"] = pattern or regex
- kwargs.update(**current_json_schema_extra)
-
- use_kwargs = {k: v for k, v in kwargs.items() if v is not _Unset}
-
- super().__init__(**use_kwargs)
-
- def __repr__(self) -> str:
- return f"{self.__class__.__name__}({self.default})"
-
-
-class Form(Body):
- def __init__(
- self,
- default: Any = Undefined,
- *,
- default_factory: Union[Callable[[], Any], None] = _Unset,
- annotation: Optional[Any] = None,
- media_type: str = "application/x-www-form-urlencoded",
- alias: Optional[str] = None,
- alias_priority: Union[int, None] = _Unset,
- # TODO: update when deprecating Pydantic v1, import these types
- # validation_alias: str | AliasPath | AliasChoices | None
- validation_alias: Union[str, None] = None,
- serialization_alias: Union[str, None] = None,
- title: Optional[str] = None,
- description: Optional[str] = None,
- gt: Optional[float] = None,
- ge: Optional[float] = None,
- lt: Optional[float] = None,
- le: Optional[float] = None,
- min_length: Optional[int] = None,
- max_length: Optional[int] = None,
- pattern: Optional[str] = None,
- regex: Annotated[
- Optional[str],
- deprecated(
- "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
- ),
- ] = None,
- discriminator: Union[str, None] = None,
- strict: Union[bool, None] = _Unset,
- multiple_of: Union[float, None] = _Unset,
- allow_inf_nan: Union[bool, None] = _Unset,
- max_digits: Union[int, None] = _Unset,
- decimal_places: Union[int, None] = _Unset,
- examples: Optional[list[Any]] = None,
- example: Annotated[
- Optional[Any],
- deprecated(
- "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
- "although still supported. Use examples instead."
- ),
- ] = _Unset,
- openapi_examples: Optional[dict[str, Example]] = None,
- deprecated: Union[deprecated, str, bool, None] = None,
- include_in_schema: bool = True,
- json_schema_extra: Union[dict[str, Any], None] = None,
- **extra: Any,
- ):
- super().__init__(
- default=default,
- default_factory=default_factory,
- annotation=annotation,
- media_type=media_type,
- alias=alias,
- alias_priority=alias_priority,
- validation_alias=validation_alias,
- serialization_alias=serialization_alias,
- title=title,
- description=description,
- gt=gt,
- ge=ge,
- lt=lt,
- le=le,
- min_length=min_length,
- max_length=max_length,
- pattern=pattern,
- regex=regex,
- discriminator=discriminator,
- strict=strict,
- multiple_of=multiple_of,
- allow_inf_nan=allow_inf_nan,
- max_digits=max_digits,
- decimal_places=decimal_places,
- deprecated=deprecated,
- example=example,
- examples=examples,
- openapi_examples=openapi_examples,
- include_in_schema=include_in_schema,
- json_schema_extra=json_schema_extra,
- **extra,
- )
-
-
-class File(Form):
- def __init__(
- self,
- default: Any = Undefined,
- *,
- default_factory: Union[Callable[[], Any], None] = _Unset,
- annotation: Optional[Any] = None,
- media_type: str = "multipart/form-data",
- alias: Optional[str] = None,
- alias_priority: Union[int, None] = _Unset,
- # TODO: update when deprecating Pydantic v1, import these types
- # validation_alias: str | AliasPath | AliasChoices | None
- validation_alias: Union[str, None] = None,
- serialization_alias: Union[str, None] = None,
- title: Optional[str] = None,
- description: Optional[str] = None,
- gt: Optional[float] = None,
- ge: Optional[float] = None,
- lt: Optional[float] = None,
- le: Optional[float] = None,
- min_length: Optional[int] = None,
- max_length: Optional[int] = None,
- pattern: Optional[str] = None,
- regex: Annotated[
- Optional[str],
- deprecated(
- "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
- ),
- ] = None,
- discriminator: Union[str, None] = None,
- strict: Union[bool, None] = _Unset,
- multiple_of: Union[float, None] = _Unset,
- allow_inf_nan: Union[bool, None] = _Unset,
- max_digits: Union[int, None] = _Unset,
- decimal_places: Union[int, None] = _Unset,
- examples: Optional[list[Any]] = None,
- example: Annotated[
- Optional[Any],
- deprecated(
- "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
- "although still supported. Use examples instead."
- ),
- ] = _Unset,
- openapi_examples: Optional[dict[str, Example]] = None,
- deprecated: Union[deprecated, str, bool, None] = None,
- include_in_schema: bool = True,
- json_schema_extra: Union[dict[str, Any], None] = None,
- **extra: Any,
- ):
- super().__init__(
- default=default,
- default_factory=default_factory,
- annotation=annotation,
- media_type=media_type,
- alias=alias,
- alias_priority=alias_priority,
- validation_alias=validation_alias,
- serialization_alias=serialization_alias,
- title=title,
- description=description,
- gt=gt,
- ge=ge,
- lt=lt,
- le=le,
- min_length=min_length,
- max_length=max_length,
- pattern=pattern,
- regex=regex,
- discriminator=discriminator,
- strict=strict,
- multiple_of=multiple_of,
- allow_inf_nan=allow_inf_nan,
- max_digits=max_digits,
- decimal_places=decimal_places,
- deprecated=deprecated,
- example=example,
- examples=examples,
- openapi_examples=openapi_examples,
- include_in_schema=include_in_schema,
- json_schema_extra=json_schema_extra,
- **extra,
- )
diff --git a/fastapi/utils.py b/fastapi/utils.py
index c4631d7ed..78fdcbb5b 100644
--- a/fastapi/utils.py
+++ b/fastapi/utils.py
@@ -6,7 +6,6 @@ from typing import (
Any,
Optional,
Union,
- cast,
)
from weakref import WeakKeyDictionary
@@ -19,10 +18,9 @@ from fastapi._compat import (
UndefinedType,
Validator,
annotation_is_pydantic_v1,
- lenient_issubclass,
- may_v1,
)
from fastapi.datastructures import DefaultPlaceholder, DefaultType
+from fastapi.exceptions import FastAPIDeprecationWarning, PydanticV1NotSupportedError
from pydantic import BaseModel
from pydantic.fields import FieldInfo
from typing_extensions import Literal
@@ -82,52 +80,18 @@ def create_model_field(
mode: Literal["validation", "serialization"] = "validation",
version: Literal["1", "auto"] = "auto",
) -> ModelField:
+ if annotation_is_pydantic_v1(type_):
+ raise PydanticV1NotSupportedError(
+ "pydantic.v1 models are no longer supported by FastAPI."
+ f" Please update the response model {type_!r}."
+ )
class_validators = class_validators or {}
- v1_model_config = may_v1.BaseConfig
- v1_field_info = field_info or may_v1.FieldInfo()
- v1_kwargs = {
- "name": name,
- "field_info": v1_field_info,
- "type_": type_,
- "class_validators": class_validators,
- "default": default,
- "required": required,
- "model_config": v1_model_config,
- "alias": alias,
- }
-
- if (
- annotation_is_pydantic_v1(type_)
- or isinstance(field_info, may_v1.FieldInfo)
- or version == "1"
- ):
- from fastapi._compat import v1
-
- try:
- return v1.ModelField(**v1_kwargs) # type: ignore[return-value]
- except RuntimeError:
- raise fastapi.exceptions.FastAPIError(
- _invalid_args_message.format(type_=type_)
- ) from None
- else:
- field_info = field_info or FieldInfo(
- annotation=type_, default=default, alias=alias
- )
- kwargs = {"mode": mode, "name": name, "field_info": field_info}
- try:
- return v2.ModelField(**kwargs) # type: ignore[return-value,arg-type]
- except PydanticSchemaGenerationError:
- raise fastapi.exceptions.FastAPIError(
- _invalid_args_message.format(type_=type_)
- ) from None
- # Pydantic v2 is not installed, but it's not a Pydantic v1 ModelField, it could be
- # a Pydantic v1 type, like a constrained int
- from fastapi._compat import v1
-
+ field_info = field_info or FieldInfo(annotation=type_, default=default, alias=alias)
+ kwargs = {"mode": mode, "name": name, "field_info": field_info}
try:
- return v1.ModelField(**v1_kwargs)
- except RuntimeError:
+ return v2.ModelField(**kwargs) # type: ignore[return-value,arg-type]
+ except PydanticSchemaGenerationError:
raise fastapi.exceptions.FastAPIError(
_invalid_args_message.format(type_=type_)
) from None
@@ -138,66 +102,16 @@ def create_cloned_field(
*,
cloned_types: Optional[MutableMapping[type[BaseModel], type[BaseModel]]] = None,
) -> ModelField:
- if isinstance(field, v2.ModelField):
- return field
-
- from fastapi._compat import v1
-
- # cloned_types caches already cloned types to support recursive models and improve
- # performance by avoiding unnecessary cloning
- if cloned_types is None:
- cloned_types = _CLONED_TYPES_CACHE
-
- original_type = field.type_
- use_type = original_type
- if lenient_issubclass(original_type, v1.BaseModel):
- original_type = cast(type[v1.BaseModel], original_type)
- use_type = cloned_types.get(original_type)
- if use_type is None:
- use_type = v1.create_model(original_type.__name__, __base__=original_type)
- cloned_types[original_type] = use_type
- for f in original_type.__fields__.values():
- use_type.__fields__[f.name] = create_cloned_field(
- f,
- cloned_types=cloned_types,
- )
- new_field = create_model_field(name=field.name, type_=use_type, version="1")
- new_field.has_alias = field.has_alias # type: ignore[attr-defined]
- new_field.alias = field.alias # type: ignore[misc]
- new_field.class_validators = field.class_validators # type: ignore[attr-defined]
- new_field.default = field.default # type: ignore[misc]
- new_field.default_factory = field.default_factory # type: ignore[attr-defined]
- new_field.required = field.required # type: ignore[misc]
- new_field.model_config = field.model_config # type: ignore[attr-defined]
- new_field.field_info = field.field_info
- new_field.allow_none = field.allow_none # type: ignore[attr-defined]
- new_field.validate_always = field.validate_always # type: ignore[attr-defined]
- if field.sub_fields: # type: ignore[attr-defined]
- new_field.sub_fields = [ # type: ignore[attr-defined]
- create_cloned_field(sub_field, cloned_types=cloned_types)
- for sub_field in field.sub_fields # type: ignore[attr-defined]
- ]
- if field.key_field: # type: ignore[attr-defined]
- new_field.key_field = create_cloned_field( # type: ignore[attr-defined]
- field.key_field, # type: ignore[attr-defined]
- cloned_types=cloned_types,
- )
- new_field.validators = field.validators # type: ignore[attr-defined]
- new_field.pre_validators = field.pre_validators # type: ignore[attr-defined]
- new_field.post_validators = field.post_validators # type: ignore[attr-defined]
- new_field.parse_json = field.parse_json # type: ignore[attr-defined]
- new_field.shape = field.shape # type: ignore[attr-defined]
- new_field.populate_validators() # type: ignore[attr-defined]
- return new_field
+ return field
def generate_operation_id_for_path(
*, name: str, path: str, method: str
) -> str: # pragma: nocover
warnings.warn(
- "fastapi.utils.generate_operation_id_for_path() was deprecated, "
+ message="fastapi.utils.generate_operation_id_for_path() was deprecated, "
"it is not used internally, and will be removed soon",
- DeprecationWarning,
+ category=FastAPIDeprecationWarning,
stacklevel=2,
)
operation_id = f"{name}{path}"
diff --git a/pyproject.toml b/pyproject.toml
index ae97cb71b..9c2c35a9f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -196,6 +196,25 @@ dynamic_context = "test_function"
omit = [
"docs_src/response_model/tutorial003_04_py39.py",
"docs_src/response_model/tutorial003_04_py310.py",
+ "docs_src/dependencies/tutorial008_an_py39.py", # difficult to mock
+ "docs_src/dependencies/tutorial013_an_py310.py", # temporary code example?
+ "docs_src/dependencies/tutorial014_an_py310.py", # temporary code example?
+ # Pydantic v1 migration, no longer tested
+ "docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py",
+ "docs_src/pydantic_v1_in_v2/tutorial001_an_py39.py",
+ "docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py",
+ "docs_src/pydantic_v1_in_v2/tutorial002_an_py39.py",
+ "docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py",
+ "docs_src/pydantic_v1_in_v2/tutorial003_an_py39.py",
+ "docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py",
+ "docs_src/pydantic_v1_in_v2/tutorial004_an_py39.py",
+ # TODO: remove when removing this file, after updating translations, Pydantic v1
+ "docs_src/schema_extra_example/tutorial001_pv1_py310.py",
+ "docs_src/schema_extra_example/tutorial001_pv1_py39.py",
+ "docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py",
+ "docs_src/settings/app03_py39/config_pv1.py",
+ "docs_src/settings/app03_an_py39/config_pv1.py",
+ "docs_src/settings/tutorial001_pv1_py39.py",
]
[tool.coverage.report]
diff --git a/requirements-tests.txt b/requirements-tests.txt
index ee188b496..1604a2858 100644
--- a/requirements-tests.txt
+++ b/requirements-tests.txt
@@ -6,6 +6,7 @@ mypy ==1.14.1
dirty-equals ==0.9.0
sqlmodel==0.0.27
flask >=1.1.2,<4.0.0
+strawberry-graphql >=0.200.0,< 1.0.0
anyio[trio] >=3.2.1,<5.0.0
PyJWT==2.9.0
pyyaml >=5.3.1,<7.0.0
diff --git a/scripts/general-llm-prompt.md b/scripts/general-llm-prompt.md
new file mode 100644
index 000000000..d45ab8eb0
--- /dev/null
+++ b/scripts/general-llm-prompt.md
@@ -0,0 +1,528 @@
+### Your task
+
+Translate an English original content to a target language.
+
+The original content is written in Markdown, write the translation in Markdown as well.
+
+The original content will be surrounded by triple percentage signs (%%%). Do not include the triple percentage signs in the translation.
+
+### Technical terms in English
+
+For technical terms in English that don't have a common translation term, use the original term in English.
+
+### Content of code snippets
+
+Do not translate the content of code snippets, keep the original in English. For example, `list`, `dict`, keep them as is.
+
+### Content of code blocks
+
+Do not translate the content of code blocks, except for comments in the language which the code block uses.
+
+Examples:
+
+Source (English) - The code block is a bash code example with one comment:
+
+```bash
+# Print greeting
+echo "Hello, World!"
+```
+
+Result (German):
+
+```bash
+# Gruß ausgeben
+echo "Hello, World!"
+```
+
+Source (English) - The code block is a console example containing HTML tags. No comments, so nothing to change here:
+
+```console
+$ fastapi run main.py
+ FastAPI Starting server
+ Searching for package file structure
+```
+
+Result (German):
+
+```console
+$ fastapi run main.py
+ FastAPI Starting server
+ Searching for package file structure
+```
+
+Source (English) - The code block is a console example containing 5 comments:
+
+
+```console
+// Go to the home directory
+$ cd
+// Create a directory for all your code projects
+$ mkdir code
+// Enter into that code directory
+$ cd code
+// Create a directory for this project
+$ mkdir awesome-project
+// Enter into that project directory
+$ cd awesome-project
+```
+
+Result (German):
+
+```console
+// Gehe zum Home-Verzeichnis
+$ cd
+// Erstelle ein Verzeichnis für alle Ihre Code-Projekte
+$ mkdir code
+// Gehe in dieses Code-Verzeichnis
+$ cd code
+// Erstelle ein Verzeichnis für dieses Projekt
+$ mkdir awesome-project
+// Gehe in dieses Projektverzeichnis
+$ cd awesome-project
+```
+
+If there is an existing translation and its Mermaid diagram is in sync with the Mermaid diagram in the English source, except a few translated words, then use the Mermaid diagram of the existing translation. The human editor of the translation translated these words in the Mermaid diagram. Keep these translations, do not revert them back to the English source.
+
+Example:
+
+Source (English):
+
+```mermaid
+flowchart LR
+ subgraph global[global env]
+ harry-1[harry v1]
+ end
+ subgraph stone-project[philosophers-stone project]
+ stone(philosophers-stone) -->|requires| harry-1
+ end
+```
+
+Existing translation (German) - has three translations:
+
+```mermaid
+flowchart LR
+ subgraph global[globale Umgebung]
+ harry-1[harry v1]
+ end
+ subgraph stone-project[philosophers-stone-Projekt]
+ stone(philosophers-stone) -->|benötigt| harry-1
+ end
+```
+
+Result (German) - you change nothing:
+
+```mermaid
+flowchart LR
+ subgraph global[globale Umgebung]
+ harry-1[harry v1]
+ end
+ subgraph stone-project[philosophers-stone-Projekt]
+ stone(philosophers-stone) -->|benötigt| harry-1
+ end
+```
+
+### Special blocks
+
+There are special blocks of notes, tips and others that look like:
+
+/// note
+Here goes a note
+///
+
+To translate it, keep the same line and add the translation after a vertical bar.
+
+For example, if you were translating to Spanish, you would write:
+
+/// note | Nota
+
+Some examples in Spanish:
+
+Source (English):
+
+/// tip
+
+Result (Spanish):
+
+/// tip | Consejo
+
+Source (English):
+
+/// details | Preview
+
+Result (Spanish):
+
+/// details | Vista previa
+
+### Tab blocks
+
+There are special blocks surrounded by four slashes (////). They mark text, which will be rendered as part of a tab in the final document. The scheme is:
+
+//// tab | {tab title}
+{tab content, may span many lines}
+////
+
+Keep everything before the vertical bar (|) as is, including the vertical bar. Translate the tab title. Translate the tab content, applying the rules you know. Keep the four block closing slashes as is.
+
+Examples:
+
+Source (English):
+
+//// tab | Python 3.8+ non-Annotated
+Hello
+////
+
+Result (German):
+
+//// tab | Python 3.8+ nicht annotiert
+Hallo
+////
+
+Source (English) - Here there is nothing to translate in the tab title:
+
+//// tab | Linux, macOS, Windows Bash
+Hello again
+////
+
+Result (German):
+
+//// tab | Linux, macOS, Windows Bash
+Hallo wieder
+////
+
+### Headings
+
+Every Markdown heading in the English text (all levels) ends with a part inside curly brackets. This part denotes the hash of this heading, which is used in links to this heading. In translations, translate the heading, but do not translate this hash part, so that links do not break.
+
+Examples of how to translate a heading:
+
+Source (English):
+
+```
+## Alternative API docs { #alternative-api-docs }
+```
+
+Result (Spanish):
+
+```
+## Documentación de la API alternativa { #alternative-api-docs }
+```
+
+Source (English):
+
+```
+### Example { #example }
+```
+
+Result (German):
+
+```
+### Beispiel { #example }
+```
+
+### Links
+
+Use the following rules for links (apply both to Markdown-style links ([text](url)) and to HTML-style text tags):
+
+- For relative URLs, only translate the link text. Do not translate the URL or its parts.
+
+Example:
+
+Source (English):
+
+```
+[One of the fastest Python frameworks available](#performance)
+```
+
+Result (German):
+
+```
+[Eines der schnellsten verfügbaren Python-Frameworks](#performance)
+```
+
+- For absolute URLs which DO NOT start EXACTLY with https://fastapi.tiangolo.com, only translate the link text and leave the URL unchanged.
+
+Example:
+
+Source (English):
+
+```
+SQLModel docs
+```
+
+Result (German):
+
+```
+SQLModel-Dokumentation
+```
+
+- For absolute URLs which DO start EXACTLY with https://fastapi.tiangolo.com, only translate the link text and change the URL by adding the language code (https://fastapi.tiangolo.com/{language_code}[rest part of the url]).
+
+Example:
+
+Source (English):
+
+```
+Documentation
+```
+
+Result (Spanish):
+
+```
+Documentación
+```
+
+- Do not add language codes for URLs that point to static assets (e.g., images, CSS, JavaScript).
+
+Example:
+
+Source (English):
+
+```
+Something
+```
+
+Result (Spanish):
+
+```
+Algo
+```
+
+- For internal links, only translate link text.
+
+Example:
+
+Source (English):
+
+```
+[Create Pull Requests](help-fastapi.md#create-a-pull-request){.internal-link target=_blank}
+```
+
+Result (German):
+
+```
+[Pull Requests erzeugen](help-fastapi.md#create-a-pull-request){.internal-link target=_blank}
+```
+
+- Do not translate anchor fragments in links (the part after `#`), as they must remain the same to work correctly.
+
+- If an existing translation has a link with an anchor fragment different to the anchor fragment in the English source, then this is an error. Fix this by using the anchor fragment of the English source.
+
+Example:
+
+Source (English):
+
+```
+[Body - Multiple Parameters: Singular values in body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}
+```
+
+Existing wrong translation (German) - notice the wrongly translated anchor fragment:
+
+```
+[Body - Mehrere Parameter: Einfache Werte im Body](body-multiple-params.md#einzelne-werte-im-body){.internal-link target=_blank}.
+```
+
+Result (German) - you fix the anchor fragment:
+
+```
+[Body - Mehrere Parameter: Einfache Werte im Body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}.
+```
+
+- Do not add anchor fragments at will, even if this makes sense. If the English source has no anchor, don't add one.
+
+Example:
+
+Source (English):
+
+```
+Create a [virtual environment](../virtual-environments.md){.internal-link target=_blank}
+```
+
+Wrong translation in German - Anchor added to the URL.
+
+```
+Erstelle eine [virtuelle Umgebung](../virtual-environments.md#create-a-virtual-environment){.internal-link target=_blank}
+```
+
+Good translation (German) - URL stays like in the English source.
+
+```
+Erstelle eine [Virtuelle Umgebung](../virtual-environments.md){.internal-link target=_blank}
+```
+
+### HTML abbr elements
+
+Translate HTML abbr elements (`text`) as follows:
+
+- If the text surrounded by the abbr element is an abbreviation (the text may be surrounded by further HTML or Markdown markup or quotes, for example 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 6ebd24f54..ffecfde07 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()
@@ -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/test_general_performance.py b/tests/benchmarks/test_general_performance.py
index 2da74b95c..87add6d17 100644
--- a/tests/benchmarks/test_general_performance.py
+++ b/tests/benchmarks/test_general_performance.py
@@ -1,12 +1,12 @@
import json
import sys
-import warnings
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(
@@ -46,148 +46,143 @@ def dep_b(a: Annotated[int, Depends(dep_a)]):
return a + 2
-@pytest.fixture(
- scope="module",
- params=[
- "pydantic-v2",
- "pydantic-v1",
- ],
-)
-def basemodel_class(request: pytest.FixtureRequest) -> type[Any]:
- if request.param == "pydantic-v2":
- from pydantic import BaseModel
+class ItemIn(BaseModel):
+ name: str
+ value: int
- return BaseModel
- else:
- from pydantic.v1 import BaseModel
- return BaseModel
+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 app(basemodel_class: type[Any]) -> FastAPI:
- class ItemIn(basemodel_class):
- name: str
- value: int
-
- class ItemOut(basemodel_class):
- name: str
- value: int
- dep: int
-
- class LargeIn(basemodel_class):
- items: list[dict[str, Any]]
- metadata: dict[str, Any]
-
- class LargeOut(basemodel_class):
- items: list[dict[str, Any]]
- metadata: dict[str, Any]
-
- app = FastAPI()
-
- with warnings.catch_warnings(record=True):
- warnings.filterwarnings(
- "ignore",
- message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*",
- category=DeprecationWarning,
- )
-
- @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)
-
- return app
-
-
-@pytest.fixture(scope="module")
-def client(app: FastAPI) -> Iterator[TestClient]:
+def client() -> Iterator[TestClient]:
with TestClient(app) as client:
yield client
diff --git a/tests/test_additional_properties_bool.py b/tests/test_additional_properties_bool.py
index 3756b7d7c..063297a3f 100644
--- a/tests/test_additional_properties_bool.py
+++ b/tests/test_additional_properties_bool.py
@@ -1,6 +1,5 @@
from typing import Union
-from dirty_equals import IsDict
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel, ConfigDict
@@ -52,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 2ad575455..376d7714e 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_annotated.py b/tests/test_annotated.py
index a9e7c78c9..39f6f83b2 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 8f1b0a18d..001586ff7 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_compat.py b/tests/test_compat.py
index 8d2071030..0b5600f8f 100644
--- a/tests/test_compat.py
+++ b/tests/test_compat.py
@@ -1,20 +1,16 @@
-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
+from .utils import needs_py310
def test_model_field_default_required():
@@ -26,35 +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)
-
-
-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()
@@ -165,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 2ac96993a..000000000
--- a/tests/test_compat_params_v1.py
+++ /dev/null
@@ -1,1059 +0,0 @@
-import sys
-import warnings
-from typing import Optional
-
-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 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()
-
-with warnings.catch_warnings(record=True):
- warnings.simplefilter("always")
-
- @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 test_client:
- test_client.cookies.set("session_id", "abc123")
- test_client.cookies.set("tracking_id", "1234567890abcdef")
- response = test_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 test_client:
- test_client.cookies.set("tracking_id", "short")
- response = test_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": {
- "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": {
- "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": {
- "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": {
- "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": {
- "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": {
- "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": {
- "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_datetime_custom_encoder.py b/tests/test_datetime_custom_encoder.py
index 56b6780f0..f154ede02 100644
--- a/tests/test_datetime_custom_encoder.py
+++ b/tests/test_datetime_custom_encoder.py
@@ -1,12 +1,9 @@
-import warnings
from datetime import datetime, timezone
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
-from .utils import needs_pydanticv1
-
def test_pydanticv2():
from pydantic import field_serializer
@@ -29,34 +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():
- from pydantic import v1
-
- class ModelWithDatetimeField(v1.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))
-
- with warnings.catch_warnings(record=True):
- warnings.simplefilter("always")
-
- @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 7c6717e2a..a8658e03b 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 154937fa0..e25db624d 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 bd16fe925..45734ec28 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 d6f2ce7d2..000000000
--- a/tests/test_filter_pydantic_sub_model/app_pv1.py
+++ /dev/null
@@ -1,45 +0,0 @@
-import warnings
-from typing import Optional
-
-from fastapi import Depends, FastAPI
-from pydantic.v1 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
- tags: dict[str, str] = {}
-
- @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")
-
-
-with warnings.catch_warnings(record=True):
- warnings.simplefilter("always")
-
- @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,
- "tags": {"key1": "value1", "key2": "value2"},
- }
diff --git a/tests/test_filter_pydantic_sub_model/test_filter_pydantic_sub_model_pv1.py b/tests/test_filter_pydantic_sub_model/test_filter_pydantic_sub_model_pv1.py
deleted file mode 100644
index b464b4f57..000000000
--- a/tests/test_filter_pydantic_sub_model/test_filter_pydantic_sub_model_pv1.py
+++ /dev/null
@@ -1,146 +0,0 @@
-import pytest
-from fastapi.exceptions import ResponseValidationError
-from fastapi.testclient import TestClient
-from inline_snapshot import snapshot
-
-from ..utils import needs_pydanticv1
-
-
-@pytest.fixture(name="client")
-def get_client():
- from .app_pv1 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_pydanticv1
-def test_filter_sub_model(client: TestClient):
- response = client.get("/model/modelA")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "name": "modelA",
- "description": "model-a-desc",
- "model_b": {"username": "test-user"},
- "tags": {"key1": "value1", "key2": "value2"},
- }
-
-
-@needs_pydanticv1
-def test_validator_is_cloned(client: TestClient):
- with pytest.raises(ResponseValidationError) as err:
- client.get("/model/modelX")
- assert err.value.errors() == [
- {
- "loc": ("response", "name"),
- "msg": "name must end in A",
- "type": "value_error",
- }
- ]
-
-
-@needs_pydanticv1
-def test_openapi_schema(client: TestClient):
- 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": {
- "/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"
- }
- }
- },
- },
- },
- }
- }
- },
- "components": {
- "schemas": {
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {
- "$ref": "#/components/schemas/ValidationError"
- },
- }
- },
- },
- "ModelA": {
- "title": "ModelA",
- "required": ["name", "model_b"],
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "description": {"title": "Description", "type": "string"},
- "model_b": {"$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"},
- },
- },
- }
- },
- }
- )
diff --git a/tests/test_filter_pydantic_sub_model_pv2.py b/tests/test_filter_pydantic_sub_model_pv2.py
index d70f53043..fc5876410 100644
--- a/tests/test_filter_pydantic_sub_model_pv2.py
+++ b/tests/test_filter_pydantic_sub_model_pv2.py
@@ -1,7 +1,7 @@
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
@@ -63,23 +63,13 @@ 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')")},
+ }
]
@@ -145,23 +135,14 @@ def test_openapi_schema(client: TestClient):
},
"ModelA": {
"title": "ModelA",
- "required": IsOneOf(
- ["name", "description", "foo"],
- # TODO remove when deprecating Pydantic v1
- ["name", "foo"],
- ),
+ "required": ["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"}),
+ "description": {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
"foo": {"$ref": "#/components/schemas/ModelB"},
"tags": {
"additionalProperties": {"type": "string"},
diff --git a/tests/test_forms_single_model.py b/tests/test_forms_single_model.py
index c401cc937..7d03d2957 100644
--- a/tests/test_forms_single_model.py
+++ b/tests/test_forms_single_model.py
@@ -1,6 +1,5 @@
from typing import Annotated, Optional
-from dirty_equals import IsDict
from fastapi import FastAPI, Form
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
@@ -79,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 dee595554..eb7939b69 100644
--- a/tests/test_get_model_definitions_formfeed_escape.py
+++ b/tests/test_get_model_definitions_formfeed_escape.py
@@ -1,25 +1,12 @@
-import warnings
-
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
-from .utils import needs_pydanticv1
-
-@pytest.fixture(
- name="client",
- params=[
- pytest.param("pydantic-v1", marks=needs_pydanticv1),
- "pydantic-v2",
- ],
-)
-def client_fixture(request: pytest.FixtureRequest) -> TestClient:
- if request.param == "pydantic-v1":
- from pydantic.v1 import BaseModel
- else:
- from pydantic import BaseModel
+@pytest.fixture(name="client")
+def client_fixture() -> TestClient:
+ from pydantic import BaseModel
class Address(BaseModel):
"""
@@ -38,28 +25,12 @@ def client_fixture(request: pytest.FixtureRequest) -> TestClient:
app = FastAPI()
- if request.param == "pydantic-v1":
- with warnings.catch_warnings(record=True):
- warnings.simplefilter("always")
-
- @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"
- ),
- )
- else:
-
- @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"
- ),
- )
+ @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
diff --git a/tests/test_infer_param_optionality.py b/tests/test_infer_param_optionality.py
index e3d57bb42..147018996 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 7f29fe33e..8cf8952f9 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
-
class MyUuid:
def __init__(self, uuid_string: str):
@@ -67,46 +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():
- from pydantic import v1
-
- 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(v1.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 81bf94ece..4528dff44 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 Undefined
from fastapi.encoders import jsonable_encoder
+from fastapi.exceptions import PydanticV1NotSupportedError
from pydantic import BaseModel, Field, ValidationError
-from .utils import needs_pydanticv1
-
class Person:
def __init__(self, name: str):
@@ -156,29 +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():
- from pydantic import v1
+def test_json_encoder_error_with_pydanticv1():
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", UserWarning)
+ from pydantic import v1
- class ModelWithCustomEncoder(v1.BaseModel):
- dt_field: datetime
+ class ModelV1(v1.BaseModel):
+ name: str
- class Config:
- json_encoders = {
- datetime: lambda dt: dt.replace(
- microsecond=0, tzinfo=timezone.utc
- ).isoformat()
- }
-
- 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():
@@ -214,25 +202,27 @@ def test_encode_model_with_default():
}
-@needs_pydanticv1
def test_custom_encoders():
- from pydantic import v1
-
class safe_datetime(datetime):
pass
- class MyModel(v1.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():
@@ -287,17 +277,6 @@ def test_encode_pure_path():
assert jsonable_encoder({"path": test_path}) == {"path": str(test_path)}
-@needs_pydanticv1
-def test_encode_root():
- from pydantic import v1
-
- class ModelWithRoot(v1.BaseModel):
- __root__: str
-
- model = ModelWithRoot(__root__="Foo")
- assert jsonable_encoder(model) == "Foo"
-
-
def test_decimal_encoder_float():
data = {"value": Decimal(1.23)}
assert jsonable_encoder(data) == {"value": 1.23}
diff --git a/tests/test_multi_body_errors.py b/tests/test_multi_body_errors.py
index 6ea405fe7..4418c77cb 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 7387a81dd..5df51ba18 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_openapi_examples.py b/tests/test_openapi_examples.py
index b3f83ae23..bd0d55452 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 dc7147c71..084cb695d 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_servers.py b/tests/test_openapi_servers.py
index 8697c8438..33079e4b1 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 19c2e8d69..670e4f5dd 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 09c1f13fb..47051b927 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_deprecation_warnings.py b/tests/test_pydantic_v1_deprecation_warnings.py
deleted file mode 100644
index e0008e218..000000000
--- a/tests/test_pydantic_v1_deprecation_warnings.py
+++ /dev/null
@@ -1,98 +0,0 @@
-import sys
-
-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._compat.v1 import BaseModel
-from fastapi.testclient import TestClient
-
-
-def test_warns_pydantic_v1_model_in_endpoint_param() -> None:
- class ParamModelV1(BaseModel):
- name: str
-
- app = FastAPI()
-
- with pytest.warns(
- DeprecationWarning,
- match=r"pydantic\.v1 is deprecated.*Please update the param data:",
- ):
-
- @app.post("/param")
- def endpoint(data: ParamModelV1):
- return data
-
- client = TestClient(app)
- response = client.post("/param", json={"name": "test"})
- assert response.status_code == 200, response.text
- assert response.json() == {"name": "test"}
-
-
-def test_warns_pydantic_v1_model_in_return_type() -> None:
- class ReturnModelV1(BaseModel):
- name: str
-
- app = FastAPI()
-
- with pytest.warns(
- DeprecationWarning,
- match=r"pydantic\.v1 is deprecated.*Please update the response model",
- ):
-
- @app.get("/return")
- def endpoint() -> ReturnModelV1:
- return ReturnModelV1(name="test")
-
- client = TestClient(app)
- response = client.get("/return")
- assert response.status_code == 200, response.text
- assert response.json() == {"name": "test"}
-
-
-def test_warns_pydantic_v1_model_in_response_model() -> None:
- class ResponseModelV1(BaseModel):
- name: str
-
- app = FastAPI()
-
- with pytest.warns(
- DeprecationWarning,
- match=r"pydantic\.v1 is deprecated.*Please update the response model",
- ):
-
- @app.get("/response-model", response_model=ResponseModelV1)
- def endpoint():
- return {"name": "test"}
-
- client = TestClient(app)
- response = client.get("/response-model")
- assert response.status_code == 200, response.text
- assert response.json() == {"name": "test"}
-
-
-def test_warns_pydantic_v1_model_in_additional_responses_model() -> None:
- class ErrorModelV1(BaseModel):
- detail: str
-
- app = FastAPI()
-
- with pytest.warns(
- DeprecationWarning,
- match=r"pydantic\.v1 is deprecated.*In responses=\{\}, please update",
- ):
-
- @app.get(
- "/responses", response_model=None, responses={400: {"model": ErrorModelV1}}
- )
- def endpoint():
- return {"ok": True}
-
- client = TestClient(app)
- response = client.get("/responses")
- assert response.status_code == 200, response.text
- assert response.json() == {"ok": True}
diff --git a/tests/test_pydantic_v1_error.py b/tests/test_pydantic_v1_error.py
new file mode 100644
index 000000000..13229a313
--- /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 4868e5d22..000000000
--- a/tests/test_pydantic_v1_v2_01.py
+++ /dev/null
@@ -1,439 +0,0 @@
-import sys
-import warnings
-from typing import Any, Union
-
-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._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()
-
-with warnings.catch_warnings(record=True):
- warnings.simplefilter("always")
-
- @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": {
- "allOf": [
- {"$ref": "#/components/schemas/SubItem"}
- ],
- "title": "Data",
- }
- }
- },
- "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": {
- "allOf": [
- {"$ref": "#/components/schemas/SubItem"}
- ],
- "title": "Data",
- }
- }
- },
- "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": {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "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-filter": {
- "post": {
- "summary": "Handle Item Filter",
- "operationId": "handle_item_filter_item_filter_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "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",
- },
- "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 108f231fa..000000000
--- a/tests/test_pydantic_v1_v2_list.py
+++ /dev/null
@@ -1,682 +0,0 @@
-import sys
-import warnings
-from typing import Any, Union
-
-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._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()
-
-
-with warnings.catch_warnings(record=True):
- warnings.simplefilter("always")
-
- @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": {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Data",
- }
- }
- },
- "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": {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Data",
- }
- }
- },
- "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 895835a4c..000000000
--- a/tests/test_pydantic_v1_v2_mixed.py
+++ /dev/null
@@ -1,1408 +0,0 @@
-import sys
-import warnings
-from typing import Any, Union
-
-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._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()
-
-with warnings.catch_warnings(record=True):
- warnings.simplefilter("always")
-
- @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": [
- {
- "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"},
- },
- ]
- }
- )
-
-
-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": [
- {
- "type": "missing",
- "loc": ["body", "new_sub", "new_sub_name"],
- "msg": "Field required",
- "input": {"wrong_field": "value"},
- }
- ]
- }
- )
-
-
-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": [
- {
- "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"},
- },
- ]
- }
- )
-
-
-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": [
- {
- "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"},
- },
- }
- ]
- }
- )
-
-
-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": {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "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"
- }
- }
- },
- },
- },
- }
- },
- "/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": {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "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": {
- "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": {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Data",
- }
- }
- },
- "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": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "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 4180ec3bf..000000000
--- a/tests/test_pydantic_v1_v2_multifile/main.py
+++ /dev/null
@@ -1,137 +0,0 @@
-import warnings
-
-from fastapi import FastAPI
-
-from . import modelsv1, modelsv2, modelsv2b
-
-app = FastAPI()
-
-with warnings.catch_warnings(record=True):
- warnings.simplefilter("always")
-
- @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]:
- item1 = data1[0]
- item2 = data2[0]
- return [
- modelsv1.ItemInList(name1=item1.name2),
- modelsv1.ItemInList(name1=item2.dup_name2),
- ]
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 0cc8de455..000000000
--- 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 d80b77e10..000000000
--- 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 e992bea2e..000000000
--- 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 32d901961..000000000
--- a/tests/test_pydantic_v1_v2_multifile/test_multifile.py
+++ /dev/null
@@ -1,951 +0,0 @@
-import sys
-
-from tests.utils import 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": {
- "allOf": [
- {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
- }
- ],
- "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": {
- "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/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input"
- },
- }
- },
- "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": {
- "allOf": [
- {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
- }
- ],
- "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 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": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input"
- },
- }
- },
- "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": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input"
- },
- "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": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input"
- },
- "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": {
- "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 ba98b5653..000000000
--- a/tests/test_pydantic_v1_v2_noneable.py
+++ /dev/null
@@ -1,692 +0,0 @@
-import sys
-import warnings
-from typing import Any, Union
-
-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._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()
-
-with warnings.catch_warnings(record=True):
- warnings.simplefilter("always")
-
- @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": [
- {
- "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"},
- },
- ]
- }
- )
-
-
-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": [
- {
- "type": "missing",
- "loc": ["body", "new_sub", "new_sub_name"],
- "msg": "Field required",
- "input": {"wrong_field": "value"},
- }
- ]
- }
- )
-
-
-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": [
- {
- "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",
- }
- ]
- }
- )
-
-
-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": {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "anyOf": [
- {
- "$ref": "#/components/schemas/NewItem"
- },
- {"type": "null"},
- ],
- "title": "Response Handle V1 Item To V2 V1 To V2 Post",
- }
- }
- },
- },
- "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": {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "anyOf": [
- {
- "$ref": "#/components/schemas/NewItem"
- },
- {"type": "null"},
- ],
- "title": "Response Handle V1 Item To V2 Filter V1 To V2 Item Filter Post",
- }
- }
- },
- },
- "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": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "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 57f551d2a..c25960cac 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_read_with_orm_mode.py b/tests/test_read_with_orm_mode.py
index a195634b8..cd7389252 100644
--- a/tests/test_read_with_orm_mode.py
+++ b/tests/test_read_with_orm_mode.py
@@ -1,12 +1,9 @@
-import warnings
from typing import Any
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel, ConfigDict
-from .utils import needs_pydanticv1
-
def test_read_with_orm_mode() -> None:
class PersonBase(BaseModel):
@@ -44,50 +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:
- from pydantic import v1
-
- class PersonBase(v1.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()
-
- with warnings.catch_warnings(record=True):
- warnings.simplefilter("always")
-
- @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 cfbff19c8..5b4daa450 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 7d9988f9f..d6eaa45fb 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_params/test_body/test_list.py b/tests/test_request_params/test_body/test_list.py
index 0048da0f8..970e6a660 100644
--- a/tests/test_request_params/test_body/test_list.py
+++ b/tests/test_request_params/test_body/test_list.py
@@ -1,7 +1,7 @@
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
@@ -59,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(
@@ -148,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(
@@ -181,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(
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 960e8890f..ba8ba9092 100644
--- a/tests/test_request_params/test_body/test_optional_list.py
+++ b/tests/test_request_params/test_body/test_optional_list.py
@@ -1,7 +1,6 @@
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
@@ -38,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():
@@ -75,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(
@@ -153,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():
@@ -194,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(
@@ -289,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():
@@ -330,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(
@@ -438,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():
@@ -479,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(
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 59732688a..b9c18034d 100644
--- a/tests/test_request_params/test_body/test_optional_str.py
+++ b/tests/test_request_params/test_body/test_optional_str.py
@@ -1,7 +1,6 @@
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
@@ -36,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():
@@ -70,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(
@@ -148,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():
@@ -182,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(
@@ -274,27 +225,16 @@ 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",
+ }
def test_optional_validation_alias_missing():
@@ -308,29 +248,16 @@ 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",
+ },
+ ],
+ }
@pytest.mark.parametrize(
@@ -413,27 +340,16 @@ 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",
+ }
def test_optional_alias_and_validation_alias_missing():
@@ -447,29 +363,16 @@ 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",
+ },
+ ],
+ }
@pytest.mark.parametrize(
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 5571ba5d5..5b434fa1d 100644
--- a/tests/test_request_params/test_body/test_required_str.py
+++ b/tests/test_request_params/test_body/test_required_str.py
@@ -1,7 +1,7 @@
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
@@ -55,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(
@@ -141,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(
@@ -174,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(
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 b2f7f9cef..6f381c8b8 100644
--- a/tests/test_request_params/test_cookie/test_optional_str.py
+++ b/tests/test_request_params/test_cookie/test_optional_str.py
@@ -1,7 +1,6 @@
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
@@ -32,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",
+ }
]
@@ -104,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",
+ }
]
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 58bb7af5b..3e877b3e3 100644
--- a/tests/test_request_params/test_cookie/test_required_str.py
+++ b/tests/test_request_params/test_cookie/test_required_str.py
@@ -1,7 +1,7 @@
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
@@ -49,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(
@@ -127,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(
@@ -164,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(
diff --git a/tests/test_request_params/test_file/test_list.py b/tests/test_request_params/test_file/test_list.py
index 90f36e5f7..68280fcf3 100644
--- a/tests/test_request_params/test_file/test_list.py
+++ b/tests/test_request_params/test_file/test_list.py
@@ -1,7 +1,6 @@
from typing import Annotated
import pytest
-from dirty_equals import IsDict
from fastapi import FastAPI, File, UploadFile
from fastapi.testclient import TestClient
@@ -36,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,
@@ -75,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(
@@ -143,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,
@@ -182,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(
@@ -218,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(
@@ -291,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,
@@ -424,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,
diff --git a/tests/test_request_params/test_file/test_optional.py b/tests/test_request_params/test_file/test_optional.py
index 4e9564873..45ef7bdec 100644
--- a/tests/test_request_params/test_file/test_optional.py
+++ b/tests/test_request_params/test_file/test_optional.py
@@ -1,7 +1,6 @@
from typing import Annotated, Optional
import pytest
-from dirty_equals import IsDict
from fastapi import FastAPI, File, UploadFile
from fastapi.testclient import TestClient
@@ -36,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",
@@ -116,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",
@@ -215,21 +198,13 @@ 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",
@@ -319,21 +294,13 @@ 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",
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 e18f36e15..162fbe08a 100644
--- a/tests/test_request_params/test_file/test_optional_list.py
+++ b/tests/test_request_params/test_file/test_optional_list.py
@@ -1,7 +1,6 @@
from typing import Annotated, Optional
import pytest
-from dirty_equals import IsDict
from fastapi import FastAPI, File, UploadFile
from fastapi.testclient import TestClient
@@ -38,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",
@@ -125,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",
@@ -228,28 +203,16 @@ 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",
@@ -336,28 +299,16 @@ 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",
diff --git a/tests/test_request_params/test_file/test_required.py b/tests/test_request_params/test_file/test_required.py
index 9783f4bce..a0f9d23a6 100644
--- a/tests/test_request_params/test_file/test_required.py
+++ b/tests/test_request_params/test_file/test_required.py
@@ -1,7 +1,6 @@
from typing import Annotated
import pytest
-from dirty_equals import IsDict
from fastapi import FastAPI, File, UploadFile
from fastapi.testclient import TestClient
@@ -55,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(
@@ -142,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(
@@ -178,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(
diff --git a/tests/test_request_params/test_form/test_list.py b/tests/test_request_params/test_form/test_list.py
index 600dba4ae..abe781c94 100644
--- a/tests/test_request_params/test_form/test_list.py
+++ b/tests/test_request_params/test_form/test_list.py
@@ -1,7 +1,7 @@
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
@@ -58,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(
@@ -146,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(
@@ -182,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(
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 4552623f5..6d1957a18 100644
--- a/tests/test_request_params/test_form/test_optional_list.py
+++ b/tests/test_request_params/test_form/test_optional_list.py
@@ -1,7 +1,6 @@
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
@@ -38,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(
@@ -119,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(
@@ -217,34 +190,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",
+ }
@pytest.mark.parametrize(
@@ -326,34 +284,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",
+ }
@pytest.mark.parametrize(
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 1b0829904..810e83caa 100644
--- a/tests/test_request_params/test_form/test_optional_str.py
+++ b/tests/test_request_params/test_form/test_optional_str.py
@@ -1,7 +1,6 @@
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
@@ -36,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(
@@ -112,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(
@@ -200,27 +177,16 @@ 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",
+ }
@pytest.mark.parametrize(
@@ -303,27 +269,16 @@ 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",
+ }
@pytest.mark.parametrize(
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 1d2431b33..7c9523b30 100644
--- a/tests/test_request_params/test_form/test_required_str.py
+++ b/tests/test_request_params/test_form/test_required_str.py
@@ -1,7 +1,7 @@
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
@@ -54,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(
@@ -137,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(
@@ -170,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(
diff --git a/tests/test_request_params/test_header/test_list.py b/tests/test_request_params/test_header/test_list.py
index 2eba17559..489a6b3e7 100644
--- a/tests/test_request_params/test_header/test_list.py
+++ b/tests/test_request_params/test_header/test_list.py
@@ -1,7 +1,7 @@
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
@@ -53,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(
@@ -135,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(
@@ -171,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(
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 cd6167a18..5dd4ea9ad 100644
--- a/tests/test_request_params/test_header/test_optional_list.py
+++ b/tests/test_request_params/test_header/test_optional_list.py
@@ -1,7 +1,6 @@
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
@@ -36,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",
+ }
]
@@ -112,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",
+ }
]
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 d4f25cc1e..0bd0eddc1 100644
--- a/tests/test_request_params/test_header/test_optional_str.py
+++ b/tests/test_request_params/test_header/test_optional_str.py
@@ -1,7 +1,6 @@
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
@@ -32,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",
+ }
]
@@ -103,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",
+ }
]
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 85bb43d5a..20dd29657 100644
--- a/tests/test_request_params/test_header/test_required_str.py
+++ b/tests/test_request_params/test_header/test_required_str.py
@@ -1,7 +1,7 @@
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
@@ -49,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(
@@ -126,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(
@@ -162,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(
diff --git a/tests/test_request_params/test_query/test_list.py b/tests/test_request_params/test_query/test_list.py
index dc21a8500..e933da214 100644
--- a/tests/test_request_params/test_query/test_list.py
+++ b/tests/test_request_params/test_query/test_list.py
@@ -1,7 +1,7 @@
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
@@ -53,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(
@@ -135,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(
@@ -171,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(
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 2a8f63a36..351e03a71 100644
--- a/tests/test_request_params/test_query/test_optional_list.py
+++ b/tests/test_request_params/test_query/test_optional_list.py
@@ -1,7 +1,6 @@
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
@@ -36,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",
+ }
]
@@ -112,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",
+ }
]
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 c6a70bc28..12e1b465a 100644
--- a/tests/test_request_params/test_query/test_optional_str.py
+++ b/tests/test_request_params/test_query/test_optional_str.py
@@ -1,7 +1,6 @@
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
@@ -32,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",
+ }
]
@@ -103,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",
+ }
]
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 2ef1b0373..9e7b96145 100644
--- a/tests/test_request_params/test_query/test_required_str.py
+++ b/tests/test_request_params/test_query/test_required_str.py
@@ -1,7 +1,7 @@
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
@@ -49,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(
@@ -126,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(
@@ -162,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(
diff --git a/tests/test_response_model_as_return_annotation.py b/tests/test_response_model_as_return_annotation.py
index 9e527d6a0..58fba89f1 100644
--- a/tests/test_response_model_as_return_annotation.py
+++ b/tests/test_response_model_as_return_annotation.py
@@ -1,4 +1,3 @@
-import warnings
from typing import Union
import pytest
@@ -8,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
@@ -512,29 +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 warnings.catch_warnings(record=True):
- warnings.simplefilter("always")
-
- 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_extra_examples.py b/tests/test_schema_extra_examples.py
index 176b5588d..ac8999c90 100644
--- a/tests/test_schema_extra_examples.py
+++ b/tests/test_schema_extra_examples.py
@@ -1,8 +1,8 @@
from typing import Union
import pytest
-from dirty_equals import IsDict
from fastapi import Body, Cookie, FastAPI, Header, Path, Query
+from fastapi.exceptions import FastAPIDeprecationWarning
from fastapi.testclient import TestClient
from pydantic import BaseModel, ConfigDict
@@ -21,7 +21,7 @@ def create_app():
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"})):
@@ -38,7 +38,7 @@ def create_app():
):
return item
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.post("/example_examples/")
def example_examples(
@@ -83,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(
@@ -101,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(
@@ -112,7 +112,7 @@ def create_app():
):
return item_id
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.get("/query_example/")
def query_example(
@@ -132,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(
@@ -144,7 +144,7 @@ def create_app():
):
return data
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.get("/header_example/")
def header_example(
@@ -167,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(
@@ -179,7 +179,7 @@ def create_app():
):
return data
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.get("/cookie_example/")
def cookie_example(
@@ -199,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(
@@ -335,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,
@@ -386,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"},
}
},
@@ -538,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",
@@ -578,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",
}
@@ -622,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",
@@ -667,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",
@@ -707,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",
}
@@ -751,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",
@@ -796,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",
@@ -836,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",
}
@@ -880,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_security_oauth2.py b/tests/test_security_oauth2.py
index 804e4152d..7ad936995 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 046ac5763..57c16058a 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 629cddca2..60c6c242e 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 ed7f4efe8..cc7e5f5c6 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 fbc69a614..d3c89045b 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 bbcad8f29..820860595 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 cbd4fff7d..c6abf5e46 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 2d1d1b03c..a164bb80b 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 e8a03e811..01bba9fed 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 7493a9e66..f5e243b95 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 6aa9f2593..5a7cae160 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 000000000..b6d51d523
--- /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 000000000..227a125e7
--- /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 000000000..10212843e
--- /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 d54ec7191..0ecadbb66 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 2035cf944..63c9c16d6 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 000000000..e98d5860f
--- /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 d3e6401af..76b7ff709 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 000000000..979c054cd
--- /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 000000000..d47aa1b4f
--- /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 000000000..d452929c3
--- /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 000000000..ff9596943
--- /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 000000000..9a07a904e
--- /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 000000000..088177cb9
--- /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 000000000..a30281950
--- /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 000000000..32eb8ee75
--- /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 db9f04546..f2e56d40f 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_tutorial002.py b/tests/test_tutorial/test_body_updates/test_tutorial002.py
new file mode 100644
index 000000000..466e6af8f
--- /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_cookie_param_models/test_tutorial001.py b/tests/test_tutorial/test_cookie_param_models/test_tutorial001.py
index 265dee944..ac8e7bdae 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 0fbf141e0..d7c3d15f1 100644
--- a/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py
+++ b/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py
@@ -1,7 +1,6 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
@@ -72,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",
+ }
+ ]
+ }
)
@@ -134,19 +119,10 @@ def test_openapi_schema(client: TestClient):
"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 a65249d65..9b47cbc67 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 643011637..a9c7ae638 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 c81e991eb..f1d2accef 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 000000000..22e2e0254
--- /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 = """
+
+
+