Compare commits

...

17 Commits

Author SHA1 Message Date
Sebastián Ramírez
7d6e70791d 🔖 Release version 0.80.0 2022-08-23 16:14:48 +02:00
Sebastián Ramírez
18819f9850 📝 Update release notes 2022-08-23 16:13:37 +02:00
github-actions
ad65e72d90 📝 Update release notes 2022-08-23 13:58:06 +00:00
Teo Koon Peng
ec072d75fe ⬆ Upgrade Swagger UI copy of oauth2-redirect.html to include fixes for flavors of authorization code flows in Swagger UI (#3439)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-23 13:57:25 +00:00
github-actions
09acce4b2c 📝 Update release notes 2022-08-23 13:48:07 +00:00
Andrey Semakin
f6808e76dc ♻ Strip empty whitespace from description extracted from docstrings (#2821)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-23 13:47:19 +00:00
github-actions
f8f5281ef5 📝 Update release notes 2022-08-23 13:31:17 +00:00
laggardkernel
b993b4af28 🐛 Fix cached dependencies when using a dependency in Security() and other places (e.g. Depends()) with different OAuth2 scopes (#2945)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-23 13:30:24 +00:00
github-actions
982911f08f 📝 Update release notes 2022-08-22 19:18:24 +00:00
Taneli Hukkinen
634cf22584 🐛 Fix response_model not invalidating None (#2725)
Co-authored-by: Taneli Hukkinen <hukkinj1@users.noreply.github.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-22 19:17:45 +00:00
github-actions
12f60cac7a 📝 Update release notes 2022-08-22 18:49:42 +00:00
Sebastián Ramírez
e7b1b96a54 🎨 Update type annotations for response_model, allow things like Union[str, None] (#5294) 2022-08-22 18:49:03 +00:00
github-actions
a6a39f3009 📝 Update release notes 2022-08-22 18:35:01 +00:00
0xflotus
1ff8df6e7f 🌐 Fix typos in German translation for docs/de/docs/features.md (#4533)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-22 18:33:10 +00:00
github-actions
7953353a35 📝 Update release notes 2022-08-22 14:53:35 +00:00
Joona Yoon
6c16d6a4f9 🌐 Add missing navigator for encoder.md in Korean translation (#5238) 2022-08-22 16:52:50 +02:00
github-actions
eb80b79555 📝 Update release notes 2022-08-22 12:44:46 +00:00
12 changed files with 209 additions and 53 deletions

View File

@@ -97,7 +97,7 @@ Hierdurch werden Sie nie wieder einen falschen Schlüsselnamen benutzen und spar
### Kompakt
FastAPI nutzt für alles sinnvolle **Standard-Einstellungen**, welche optional überall konfiguriert werden können. Alle Parameter können ganz genau an Ihre Bedürfnisse angepasst werden, sodass sie genau die API definieren können, die sie brauchen.
FastAPI nutzt für alles sensible **Standard-Einstellungen**, welche optional überall konfiguriert werden können. Alle Parameter können ganz genau an Ihre Bedürfnisse angepasst werden, sodass sie genau die API definieren können, die sie brauchen.
Aber standardmäßig, **"funktioniert einfach"** alles.
@@ -119,9 +119,9 @@ Die gesamte Validierung übernimmt das etablierte und robuste **Pydantic**.
### Sicherheit und Authentifizierung
Sicherheit und Authentifizierung integriert. Ohne einen Kompromiss aufgrund einer Datenbank oder den Datenentitäten.
Integrierte Sicherheit und Authentifizierung. Ohne Kompromisse bei Datenbanken oder Datenmodellen.
Unterstützt alle von OpenAPI definierten Sicherheitsschemata, hierzu gehören:
Unterstützt werden alle von OpenAPI definierten Sicherheitsschemata, hierzu gehören:
* HTTP Basis Authentifizierung.
* **OAuth2** (auch mit **JWT Zugriffstokens**). Schauen Sie sich hierzu dieses Tutorial an: [OAuth2 mit JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}.
@@ -142,8 +142,8 @@ FastAPI enthält ein extrem einfaches, aber extrem mächtiges <abbr title='oft v
* **Automatische Umsetzung** durch FastAPI.
* Alle abhängigen Komponenten könnten Daten von Anfragen, **Erweiterungen der Pfadoperations-**Einschränkungen und der automatisierten Dokumentation benötigen.
* **Automatische Validierung** selbst für *Pfadoperationen*-Parameter, die in den Abhängigkeiten definiert wurden.
* Unterstützt komplexe Benutzerauthentifizierungssysteme, mit **Datenbankverbindungen**, usw.
* **Keine Kompromisse** bei Datenbanken, Eingabemasken, usw., sondern einfache Integration von allen.
* Unterstützt komplexe Benutzerauthentifizierungssysteme, **Datenbankverbindungen**, usw.
* **Keine Kompromisse** bei Datenbanken, Eingabemasken, usw. Sondern einfache Integration von allen.
### Unbegrenzte Erweiterungen
@@ -165,7 +165,7 @@ Jede Integration wurde so entworfen, dass sie einfach zu nutzen ist (mit Abhäng
Mit **FastAPI** bekommen Sie viele von **Starlette**'s Funktionen (da FastAPI nur Starlette auf Steroiden ist):
* Stark beeindruckende Performanz. Es ist <a href="https://github.com/encode/starlette#performance" class="external-link" target="_blank">eines der schnellsten Python frameworks, auf Augenhöhe mit **NodeJS** und **Go**</a>.
* Stark beeindruckende Performanz. Es ist <a href="https://github.com/encode/starlette#performance" class="external-link" target="_blank">eines der schnellsten Python Frameworks, auf Augenhöhe mit **NodeJS** und **Go**</a>.
* **WebSocket**-Unterstützung.
* Hintergrundaufgaben im selben Prozess.
* Ereignisse für das Starten und Herunterfahren.
@@ -196,8 +196,8 @@ Mit **FastAPI** bekommen Sie alle Funktionen von **Pydantic** (da FastAPI für d
* In <a href="https://pydantic-docs.helpmanual.io/benchmarks/" class="external-link" target="_blank">Vergleichen</a> ist Pydantic schneller als jede andere getestete Bibliothek.
* Validierung von **komplexen Strukturen**:
* Benutzung von hierachischen Pydantic Schemata, Python `typing`s `List` und `Dict`, etc.
* Validierungen erlauben klare und einfache Datenschemadefinition, überprüft und dokumentiert als JSON Schema.
* Validierungen erlauben eine klare und einfache Datenschemadefinition, überprüft und dokumentiert als JSON Schema.
* Sie können stark **verschachtelte JSON** Objekte haben und diese sind trotzdem validiert und annotiert.
* **Erweiterbar**:
* Pydantic erlaubt die Definition von eigenen Datentypen oder Sie können die Validierung mit einer `validator` dekorierten Methode erweitern..
* Pydantic erlaubt die Definition von eigenen Datentypen oder sie können die Validierung mit einer `validator` dekorierten Methode erweitern.
* 100% Testabdeckung.

View File

@@ -3,6 +3,89 @@
## Latest Changes
## 0.80.0
### Breaking Changes - Fixes
* 🐛 Fix `response_model` not invalidating `None`. PR [#2725](https://github.com/tiangolo/fastapi/pull/2725) by [@hukkin](https://github.com/hukkin).
If you are using `response_model` with some type that doesn't include `None` but the function is returning `None`, it will now raise an internal server error, because you are returning invalid data that violates the contract in `response_model`. Before this release it would allow breaking that contract returning `None`.
For example, if you have an app like this:
```Python
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
price: Optional[float] = None
owner_ids: Optional[List[int]] = None
app = FastAPI()
@app.get("/items/invalidnone", response_model=Item)
def get_invalid_none():
return None
```
...calling the path `/items/invalidnone` will raise an error, because `None` is not a valid type for the `response_model` declared with `Item`.
You could also be implicitly returning `None` without realizing, for example:
```Python
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
price: Optional[float] = None
owner_ids: Optional[List[int]] = None
app = FastAPI()
@app.get("/items/invalidnone", response_model=Item)
def get_invalid_none():
if flag:
return {"name": "foo"}
# if flag is False, at this point the function will implicitly return None
```
If you have *path operations* using `response_model` that need to be allowed to return `None`, make it explicit in `response_model` using `Union[Something, None]`:
```Python
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
price: Optional[float] = None
owner_ids: Optional[List[int]] = None
app = FastAPI()
@app.get("/items/invalidnone", response_model=Union[Item, None])
def get_invalid_none():
return None
```
This way the data will be correctly validated, you won't have an internal server error, and the documentation will also reflect that this *path operation* could return `None` (or `null` in JSON).
### Fixes
* ⬆ Upgrade Swagger UI copy of `oauth2-redirect.html` to include fixes for flavors of authorization code flows in Swagger UI. PR [#3439](https://github.com/tiangolo/fastapi/pull/3439) initial PR by [@koonpeng](https://github.com/koonpeng).
* ♻ Strip empty whitespace from description extracted from docstrings. PR [#2821](https://github.com/tiangolo/fastapi/pull/2821) by [@and-semakin](https://github.com/and-semakin).
* 🐛 Fix cached dependencies when using a dependency in `Security()` and other places (e.g. `Depends()`) with different OAuth2 scopes. PR [#2945](https://github.com/tiangolo/fastapi/pull/2945) by [@laggardkernel](https://github.com/laggardkernel).
* 🎨 Update type annotations for `response_model`, allow things like `Union[str, None]`. PR [#5294](https://github.com/tiangolo/fastapi/pull/5294) by [@tiangolo](https://github.com/tiangolo).
### Translations
* 🌐 Fix typos in German translation for `docs/de/docs/features.md`. PR [#4533](https://github.com/tiangolo/fastapi/pull/4533) by [@0xflotus](https://github.com/0xflotus).
* 🌐 Add missing navigator for `encoder.md` in Korean translation. PR [#5238](https://github.com/tiangolo/fastapi/pull/5238) by [@joonas-yoon](https://github.com/joonas-yoon).
* (Empty PR merge by accident) [#4913](https://github.com/tiangolo/fastapi/pull/4913).
## 0.79.1
### Fixes

View File

@@ -68,6 +68,7 @@ nav:
- tutorial/response-status-code.md
- tutorial/request-files.md
- tutorial/request-forms-and-files.md
- tutorial/encoder.md
markdown_extensions:
- toc:
permalink: true

View File

@@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
__version__ = "0.79.1"
__version__ = "0.80.0"
from starlette import status as status

View File

@@ -273,7 +273,7 @@ class FastAPI(Starlette):
path: str,
endpoint: Callable[..., Coroutine[Any, Any, Response]],
*,
response_model: Optional[Type[Any]] = None,
response_model: Any = None,
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
@@ -331,7 +331,7 @@ class FastAPI(Starlette):
self,
path: str,
*,
response_model: Optional[Type[Any]] = None,
response_model: Any = None,
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
@@ -434,7 +434,7 @@ class FastAPI(Starlette):
self,
path: str,
*,
response_model: Optional[Type[Any]] = None,
response_model: Any = None,
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
@@ -489,7 +489,7 @@ class FastAPI(Starlette):
self,
path: str,
*,
response_model: Optional[Type[Any]] = None,
response_model: Any = None,
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
@@ -544,7 +544,7 @@ class FastAPI(Starlette):
self,
path: str,
*,
response_model: Optional[Type[Any]] = None,
response_model: Any = None,
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
@@ -599,7 +599,7 @@ class FastAPI(Starlette):
self,
path: str,
*,
response_model: Optional[Type[Any]] = None,
response_model: Any = None,
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
@@ -654,7 +654,7 @@ class FastAPI(Starlette):
self,
path: str,
*,
response_model: Optional[Type[Any]] = None,
response_model: Any = None,
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
@@ -709,7 +709,7 @@ class FastAPI(Starlette):
self,
path: str,
*,
response_model: Optional[Type[Any]] = None,
response_model: Any = None,
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
@@ -764,7 +764,7 @@ class FastAPI(Starlette):
self,
path: str,
*,
response_model: Optional[Type[Any]] = None,
response_model: Any = None,
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
@@ -819,7 +819,7 @@ class FastAPI(Starlette):
self,
path: str,
*,
response_model: Optional[Type[Any]] = None,
response_model: Any = None,
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,

View File

@@ -161,7 +161,6 @@ def get_sub_dependant(
)
if security_requirement:
sub_dependant.security_requirements.append(security_requirement)
sub_dependant.security_scopes = security_scopes
return sub_dependant
@@ -278,7 +277,13 @@ def get_dependant(
path_param_names = get_path_param_names(path)
endpoint_signature = get_typed_signature(call)
signature_params = endpoint_signature.parameters
dependant = Dependant(call=call, name=name, path=path, use_cache=use_cache)
dependant = Dependant(
call=call,
name=name,
path=path,
security_scopes=security_scopes,
use_cache=use_cache,
)
for param_name, param in signature_params.items():
if isinstance(param.default, params.Depends):
sub_dependant = get_param_sub_dependant(
@@ -495,7 +500,6 @@ async def solve_dependencies(
name=sub_dependant.name,
security_scopes=sub_dependant.security_scopes,
)
use_sub_dependant.security_scopes = sub_dependant.security_scopes
solved_result = await solve_dependencies(
request=request,

View File

@@ -115,12 +115,14 @@ def get_redoc_html(
def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse:
# copied from https://github.com/swagger-api/swagger-ui/blob/v4.14.0/dist/oauth2-redirect.html
html = """
<!DOCTYPE html>
<!doctype html>
<html lang="en-US">
<body onload="run()">
</body>
</html>
<head>
<title>Swagger UI: OAuth2 Redirect</title>
</head>
<body>
<script>
'use strict';
function run () {
@@ -130,31 +132,32 @@ def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse:
var isValid, qp, arr;
if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1);
qp = window.location.hash.substring(1).replace('?', '&');
} else {
qp = location.search.substring(1);
}
arr = qp.split("&")
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
arr = qp.split("&");
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value)
return key === "" ? value : decodeURIComponent(value);
}
) : {}
) : {};
isValid = qp.state === sentState
isValid = qp.state === sentState;
if ((
oauth2.auth.schema.get("flow") === "accessCode"||
oauth2.auth.schema.get("flow") === "authorizationCode"
oauth2.auth.schema.get("flow") === "accessCode" ||
oauth2.auth.schema.get("flow") === "authorizationCode" ||
oauth2.auth.schema.get("flow") === "authorization_code"
) && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
});
}
@@ -163,7 +166,7 @@ def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse:
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
let oauthErrorMsg
let oauthErrorMsg;
if (qp.error) {
oauthErrorMsg = "["+qp.error+"]: " +
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
@@ -174,7 +177,7 @@ def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse:
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
});
}
} else {
@@ -182,6 +185,16 @@ def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse:
}
window.close();
}
if (document.readyState !== 'loading') {
run();
} else {
document.addEventListener('DOMContentLoaded', function () {
run();
});
}
</script>
</body>
</html>
"""
return HTMLResponse(content=html)

View File

@@ -315,7 +315,7 @@ class APIRoute(routing.Route):
path: str,
endpoint: Callable[..., Any],
*,
response_model: Optional[Type[Any]] = None,
response_model: Any = None,
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
@@ -409,7 +409,7 @@ class APIRoute(routing.Route):
self.description = description or inspect.cleandoc(self.endpoint.__doc__ or "")
# if a "form feed" character (page break) is found in the description text,
# truncate description text to the content preceding the first "form feed"
self.description = self.description.split("\f")[0]
self.description = self.description.split("\f")[0].strip()
response_fields = {}
for additional_status_code, response in self.responses.items():
assert isinstance(response, dict), "An additional response must be a dict"
@@ -511,7 +511,7 @@ class APIRouter(routing.Router):
path: str,
endpoint: Callable[..., Any],
*,
response_model: Optional[Type[Any]] = None,
response_model: Any = None,
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
@@ -592,7 +592,7 @@ class APIRouter(routing.Router):
self,
path: str,
*,
response_model: Optional[Type[Any]] = None,
response_model: Any = None,
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
@@ -787,7 +787,7 @@ class APIRouter(routing.Router):
self,
path: str,
*,
response_model: Optional[Type[Any]] = None,
response_model: Any = None,
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
@@ -843,7 +843,7 @@ class APIRouter(routing.Router):
self,
path: str,
*,
response_model: Optional[Type[Any]] = None,
response_model: Any = None,
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
@@ -899,7 +899,7 @@ class APIRouter(routing.Router):
self,
path: str,
*,
response_model: Optional[Type[Any]] = None,
response_model: Any = None,
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
@@ -955,7 +955,7 @@ class APIRouter(routing.Router):
self,
path: str,
*,
response_model: Optional[Type[Any]] = None,
response_model: Any = None,
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
@@ -1011,7 +1011,7 @@ class APIRouter(routing.Router):
self,
path: str,
*,
response_model: Optional[Type[Any]] = None,
response_model: Any = None,
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
@@ -1067,7 +1067,7 @@ class APIRouter(routing.Router):
self,
path: str,
*,
response_model: Optional[Type[Any]] = None,
response_model: Any = None,
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
@@ -1123,7 +1123,7 @@ class APIRouter(routing.Router):
self,
path: str,
*,
response_model: Optional[Type[Any]] = None,
response_model: Any = None,
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
@@ -1179,7 +1179,7 @@ class APIRouter(routing.Router):
self,
path: str,
*,
response_model: Optional[Type[Any]] = None,
response_model: Any = None,
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,

View File

@@ -50,7 +50,7 @@ def create_response_field(
type_: Type[Any],
class_validators: Optional[Dict[str, Validator]] = None,
default: Optional[Any] = None,
required: Union[bool, UndefinedType] = False,
required: Union[bool, UndefinedType] = True,
model_config: Type[BaseConfig] = BaseConfig,
field_info: Optional[FieldInfo] = None,
alias: Optional[str] = None,

View File

@@ -1,4 +1,4 @@
from fastapi import Depends, FastAPI
from fastapi import Depends, FastAPI, Security
from fastapi.testclient import TestClient
app = FastAPI()
@@ -35,6 +35,19 @@ async def get_sub_counter_no_cache(
return {"counter": count, "subcounter": subcount}
@app.get("/scope-counter")
async def get_scope_counter(
count: int = Security(dep_counter),
scope_count_1: int = Security(dep_counter, scopes=["scope"]),
scope_count_2: int = Security(dep_counter, scopes=["scope"]),
):
return {
"counter": count,
"scope_counter_1": scope_count_1,
"scope_counter_2": scope_count_2,
}
client = TestClient(app)
@@ -66,3 +79,13 @@ def test_sub_counter_no_cache():
response = client.get("/sub-counter-no-cache/")
assert response.status_code == 200, response.text
assert response.json() == {"counter": 4, "subcounter": 3}
def test_security_cache():
counter_holder["counter"] = 0
response = client.get("/scope-counter/")
assert response.status_code == 200, response.text
assert response.json() == {"counter": 1, "scope_counter_1": 2, "scope_counter_2": 2}
response = client.get("/scope-counter/")
assert response.status_code == 200, response.text
assert response.json() == {"counter": 3, "scope_counter_1": 4, "scope_counter_2": 4}

View File

@@ -31,7 +31,7 @@ openapi_schema = {
},
},
"summary": "Create an item",
"description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item\n",
"description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item",
"operationId": "create_item_items__post",
"requestBody": {
"content": {

View File

@@ -1,4 +1,4 @@
from typing import List, Optional
from typing import List, Optional, Union
import pytest
from fastapi import FastAPI
@@ -19,6 +19,19 @@ def get_invalid():
return {"name": "invalid", "price": "foo"}
@app.get("/items/invalidnone", response_model=Item)
def get_invalid_none():
return None
@app.get("/items/validnone", response_model=Union[Item, None])
def get_valid_none(send_none: bool = False):
if send_none:
return None
else:
return {"name": "invalid", "price": 3.2}
@app.get("/items/innerinvalid", response_model=Item)
def get_innerinvalid():
return {"name": "double invalid", "price": "foo", "owner_ids": ["foo", "bar"]}
@@ -41,6 +54,25 @@ def test_invalid():
client.get("/items/invalid")
def test_invalid_none():
with pytest.raises(ValidationError):
client.get("/items/invalidnone")
def test_valid_none_data():
response = client.get("/items/validnone")
data = response.json()
assert response.status_code == 200
assert data == {"name": "invalid", "price": 3.2, "owner_ids": None}
def test_valid_none_none():
response = client.get("/items/validnone", params={"send_none": "true"})
data = response.json()
assert response.status_code == 200
assert data is None
def test_double_invalid():
with pytest.raises(ValidationError):
client.get("/items/innerinvalid")