mirror of
https://github.com/fastapi/fastapi.git
synced 2025-12-26 15:51:02 -05:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d6e70791d | ||
|
|
18819f9850 | ||
|
|
ad65e72d90 | ||
|
|
ec072d75fe | ||
|
|
09acce4b2c | ||
|
|
f6808e76dc | ||
|
|
f8f5281ef5 | ||
|
|
b993b4af28 | ||
|
|
982911f08f | ||
|
|
634cf22584 | ||
|
|
12f60cac7a | ||
|
|
e7b1b96a54 | ||
|
|
a6a39f3009 | ||
|
|
1ff8df6e7f | ||
|
|
7953353a35 | ||
|
|
6c16d6a4f9 | ||
|
|
eb80b79555 |
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user