mirror of
https://github.com/fastapi/fastapi.git
synced 2025-12-24 22:59:32 -05:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b212ddd76 | ||
|
|
7203e860b3 | ||
|
|
e55f223b46 | ||
|
|
a329baaa54 | ||
|
|
a7a0aee984 | ||
|
|
6539b80d9f | ||
|
|
e1bd9f3e33 | ||
|
|
b9b2793bda | ||
|
|
c4a1ab5036 | ||
|
|
22c7200ebb | ||
|
|
6e42bcd8ce | ||
|
|
6513d4daa1 | ||
|
|
1d93d531bc | ||
|
|
c2c1cc8aec | ||
|
|
5289259275 | ||
|
|
5783910d0c | ||
|
|
026b43e5d3 |
2
.github/workflows/build-docs.yml
vendored
2
.github/workflows/build-docs.yml
vendored
@@ -60,8 +60,6 @@ jobs:
|
||||
pyproject.toml
|
||||
- name: Install docs extras
|
||||
run: uv pip install -r requirements-docs.txt
|
||||
- name: Verify Docs
|
||||
run: python ./scripts/docs.py verify-docs
|
||||
- name: Export Language Codes
|
||||
id: show-langs
|
||||
run: |
|
||||
|
||||
11
.github/workflows/pre-commit.yml
vendored
11
.github/workflows/pre-commit.yml
vendored
@@ -7,7 +7,8 @@ on:
|
||||
- synchronize
|
||||
|
||||
env:
|
||||
IS_FORK: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
|
||||
# Forks and Dependabot don't have access to secrets
|
||||
HAS_SECRETS: ${{ secrets.PRE_COMMIT != '' }}
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
@@ -19,7 +20,7 @@ jobs:
|
||||
run: echo "$GITHUB_CONTEXT"
|
||||
- uses: actions/checkout@v5
|
||||
name: Checkout PR for own repo
|
||||
if: env.IS_FORK == 'false'
|
||||
if: env.HAS_SECRETS == 'true'
|
||||
with:
|
||||
# To be able to commit it needs to fetch the head of the branch, not the
|
||||
# merge commit
|
||||
@@ -31,7 +32,7 @@ jobs:
|
||||
# pre-commit lite ci needs the default checkout configs to work
|
||||
- uses: actions/checkout@v5
|
||||
name: Checkout PR for fork
|
||||
if: env.IS_FORK == 'true'
|
||||
if: env.HAS_SECRETS == 'false'
|
||||
with:
|
||||
# To be able to commit it needs the head branch of the PR, the remote one
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
@@ -56,7 +57,7 @@ jobs:
|
||||
run: uvx prek run --from-ref origin/${GITHUB_BASE_REF} --to-ref HEAD --show-diff-on-failure
|
||||
continue-on-error: true
|
||||
- name: Commit and push changes
|
||||
if: env.IS_FORK == 'false'
|
||||
if: env.HAS_SECRETS == 'true'
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
@@ -68,7 +69,7 @@ jobs:
|
||||
git push
|
||||
fi
|
||||
- uses: pre-commit-ci/lite-action@v1.1.0
|
||||
if: env.IS_FORK == 'true'
|
||||
if: env.HAS_SECRETS == 'false'
|
||||
with:
|
||||
msg: 🎨 Auto format
|
||||
- name: Error out on pre-commit errors
|
||||
|
||||
9
.github/workflows/test.yml
vendored
9
.github/workflows/test.yml
vendored
@@ -54,10 +54,14 @@ jobs:
|
||||
- os: windows-latest
|
||||
python-version: "3.12"
|
||||
coverage: coverage
|
||||
# Ubuntu with 3.13 needs coverage for CodSpeed benchmarks
|
||||
- os: ubuntu-latest
|
||||
python-version: "3.13"
|
||||
coverage: coverage
|
||||
# Ubuntu with 3.13 needs coverage for CodSpeed benchmarks
|
||||
- os: ubuntu-latest
|
||||
python-version: "3.13"
|
||||
coverage: coverage
|
||||
codspeed: codspeed
|
||||
- os: ubuntu-latest
|
||||
python-version: "3.14"
|
||||
coverage: coverage
|
||||
@@ -85,12 +89,13 @@ jobs:
|
||||
run: uv pip install -r requirements-tests.txt
|
||||
- run: mkdir coverage
|
||||
- name: Test
|
||||
if: matrix.codspeed != 'codspeed'
|
||||
run: bash scripts/test.sh
|
||||
env:
|
||||
COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}
|
||||
CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}
|
||||
- name: CodSpeed benchmarks
|
||||
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13'
|
||||
if: matrix.codspeed == 'codspeed'
|
||||
uses: CodSpeedHQ/action@v4
|
||||
env:
|
||||
COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}
|
||||
|
||||
@@ -21,10 +21,28 @@ repos:
|
||||
- id: ruff-format
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: local-script
|
||||
- id: add-permalinks-pages
|
||||
language: unsupported
|
||||
name: local script
|
||||
name: add-permalinks-pages
|
||||
entry: uv run ./scripts/docs.py add-permalinks-pages
|
||||
args:
|
||||
- --update-existing
|
||||
files: ^docs/en/docs/.*\.md$
|
||||
- id: generate-readme
|
||||
language: unsupported
|
||||
name: generate README.md from index.md
|
||||
entry: uv run ./scripts/docs.py generate-readme
|
||||
files: ^docs/en/docs/index\.md|docs/en/data/sponsors\.yml|scripts/docs\.py$
|
||||
pass_filenames: false
|
||||
- id: update-languages
|
||||
language: unsupported
|
||||
name: update languages
|
||||
entry: uv run ./scripts/docs.py update-languages
|
||||
files: ^docs/.*|scripts/docs\.py$
|
||||
pass_filenames: false
|
||||
- id: ensure-non-translated
|
||||
language: unsupported
|
||||
name: ensure non-translated files are not modified
|
||||
entry: uv run ./scripts/docs.py ensure-non-translated
|
||||
files: ^docs/(?!en/).*|^scripts/docs\.py$
|
||||
pass_filenames: false
|
||||
|
||||
@@ -48,7 +48,7 @@ Sie können die verwendeten Zeilen aus dem Docstring einer *Pfadoperation-Funkti
|
||||
|
||||
Das Hinzufügen eines `\f` (ein maskiertes „Form Feed“-Zeichen) führt dazu, dass **FastAPI** die für OpenAPI verwendete Ausgabe an dieser Stelle abschneidet.
|
||||
|
||||
Sie wird nicht in der Dokumentation angezeigt, aber andere Tools (z. B. Sphinx) können den Rest verwenden.
|
||||
Sie wird nicht in der Dokumentation angezeigt, aber andere Tools (wie z. B. Sphinx) können den Rest verwenden.
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *}
|
||||
|
||||
@@ -153,48 +153,16 @@ Und Sie könnten dies auch tun, wenn der Datentyp im Request nicht JSON ist.
|
||||
|
||||
In der folgenden Anwendung verwenden wir beispielsweise weder die integrierte Funktionalität von FastAPI zum Extrahieren des JSON-Schemas aus Pydantic-Modellen noch die automatische Validierung für JSON. Tatsächlich deklarieren wir den Request-Content-Type als YAML und nicht als JSON:
|
||||
|
||||
//// tab | Pydantic v2
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *}
|
||||
|
||||
////
|
||||
|
||||
//// tab | Pydantic v1
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[15:20, 22] *}
|
||||
|
||||
////
|
||||
|
||||
/// info | Info
|
||||
|
||||
In Pydantic Version 1 hieß die Methode zum Abrufen des JSON-Schemas für ein Modell `Item.schema()`, in Pydantic Version 2 heißt die Methode `Item.model_json_schema()`.
|
||||
|
||||
///
|
||||
|
||||
Obwohl wir nicht die standardmäßig integrierte Funktionalität verwenden, verwenden wir dennoch ein Pydantic-Modell, um das JSON-Schema für die Daten, die wir in YAML empfangen möchten, manuell zu generieren.
|
||||
|
||||
Dann verwenden wir den Request direkt und extrahieren den Body als `bytes`. Das bedeutet, dass FastAPI nicht einmal versucht, den Request-Payload als JSON zu parsen.
|
||||
Dann verwenden wir den Request direkt und extrahieren den Body als `bytes`. Das bedeutet, dass FastAPI nicht einmal versucht, die Request-Payload als JSON zu parsen.
|
||||
|
||||
Und dann parsen wir in unserem Code diesen YAML-Inhalt direkt und verwenden dann wieder dasselbe Pydantic-Modell, um den YAML-Inhalt zu validieren:
|
||||
|
||||
//// tab | Pydantic v2
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *}
|
||||
|
||||
////
|
||||
|
||||
//// tab | Pydantic v1
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[24:31] *}
|
||||
|
||||
////
|
||||
|
||||
/// info | Info
|
||||
|
||||
In Pydantic Version 1 war die Methode zum Parsen und Validieren eines Objekts `Item.parse_obj()`, in Pydantic Version 2 heißt die Methode `Item.model_validate()`.
|
||||
|
||||
///
|
||||
|
||||
/// tip | Tipp
|
||||
|
||||
Hier verwenden wir dasselbe Pydantic-Modell wieder.
|
||||
|
||||
@@ -60,24 +60,8 @@ Auf die gleiche Weise wie bei Pydantic-Modellen deklarieren Sie Klassenattribute
|
||||
|
||||
Sie können dieselben Validierungs-Funktionen und -Tools verwenden, die Sie für Pydantic-Modelle verwenden, z. B. verschiedene Datentypen und zusätzliche Validierungen mit `Field()`.
|
||||
|
||||
//// tab | Pydantic v2
|
||||
|
||||
{* ../../docs_src/settings/tutorial001_py39.py hl[2,5:8,11] *}
|
||||
|
||||
////
|
||||
|
||||
//// tab | Pydantic v1
|
||||
|
||||
/// info | Info
|
||||
|
||||
In Pydantic v1 würden Sie `BaseSettings` direkt von `pydantic` statt von `pydantic_settings` importieren.
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/settings/tutorial001_pv1_py39.py hl[2,5:8,11] *}
|
||||
|
||||
////
|
||||
|
||||
/// tip | Tipp
|
||||
|
||||
Für ein schnelles Copy-and-paste verwenden Sie nicht dieses Beispiel, sondern das letzte unten.
|
||||
@@ -215,8 +199,6 @@ APP_NAME="ChimichangApp"
|
||||
|
||||
Und dann aktualisieren Sie Ihre `config.py` mit:
|
||||
|
||||
//// tab | Pydantic v2
|
||||
|
||||
{* ../../docs_src/settings/app03_an_py39/config.py hl[9] *}
|
||||
|
||||
/// tip | Tipp
|
||||
@@ -225,26 +207,6 @@ Das Attribut `model_config` wird nur für die Pydantic-Konfiguration verwendet.
|
||||
|
||||
///
|
||||
|
||||
////
|
||||
|
||||
//// tab | Pydantic v1
|
||||
|
||||
{* ../../docs_src/settings/app03_an_py39/config_pv1.py hl[9:10] *}
|
||||
|
||||
/// tip | Tipp
|
||||
|
||||
Die Klasse `Config` wird nur für die Pydantic-Konfiguration verwendet. Weitere Informationen finden Sie unter <a href="https://docs.pydantic.dev/1.10/usage/model_config/" class="external-link" target="_blank">Pydantic Model Config</a>.
|
||||
|
||||
///
|
||||
|
||||
////
|
||||
|
||||
/// info | Info
|
||||
|
||||
In Pydantic Version 1 erfolgte die Konfiguration in einer internen Klasse `Config`, in Pydantic Version 2 erfolgt sie in einem Attribut `model_config`. Dieses Attribut akzeptiert ein <abbr title="Dictionary – Zuordnungstabelle: In anderen Sprachen auch Hash, Map, Objekt, Assoziatives Array genannt">`dict`</abbr>. Um automatische Codevervollständigung und Inline-Fehlerberichte zu erhalten, können Sie `SettingsConfigDict` importieren und verwenden, um dieses `dict` zu definieren.
|
||||
|
||||
///
|
||||
|
||||
Hier definieren wir die Konfiguration `env_file` innerhalb Ihrer Pydantic-`Settings`-Klasse und setzen den Wert auf den Dateinamen mit der dotenv-Datei, die wir verwenden möchten.
|
||||
|
||||
### Die `Settings` nur einmal laden mittels `lru_cache` { #creating-the-settings-only-once-with-lru-cache }
|
||||
|
||||
@@ -2,21 +2,23 @@
|
||||
|
||||
Wenn Sie eine ältere FastAPI-App haben, nutzen Sie möglicherweise Pydantic Version 1.
|
||||
|
||||
FastAPI unterstützt seit Version 0.100.0 sowohl Pydantic v1 als auch v2.
|
||||
FastAPI Version 0.100.0 unterstützte sowohl Pydantic v1 als auch v2. Es verwendete, was auch immer Sie installiert hatten.
|
||||
|
||||
Wenn Sie Pydantic v2 installiert hatten, wurde dieses verwendet. Wenn stattdessen Pydantic v1 installiert war, wurde jenes verwendet.
|
||||
FastAPI Version 0.119.0 führte eine teilweise Unterstützung für Pydantic v1 innerhalb von Pydantic v2 (als `pydantic.v1`) ein, um die Migration zu v2 zu erleichtern.
|
||||
|
||||
Pydantic v1 ist jetzt deprecatet und die Unterstützung dafür wird in den nächsten Versionen von FastAPI entfernt, Sie sollten also zu **Pydantic v2 migrieren**. Auf diese Weise erhalten Sie die neuesten Features, Verbesserungen und Fixes.
|
||||
FastAPI 0.126.0 entfernte die Unterstützung für Pydantic v1, während `pydantic.v1` noch eine Weile unterstützt wurde.
|
||||
|
||||
/// warning | Achtung
|
||||
|
||||
Außerdem hat das Pydantic-Team die Unterstützung für Pydantic v1 in den neuesten Python-Versionen eingestellt, beginnend mit **Python 3.14**.
|
||||
Das Pydantic-Team hat die Unterstützung für Pydantic v1 in den neuesten Python-Versionen eingestellt, beginnend mit **Python 3.14**.
|
||||
|
||||
Dies schließt `pydantic.v1` ein, das unter Python 3.14 und höher nicht mehr unterstützt wird.
|
||||
|
||||
Wenn Sie die neuesten Features von Python nutzen möchten, müssen Sie sicherstellen, dass Sie Pydantic v2 verwenden.
|
||||
|
||||
///
|
||||
|
||||
Wenn Sie eine ältere FastAPI-App mit Pydantic v1 haben, zeige ich Ihnen hier, wie Sie sie zu Pydantic v2 migrieren, und die **neuen Features in FastAPI 0.119.0**, die Ihnen bei einer schrittweisen Migration helfen.
|
||||
Wenn Sie eine ältere FastAPI-App mit Pydantic v1 haben, zeige ich Ihnen hier, wie Sie sie zu Pydantic v2 migrieren, und die **Features in FastAPI 0.119.0**, die Ihnen bei einer schrittweisen Migration helfen.
|
||||
|
||||
## Offizieller Leitfaden { #official-guide }
|
||||
|
||||
@@ -44,7 +46,7 @@ Danach können Sie die Tests ausführen und prüfen, ob alles funktioniert. Fall
|
||||
|
||||
## Pydantic v1 in v2 { #pydantic-v1-in-v2 }
|
||||
|
||||
Pydantic v2 enthält alles aus Pydantic v1 als Untermodul `pydantic.v1`.
|
||||
Pydantic v2 enthält alles aus Pydantic v1 als Untermodul `pydantic.v1`. Dies wird aber in Versionen oberhalb von Python 3.13 nicht mehr unterstützt.
|
||||
|
||||
Das bedeutet, Sie können die neueste Version von Pydantic v2 installieren und die alten Pydantic‑v1‑Komponenten aus diesem Untermodul importieren und verwenden, als hätten Sie das alte Pydantic v1 installiert.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Separate OpenAPI-Schemas für Eingabe und Ausgabe oder nicht { #separate-openapi-schemas-for-input-and-output-or-not }
|
||||
|
||||
Bei Verwendung von **Pydantic v2** ist die generierte OpenAPI etwas genauer und **korrekter** als zuvor. 😎
|
||||
Seit der Veröffentlichung von **Pydantic v2** ist die generierte OpenAPI etwas genauer und **korrekter** als zuvor. 😎
|
||||
|
||||
Tatsächlich gibt es in einigen Fällen sogar **zwei JSON-Schemas** in OpenAPI für dasselbe Pydantic-Modell, für Eingabe und Ausgabe, je nachdem, ob sie **Defaultwerte** haben.
|
||||
|
||||
@@ -100,5 +100,3 @@ Und jetzt wird es ein einziges Schema für die Eingabe und Ausgabe des Modells g
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/separate-openapi-schemas/image05.png">
|
||||
</div>
|
||||
|
||||
Dies ist das gleiche Verhalten wie in Pydantic v1. 🤓
|
||||
|
||||
@@ -50,14 +50,6 @@ Wenn Sie Teil-Aktualisierungen entgegennehmen, ist der `exclude_unset`-Parameter
|
||||
|
||||
Wie in `item.model_dump(exclude_unset=True)`.
|
||||
|
||||
/// info | Info
|
||||
|
||||
In Pydantic v1 hieß diese Methode `.dict()`, in Pydantic v2 wurde sie <abbr title="veraltet, obsolet: Es soll nicht mehr verwendet werden">deprecatet</abbr> (aber immer noch unterstützt) und in `.model_dump()` umbenannt.
|
||||
|
||||
Die Beispiele hier verwenden `.dict()` für die Kompatibilität mit Pydantic v1, Sie sollten jedoch stattdessen `.model_dump()` verwenden, wenn Sie Pydantic v2 verwenden können.
|
||||
|
||||
///
|
||||
|
||||
Das wird ein <abbr title="Dictionary – Zuordnungstabelle: In anderen Sprachen auch Hash, Map, Objekt, Assoziatives Array genannt">`dict`</abbr> erstellen, mit nur den Daten, die gesetzt wurden, als das `item`-Modell erstellt wurde, Defaultwerte ausgeschlossen.
|
||||
|
||||
Sie können das verwenden, um ein `dict` zu erstellen, das nur die (im <abbr title="Request – Anfrage: Daten, die der Client zum Server sendet">Request</abbr>) gesendeten Daten enthält, ohne Defaultwerte:
|
||||
@@ -68,14 +60,6 @@ Sie können das verwenden, um ein `dict` zu erstellen, das nur die (im <abbr tit
|
||||
|
||||
Jetzt können Sie eine Kopie des existierenden Modells mittels `.model_copy()` erstellen, wobei Sie dem `update`-Parameter ein `dict` mit den zu ändernden Daten übergeben.
|
||||
|
||||
/// info | Info
|
||||
|
||||
In Pydantic v1 hieß diese Methode `.copy()`, in Pydantic v2 wurde sie <abbr title="veraltet, obsolet: Es soll nicht mehr verwendet werden">deprecatet</abbr> (aber immer noch unterstützt) und in `.model_copy()` umbenannt.
|
||||
|
||||
Die Beispiele hier verwenden `.copy()` für die Kompatibilität mit Pydantic v1, Sie sollten jedoch stattdessen `.model_copy()` verwenden, wenn Sie Pydantic v2 verwenden können.
|
||||
|
||||
///
|
||||
|
||||
Wie in `stored_item_model.model_copy(update=update_data)`:
|
||||
|
||||
{* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *}
|
||||
|
||||
@@ -127,14 +127,6 @@ Innerhalb der Funktion können Sie alle Attribute des Modellobjekts direkt verwe
|
||||
|
||||
{* ../../docs_src/body/tutorial002_py310.py *}
|
||||
|
||||
/// info | Info
|
||||
|
||||
In Pydantic v1 hieß die Methode `.dict()`, sie wurde in Pydantic v2 deprecatet (aber weiterhin unterstützt) und in `.model_dump()` umbenannt.
|
||||
|
||||
Die Beispiele hier verwenden `.dict()` zur Kompatibilität mit Pydantic v1, aber Sie sollten stattdessen `.model_dump()` verwenden, wenn Sie Pydantic v2 nutzen können.
|
||||
|
||||
///
|
||||
|
||||
## Requestbody- + Pfad-Parameter { #request-body-path-parameters }
|
||||
|
||||
Sie können Pfad-Parameter und den Requestbody gleichzeitig deklarieren.
|
||||
|
||||
@@ -22,21 +22,13 @@ Hier ist eine allgemeine Idee, wie die Modelle mit ihren Passwortfeldern aussehe
|
||||
|
||||
{* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *}
|
||||
|
||||
/// info | Info
|
||||
### Über `**user_in.model_dump()` { #about-user-in-model-dump }
|
||||
|
||||
In Pydantic v1 hieß die Methode `.dict()`, in Pydantic v2 wurde sie <abbr title="veraltet, obsolet: Es soll nicht mehr verwendet werden">deprecatet</abbr> (aber weiterhin unterstützt) und in `.model_dump()` umbenannt.
|
||||
|
||||
Die Beispiele hier verwenden `.dict()` für die Kompatibilität mit Pydantic v1, aber Sie sollten `.model_dump()` verwenden, wenn Sie Pydantic v2 verwenden können.
|
||||
|
||||
///
|
||||
|
||||
### Über `**user_in.dict()` { #about-user-in-dict }
|
||||
|
||||
#### Die `.dict()`-Methode von Pydantic { #pydantics-dict }
|
||||
#### Pydantics `.model_dump()` { #pydantics-model-dump }
|
||||
|
||||
`user_in` ist ein Pydantic-Modell der Klasse `UserIn`.
|
||||
|
||||
Pydantic-Modelle haben eine `.dict()`-Methode, die ein <abbr title="Dictionary – Zuordnungstabelle: In anderen Sprachen auch Hash, Map, Objekt, Assoziatives Array genannt">`dict`</abbr> mit den Daten des Modells zurückgibt.
|
||||
Pydantic-Modelle haben eine `.model_dump()`-Methode, die ein <abbr title="Dictionary – Zuordnungstabelle: In anderen Sprachen auch Hash, Map, Objekt, Assoziatives Array genannt">`dict`</abbr> mit den Daten des Modells zurückgibt.
|
||||
|
||||
Wenn wir also ein Pydantic-Objekt `user_in` erstellen, etwa so:
|
||||
|
||||
@@ -47,7 +39,7 @@ user_in = UserIn(username="john", password="secret", email="john.doe@example.com
|
||||
und dann aufrufen:
|
||||
|
||||
```Python
|
||||
user_dict = user_in.dict()
|
||||
user_dict = user_in.model_dump()
|
||||
```
|
||||
|
||||
haben wir jetzt ein `dict` mit den Daten in der Variablen `user_dict` (es ist ein `dict` statt eines Pydantic-Modellobjekts).
|
||||
@@ -103,20 +95,20 @@ UserInDB(
|
||||
|
||||
#### Ein Pydantic-Modell aus dem Inhalt eines anderen { #a-pydantic-model-from-the-contents-of-another }
|
||||
|
||||
Da wir im obigen Beispiel `user_dict` von `user_in.dict()` bekommen haben, wäre dieser Code:
|
||||
Da wir im obigen Beispiel `user_dict` von `user_in.model_dump()` bekommen haben, wäre dieser Code:
|
||||
|
||||
```Python
|
||||
user_dict = user_in.dict()
|
||||
user_dict = user_in.model_dump()
|
||||
UserInDB(**user_dict)
|
||||
```
|
||||
|
||||
gleichwertig zu:
|
||||
|
||||
```Python
|
||||
UserInDB(**user_in.dict())
|
||||
UserInDB(**user_in.model_dump())
|
||||
```
|
||||
|
||||
... weil `user_in.dict()` ein `dict` ist, und dann lassen wir Python es „entpacken“, indem wir es an `UserInDB` mit vorangestelltem `**` übergeben.
|
||||
... weil `user_in.model_dump()` ein `dict` ist, und dann lassen wir Python es „entpacken“, indem wir es an `UserInDB` mit vorangestelltem `**` übergeben.
|
||||
|
||||
Auf diese Weise erhalten wir ein Pydantic-Modell aus den Daten eines anderen Pydantic-Modells.
|
||||
|
||||
@@ -125,7 +117,7 @@ Auf diese Weise erhalten wir ein Pydantic-Modell aus den Daten eines anderen Pyd
|
||||
Und dann fügen wir das zusätzliche Schlüsselwort-Argument `hashed_password=hashed_password` hinzu, wie in:
|
||||
|
||||
```Python
|
||||
UserInDB(**user_in.dict(), hashed_password=hashed_password)
|
||||
UserInDB(**user_in.model_dump(), hashed_password=hashed_password)
|
||||
```
|
||||
|
||||
... was so ist wie:
|
||||
@@ -180,7 +172,6 @@ Wenn Sie eine <a href="https://docs.pydantic.dev/latest/concepts/types/#unions"
|
||||
|
||||
{* ../../docs_src/extra_models/tutorial003_py310.py hl[1,14:15,18:20,33] *}
|
||||
|
||||
|
||||
### `Union` in Python 3.10 { #union-in-python-3-10 }
|
||||
|
||||
In diesem Beispiel übergeben wir `Union[PlaneItem, CarItem]` als Wert des Arguments `response_model`.
|
||||
@@ -203,7 +194,6 @@ Dafür verwenden Sie Pythons Standard-`typing.List` (oder nur `list` in Python 3
|
||||
|
||||
{* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *}
|
||||
|
||||
|
||||
## Response mit beliebigem `dict` { #response-with-arbitrary-dict }
|
||||
|
||||
Sie können auch eine Response deklarieren, die ein beliebiges `dict` zurückgibt, indem Sie nur die Typen der Schlüssel und Werte ohne ein Pydantic-Modell deklarieren.
|
||||
@@ -214,7 +204,6 @@ In diesem Fall können Sie `typing.Dict` verwenden (oder nur `dict` in Python 3.
|
||||
|
||||
{* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *}
|
||||
|
||||
|
||||
## Zusammenfassung { #recap }
|
||||
|
||||
Verwenden Sie gerne mehrere Pydantic-Modelle und vererben Sie je nach Bedarf.
|
||||
|
||||
@@ -205,20 +205,6 @@ Wenn Sie sich mit all diesen **„regulärer Ausdruck“**-Ideen verloren fühle
|
||||
|
||||
Aber nun wissen Sie, dass Sie sie in **FastAPI** immer dann verwenden können, wenn Sie sie brauchen.
|
||||
|
||||
### Pydantic v1 `regex` statt `pattern` { #pydantic-v1-regex-instead-of-pattern }
|
||||
|
||||
Vor Pydantic Version 2 und FastAPI 0.100.0, hieß der Parameter `regex` statt `pattern`, aber das ist jetzt obsolet.
|
||||
|
||||
Sie könnten immer noch Code sehen, der den alten Namen verwendet:
|
||||
|
||||
//// tab | Pydantic v1
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial004_regex_an_py310.py hl[11] *}
|
||||
|
||||
////
|
||||
|
||||
Beachten Sie aber, dass das obsolet ist und auf den neuen Parameter `pattern` aktualisiert werden sollte. 🤓
|
||||
|
||||
## Defaultwerte { #default-values }
|
||||
|
||||
Natürlich können Sie Defaultwerte verwenden, die nicht `None` sind.
|
||||
|
||||
@@ -252,20 +252,6 @@ Wenn Sie also den Artikel mit der ID `foo` bei der *Pfadoperation* anfragen, wir
|
||||
|
||||
/// info | Info
|
||||
|
||||
In Pydantic v1 hieß diese Methode `.dict()`, in Pydantic v2 wurde sie <abbr title="veraltet, obsolet: Es soll nicht mehr verwendet werden">deprecatet</abbr> (aber immer noch unterstützt) und in `.model_dump()` umbenannt.
|
||||
|
||||
Die Beispiele hier verwenden `.dict()` für die Kompatibilität mit Pydantic v1, Sie sollten jedoch stattdessen `.model_dump()` verwenden, wenn Sie Pydantic v2 verwenden können.
|
||||
|
||||
///
|
||||
|
||||
/// info | Info
|
||||
|
||||
FastAPI verwendet `.dict()` von Pydantic Modellen, <a href="https://docs.pydantic.dev/1.10/usage/exporting_models/#modeldict" class="external-link" target="_blank">mit dessen `exclude_unset`-Parameter</a>, um das zu erreichen.
|
||||
|
||||
///
|
||||
|
||||
/// info | Info
|
||||
|
||||
Sie können auch:
|
||||
|
||||
* `response_model_exclude_defaults=True`
|
||||
|
||||
@@ -8,36 +8,14 @@ Hier sind mehrere Möglichkeiten, das zu tun.
|
||||
|
||||
Sie können `examples` („Beispiele“) für ein Pydantic-Modell deklarieren, welche dem generierten JSON-Schema hinzugefügt werden.
|
||||
|
||||
//// tab | Pydantic v2
|
||||
|
||||
{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *}
|
||||
|
||||
////
|
||||
|
||||
//// tab | Pydantic v1
|
||||
|
||||
{* ../../docs_src/schema_extra_example/tutorial001_pv1_py310.py hl[13:23] *}
|
||||
|
||||
////
|
||||
|
||||
Diese zusätzlichen Informationen werden unverändert zum für dieses Modell ausgegebenen **JSON-Schema** hinzugefügt und in der API-Dokumentation verwendet.
|
||||
|
||||
//// tab | Pydantic v2
|
||||
|
||||
In Pydantic Version 2 würden Sie das Attribut `model_config` verwenden, das ein <abbr title="Dictionary – Zuordnungstabelle: In anderen Sprachen auch Hash, Map, Objekt, Assoziatives Array genannt">`dict`</abbr> akzeptiert, wie beschrieben in <a href="https://docs.pydantic.dev/latest/api/config/" class="external-link" target="_blank">Pydantic-Dokumentation: Configuration</a>.
|
||||
Sie können das Attribut `model_config` verwenden, das ein <abbr title="Dictionary – Zuordnungstabelle: In anderen Sprachen auch Hash, Map, Objekt, Assoziatives Array genannt">`dict`</abbr> akzeptiert, wie beschrieben in <a href="https://docs.pydantic.dev/latest/api/config/" class="external-link" target="_blank">Pydantic-Dokumentation: Configuration</a>.
|
||||
|
||||
Sie können `json_schema_extra` setzen, mit einem `dict`, das alle zusätzlichen Daten enthält, die im generierten JSON-Schema angezeigt werden sollen, einschließlich `examples`.
|
||||
|
||||
////
|
||||
|
||||
//// tab | Pydantic v1
|
||||
|
||||
In Pydantic Version 1 würden Sie eine interne Klasse `Config` und `schema_extra` verwenden, wie beschrieben in <a href="https://docs.pydantic.dev/1.10/usage/schema/#schema-customization" class="external-link" target="_blank">Pydantic-Dokumentation: Schema customization</a>.
|
||||
|
||||
Sie können `schema_extra` setzen, mit einem `dict`, das alle zusätzlichen Daten enthält, die im generierten JSON-Schema angezeigt werden sollen, einschließlich `examples`.
|
||||
|
||||
////
|
||||
|
||||
/// tip | Tipp
|
||||
|
||||
Mit derselben Technik können Sie das JSON-Schema erweitern und Ihre eigenen benutzerdefinierten Zusatzinformationen hinzufügen.
|
||||
|
||||
@@ -7,6 +7,31 @@ hide:
|
||||
|
||||
## Latest Changes
|
||||
|
||||
### Translations
|
||||
|
||||
* 🌐 Update translations for de (update-outdated). PR [#14581](https://github.com/fastapi/fastapi/pull/14581) by [@nilslindemann](https://github.com/nilslindemann).
|
||||
|
||||
### Internal
|
||||
|
||||
* 👷 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).
|
||||
* 🔨 Update scripts and pre-commit to autofix files. PR [#14585](https://github.com/fastapi/fastapi/pull/14585) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
## 0.127.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* 🔊 Add deprecation warnings when using `pydantic.v1`. PR [#14583](https://github.com/fastapi/fastapi/pull/14583) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
### Translations
|
||||
|
||||
* 🔧 Add LLM prompt file for Korean, generated from the existing translations. PR [#14546](https://github.com/fastapi/fastapi/pull/14546) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 🔧 Add LLM prompt file for Japanese, generated from the existing translations. PR [#14545](https://github.com/fastapi/fastapi/pull/14545) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
### Internal
|
||||
|
||||
* ⬆️ Upgrade OpenAI model for translations to gpt-5.2. PR [#14579](https://github.com/fastapi/fastapi/pull/14579) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
## 0.126.0
|
||||
|
||||
### Upgrades
|
||||
|
||||
47
docs/ja/llm-prompt.md
Normal file
47
docs/ja/llm-prompt.md
Normal file
@@ -0,0 +1,47 @@
|
||||
### Target language
|
||||
|
||||
Translate to Japanese (日本語).
|
||||
|
||||
Language code: ja.
|
||||
|
||||
### Grammar and tone
|
||||
|
||||
1) Use polite, instructional Japanese (です/ます調).
|
||||
2) 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.
|
||||
|
||||
### 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.
|
||||
|
||||
### Ellipsis
|
||||
|
||||
1) Keep ellipsis style consistent with existing Japanese docs (commonly `...`).
|
||||
2) Never change `...` 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
|
||||
|
||||
1) Keep the admonition keyword in English (do not translate `note`, `tip`, etc.).
|
||||
2) If a title is present, prefer these canonical titles:
|
||||
|
||||
- `/// note | 備考`
|
||||
- `/// note | 技術詳細`
|
||||
- `/// tip | 豆知識`
|
||||
- `/// warning | 注意`
|
||||
- `/// info | 情報`
|
||||
- `/// check | 確認`
|
||||
- `/// danger | 警告`
|
||||
51
docs/ko/llm-prompt.md
Normal file
51
docs/ko/llm-prompt.md
Normal file
@@ -0,0 +1,51 @@
|
||||
### Target language
|
||||
|
||||
Translate to Korean (한국어).
|
||||
|
||||
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.
|
||||
|
||||
### Headings
|
||||
|
||||
1) Follow existing Korean heading style (short, action-oriented headings like “확인하기”).
|
||||
2) 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.
|
||||
|
||||
### Ellipsis
|
||||
|
||||
1) Keep ellipsis style consistent with existing Korean docs (often `...`).
|
||||
2) Never change `...` 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
|
||||
|
||||
1) Keep the admonition keyword in English (do not translate `note`, `tip`, etc.).
|
||||
2) If a title is present, prefer these canonical titles:
|
||||
|
||||
- `/// note | 참고`
|
||||
- `/// tip | 팁`
|
||||
- `/// warning | 경고`
|
||||
- `/// info | 정보`
|
||||
- `/// danger | 위험`
|
||||
- `/// note Technical Details | 기술 세부사항`
|
||||
- `/// check | 확인`
|
||||
Notes:
|
||||
|
||||
- `details` blocks exist in Korean docs; keep `/// details` as-is and translate only the title after `|`.
|
||||
- Example canonical title used: `/// details | 상세 설명`
|
||||
@@ -1,6 +1,6 @@
|
||||
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
|
||||
|
||||
__version__ = "0.126.0"
|
||||
__version__ = "0.127.0"
|
||||
|
||||
from starlette import status as status
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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
|
||||
@@ -322,6 +323,13 @@ 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)
|
||||
):
|
||||
|
||||
@@ -2,6 +2,7 @@ import email.message
|
||||
import functools
|
||||
import inspect
|
||||
import json
|
||||
import warnings
|
||||
from collections.abc import (
|
||||
AsyncIterator,
|
||||
Awaitable,
|
||||
@@ -28,6 +29,7 @@ from fastapi._compat import (
|
||||
_get_model_config,
|
||||
_model_dump,
|
||||
_normalize_errors,
|
||||
annotation_is_pydantic_v1,
|
||||
lenient_issubclass,
|
||||
may_v1,
|
||||
)
|
||||
@@ -634,6 +636,13 @@ class APIRoute(routing.Route):
|
||||
f"Status code {status_code} must not have a response body"
|
||||
)
|
||||
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,
|
||||
)
|
||||
self.response_field = create_model_field(
|
||||
name=response_name,
|
||||
type_=self.response_model,
|
||||
@@ -667,6 +676,13 @@ class APIRoute(routing.Route):
|
||||
f"Status code {additional_status_code} must not have a response body"
|
||||
)
|
||||
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,
|
||||
)
|
||||
response_field = create_model_field(
|
||||
name=response_name, type_=model, mode="serialization"
|
||||
)
|
||||
|
||||
@@ -19,7 +19,13 @@ from slugify import slugify as py_slugify
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
SUPPORTED_LANGS = {"en", "de", "es", "pt", "ru"}
|
||||
SUPPORTED_LANGS = {
|
||||
"en",
|
||||
"de",
|
||||
"es",
|
||||
"pt",
|
||||
"ru",
|
||||
}
|
||||
|
||||
|
||||
app = typer.Typer()
|
||||
@@ -232,27 +238,15 @@ def generate_readme() -> None:
|
||||
"""
|
||||
Generate README.md content from main index.md
|
||||
"""
|
||||
typer.echo("Generating README")
|
||||
readme_path = Path("README.md")
|
||||
old_content = readme_path.read_text()
|
||||
new_content = generate_readme_content()
|
||||
readme_path.write_text(new_content, encoding="utf-8")
|
||||
|
||||
|
||||
@app.command()
|
||||
def verify_readme() -> None:
|
||||
"""
|
||||
Verify README.md content from main index.md
|
||||
"""
|
||||
typer.echo("Verifying README")
|
||||
readme_path = Path("README.md")
|
||||
generated_content = generate_readme_content()
|
||||
readme_content = readme_path.read_text("utf-8")
|
||||
if generated_content != readme_content:
|
||||
typer.secho(
|
||||
"README.md outdated from the latest index.md", color=typer.colors.RED
|
||||
)
|
||||
raise typer.Abort()
|
||||
typer.echo("Valid README ✅")
|
||||
if new_content != old_content:
|
||||
print("README.md outdated from the latest index.md")
|
||||
print("Updating README.md")
|
||||
readme_path.write_text(new_content, encoding="utf-8")
|
||||
raise typer.Exit(1)
|
||||
print("README.md is up to date ✅")
|
||||
|
||||
|
||||
@app.command()
|
||||
@@ -280,7 +274,17 @@ def update_languages() -> None:
|
||||
"""
|
||||
Update the mkdocs.yml file Languages section including all the available languages.
|
||||
"""
|
||||
update_config()
|
||||
old_config = get_en_config()
|
||||
updated_config = get_updated_config_content()
|
||||
if old_config != updated_config:
|
||||
print("docs/en/mkdocs.yml outdated")
|
||||
print("Updating docs/en/mkdocs.yml")
|
||||
en_config_path.write_text(
|
||||
yaml.dump(updated_config, sort_keys=False, width=200, allow_unicode=True),
|
||||
encoding="utf-8",
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
print("docs/en/mkdocs.yml is up to date ✅")
|
||||
|
||||
|
||||
@app.command()
|
||||
@@ -367,39 +371,12 @@ def get_updated_config_content() -> dict[str, Any]:
|
||||
return config
|
||||
|
||||
|
||||
def update_config() -> None:
|
||||
config = get_updated_config_content()
|
||||
en_config_path.write_text(
|
||||
yaml.dump(config, sort_keys=False, width=200, allow_unicode=True),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
@app.command()
|
||||
def verify_config() -> None:
|
||||
def ensure_non_translated() -> None:
|
||||
"""
|
||||
Verify main mkdocs.yml content to make sure it uses the latest language names.
|
||||
Ensure there are no files in the non translatable pages.
|
||||
"""
|
||||
typer.echo("Verifying mkdocs.yml")
|
||||
config = get_en_config()
|
||||
updated_config = get_updated_config_content()
|
||||
if config != updated_config:
|
||||
typer.secho(
|
||||
"docs/en/mkdocs.yml outdated from docs/language_names.yml, "
|
||||
"update language_names.yml and run "
|
||||
"python ./scripts/docs.py update-languages",
|
||||
color=typer.colors.RED,
|
||||
)
|
||||
raise typer.Abort()
|
||||
typer.echo("Valid mkdocs.yml ✅")
|
||||
|
||||
|
||||
@app.command()
|
||||
def verify_non_translated() -> None:
|
||||
"""
|
||||
Verify there are no files in the non translatable pages.
|
||||
"""
|
||||
print("Verifying non translated pages")
|
||||
print("Ensuring no non translated pages")
|
||||
lang_paths = get_lang_paths()
|
||||
error_paths = []
|
||||
for lang in lang_paths:
|
||||
@@ -410,20 +387,17 @@ def verify_non_translated() -> None:
|
||||
if non_translatable_path.exists():
|
||||
error_paths.append(non_translatable_path)
|
||||
if error_paths:
|
||||
print("Non-translated pages found, remove them:")
|
||||
print("Non-translated pages found, removing them:")
|
||||
for error_path in error_paths:
|
||||
print(error_path)
|
||||
raise typer.Abort()
|
||||
if error_path.is_file():
|
||||
error_path.unlink()
|
||||
else:
|
||||
shutil.rmtree(error_path)
|
||||
raise typer.Exit(1)
|
||||
print("No non-translated pages found ✅")
|
||||
|
||||
|
||||
@app.command()
|
||||
def verify_docs():
|
||||
verify_readme()
|
||||
verify_config()
|
||||
verify_non_translated()
|
||||
|
||||
|
||||
@app.command()
|
||||
def langs_json():
|
||||
langs = []
|
||||
|
||||
@@ -727,7 +727,7 @@ def translate_page(
|
||||
print(f"Found existing translation: {out_path}")
|
||||
old_translation = out_path.read_text(encoding="utf-8")
|
||||
print(f"Translating {en_path} to {language} ({language_name})")
|
||||
agent = Agent("openai:gpt-5")
|
||||
agent = Agent("openai:gpt-5.2")
|
||||
|
||||
prompt_segments = [
|
||||
general_prompt,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import json
|
||||
import sys
|
||||
import warnings
|
||||
from collections.abc import Iterator
|
||||
from typing import Annotated, Any
|
||||
|
||||
@@ -84,96 +85,103 @@ def app(basemodel_class: type[Any]) -> FastAPI:
|
||||
|
||||
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)
|
||||
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.get("/sync/dict-no-response-model")
|
||||
def sync_dict_no_response_model():
|
||||
return {"name": "foo", "value": 123}
|
||||
@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-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/dict-no-response-model")
|
||||
def sync_dict_no_response_model():
|
||||
return {"name": "foo", "value": 123}
|
||||
|
||||
@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/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-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.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.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.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("/sync/large-receive")
|
||||
def sync_large_receive(payload: LargeIn):
|
||||
return {"received": len(payload.items)}
|
||||
@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("/async/large-receive")
|
||||
async def async_large_receive(payload: LargeIn):
|
||||
return {"received": len(payload.items)}
|
||||
@app.post("/sync/large-receive")
|
||||
def sync_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.post("/async/large-receive")
|
||||
async def async_large_receive(payload: LargeIn):
|
||||
return {"received": len(payload.items)}
|
||||
|
||||
@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-dict-no-response-model")
|
||||
def sync_large_dict_no_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-dict-with-response-model", response_model=LargeOut)
|
||||
def sync_large_dict_with_response_model():
|
||||
return LARGE_PAYLOAD
|
||||
|
||||
@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("/sync/large-model-no-response-model")
|
||||
def sync_large_model_no_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("/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-with-response-model", response_model=LargeOut)
|
||||
async def async_large_dict_with_response_model():
|
||||
return LARGE_PAYLOAD
|
||||
@app.get("/async/large-dict-no-response-model")
|
||||
async def async_large_dict_no_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-dict-with-response-model", response_model=LargeOut)
|
||||
async def async_large_dict_with_response_model():
|
||||
return LARGE_PAYLOAD
|
||||
|
||||
@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/large-model-no-response-model")
|
||||
async def async_large_model_no_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/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-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/dict-no-response-model")
|
||||
async def async_dict_no_response_model():
|
||||
return {"name": "foo", "value": 123}
|
||||
|
||||
@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/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-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)
|
||||
@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
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import sys
|
||||
import warnings
|
||||
from typing import Optional
|
||||
|
||||
import pytest
|
||||
@@ -33,94 +34,90 @@ class Item(BaseModel):
|
||||
|
||||
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/{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("/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.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.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.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.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("/items/")
|
||||
def create_item(
|
||||
item: Annotated[
|
||||
Item,
|
||||
Body(examples=[{"name": "Foo", "price": 35.4, "description": "The Foo item"}]),
|
||||
],
|
||||
):
|
||||
return {"item": item}
|
||||
@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("/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,
|
||||
}
|
||||
@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)
|
||||
@@ -211,10 +208,10 @@ def test_header_params_none():
|
||||
|
||||
# Cookie parameter tests
|
||||
def test_cookie_params():
|
||||
with TestClient(app) as client:
|
||||
client.cookies.set("session_id", "abc123")
|
||||
client.cookies.set("tracking_id", "1234567890abcdef")
|
||||
response = client.get("/cookies/")
|
||||
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",
|
||||
@@ -223,9 +220,9 @@ def test_cookie_params():
|
||||
|
||||
|
||||
def test_cookie_tracking_id_too_short():
|
||||
with TestClient(app) as client:
|
||||
client.cookies.set("tracking_id", "short")
|
||||
response = client.get("/cookies/")
|
||||
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(
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import warnings
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from fastapi import FastAPI
|
||||
@@ -48,9 +49,12 @@ def test_pydanticv1():
|
||||
app = FastAPI()
|
||||
model = ModelWithDatetimeField(dt_field=datetime(2019, 1, 1, 8))
|
||||
|
||||
@app.get("/model", response_model=ModelWithDatetimeField)
|
||||
def get_model():
|
||||
return model
|
||||
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:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import warnings
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import Depends, FastAPI
|
||||
@@ -31,11 +32,14 @@ async def get_model_c() -> ModelC:
|
||||
return ModelC(username="test-user", password="test-password")
|
||||
|
||||
|
||||
@app.get("/model/{name}", response_model=ModelA)
|
||||
async def get_model_a(name: str, model_c=Depends(get_model_c)):
|
||||
return {
|
||||
"name": name,
|
||||
"description": "model-a-desc",
|
||||
"model_b": model_c,
|
||||
"tags": {"key1": "value1", "key2": "value2"},
|
||||
}
|
||||
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"},
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
@@ -36,12 +38,28 @@ def client_fixture(request: pytest.FixtureRequest) -> TestClient:
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/facilities/{facility_id}")
|
||||
def get_facility(facility_id: str) -> Facility:
|
||||
return Facility(
|
||||
id=facility_id,
|
||||
address=Address(line_1="123 Main St", city="Anytown", state_province="CA"),
|
||||
)
|
||||
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"
|
||||
),
|
||||
)
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
98
tests/test_pydantic_v1_deprecation_warnings.py
Normal file
98
tests/test_pydantic_v1_deprecation_warnings.py
Normal file
@@ -0,0 +1,98 @@
|
||||
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}
|
||||
@@ -1,4 +1,5 @@
|
||||
import sys
|
||||
import warnings
|
||||
from typing import Any, Union
|
||||
|
||||
from tests.utils import skip_module_if_py_gte_314
|
||||
@@ -26,30 +27,29 @@ class Item(BaseModel):
|
||||
|
||||
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")
|
||||
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("/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")
|
||||
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
|
||||
@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)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import sys
|
||||
import warnings
|
||||
from typing import Any, Union
|
||||
|
||||
from tests.utils import skip_module_if_py_gte_314
|
||||
@@ -27,49 +28,47 @@ class Item(BaseModel):
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.post("/item")
|
||||
def handle_item(data: Item) -> list[Item]:
|
||||
return [data, data]
|
||||
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()
|
||||
@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]
|
||||
return []
|
||||
|
||||
@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)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import sys
|
||||
import warnings
|
||||
from typing import Any, Union
|
||||
|
||||
from tests.utils import skip_module_if_py_gte_314
|
||||
@@ -39,179 +40,181 @@ class NewItem(NewBaseModel):
|
||||
|
||||
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")
|
||||
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("/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("/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("/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("/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/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/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(
|
||||
@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 result
|
||||
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("/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("/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("/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(
|
||||
@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 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=""))
|
||||
return Item(title="", size=0, sub=SubItem(name=""))
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
@@ -1,140 +1,137 @@
|
||||
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("/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("/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/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(
|
||||
@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 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],
|
||||
new_title="", new_size=0, new_sub=modelsv2.SubItem(new_sub_name="")
|
||||
)
|
||||
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/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-list")
|
||||
def handle_v2_list_to_v1_list(data: list[modelsv2.Item]) -> list[modelsv1.Item]:
|
||||
result = []
|
||||
for item in data:
|
||||
result.append(
|
||||
modelsv1.Item(
|
||||
@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 result
|
||||
return modelsv1.Item(title="", size=0, sub=modelsv1.SubItem(name=""))
|
||||
|
||||
|
||||
@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]
|
||||
@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=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],
|
||||
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],
|
||||
)
|
||||
return modelsv1.Item(title="", size=0, sub=modelsv1.SubItem(name=""))
|
||||
|
||||
|
||||
@app.post("/v2-to-v1/same-name")
|
||||
def handle_v2_same_name_to_v1(
|
||||
item1: modelsv2.Item, item2: modelsv2b.Item
|
||||
) -> modelsv1.Item:
|
||||
return modelsv1.Item(
|
||||
title=item1.new_title,
|
||||
size=item2.dup_size,
|
||||
description=item1.new_description,
|
||||
sub=modelsv1.SubItem(name=item1.new_sub.new_sub_name),
|
||||
multi=[modelsv1.SubItem(name=s.dup_sub_name) for s in item2.dup_multi],
|
||||
)
|
||||
|
||||
|
||||
@app.post("/v2-to-v1/list-of-items-to-list-of-items")
|
||||
def handle_v2_items_in_list_to_v1_item_in_list(
|
||||
data1: list[modelsv2.ItemInList], data2: list[modelsv2b.ItemInList]
|
||||
) -> list[modelsv1.ItemInList]:
|
||||
result = []
|
||||
item1 = data1[0]
|
||||
item2 = data2[0]
|
||||
result = [
|
||||
modelsv1.ItemInList(name1=item1.name2),
|
||||
modelsv1.ItemInList(name1=item2.dup_name2),
|
||||
]
|
||||
return result
|
||||
@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),
|
||||
]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import sys
|
||||
import warnings
|
||||
from typing import Any, Union
|
||||
|
||||
from tests.utils import skip_module_if_py_gte_314
|
||||
@@ -39,65 +40,69 @@ class NewItem(NewBaseModel):
|
||||
|
||||
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/")
|
||||
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("/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")
|
||||
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
|
||||
@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)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import warnings
|
||||
from typing import Any
|
||||
|
||||
from fastapi import FastAPI
|
||||
@@ -73,10 +74,13 @@ def test_read_with_orm_mode_pv1() -> None:
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@app.post("/people/", response_model=PersonRead)
|
||||
def create_person(person: PersonCreate) -> Any:
|
||||
db_person = Person.from_orm(person)
|
||||
return db_person
|
||||
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)
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import warnings
|
||||
from typing import Union
|
||||
|
||||
import pytest
|
||||
@@ -521,11 +522,14 @@ def test_invalid_response_model_field_pv1():
|
||||
class Model(v1.BaseModel):
|
||||
foo: str
|
||||
|
||||
with pytest.raises(FastAPIError) as e:
|
||||
with warnings.catch_warnings(record=True):
|
||||
warnings.simplefilter("always")
|
||||
|
||||
@app.get("/")
|
||||
def read_root() -> Union[Response, Model, None]:
|
||||
return Response(content="Foo") # pragma: no cover
|
||||
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]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
from inline_snapshot import snapshot
|
||||
@@ -24,7 +25,13 @@ from ...utils import needs_py310
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}")
|
||||
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,
|
||||
)
|
||||
mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}")
|
||||
|
||||
c = TestClient(mod.app)
|
||||
return c
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
from inline_snapshot import snapshot
|
||||
@@ -24,7 +25,13 @@ from ...utils import needs_py310
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}")
|
||||
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,
|
||||
)
|
||||
mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}")
|
||||
|
||||
c = TestClient(mod.app)
|
||||
return c
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
from inline_snapshot import snapshot
|
||||
@@ -24,7 +25,13 @@ from ...utils import needs_py310
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}")
|
||||
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,
|
||||
)
|
||||
mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}")
|
||||
|
||||
c = TestClient(mod.app)
|
||||
return c
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import importlib
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
@@ -14,7 +15,13 @@ from ...utils import needs_pydanticv1
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.request_form_models.{request.param}")
|
||||
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,
|
||||
)
|
||||
mod = importlib.import_module(f"docs_src.request_form_models.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import importlib
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
@@ -15,7 +16,13 @@ from ...utils import needs_py310, needs_pydanticv1
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.schema_extra_example.{request.param}")
|
||||
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,
|
||||
)
|
||||
mod = importlib.import_module(f"docs_src.schema_extra_example.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
Reference in New Issue
Block a user