mirror of
https://github.com/fastapi/fastapi.git
synced 2025-12-26 07:40:57 -05:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2fadcbc90 | ||
|
|
b3bb29afa8 | ||
|
|
c7db2ff858 |
@@ -1,5 +1,9 @@
|
||||
## Latest changes
|
||||
|
||||
## 0.27.2
|
||||
|
||||
* Fix path and query parameters receiving `dict` as a valid type. It should be mapped to a body payload. PR [#287](https://github.com/tiangolo/fastapi/pull/287). Updated docs at: [Query parameter list / multiple values with defaults: Using `list`](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#using-list).
|
||||
|
||||
## 0.27.1
|
||||
|
||||
* Fix `auto_error=False` handling in `HTTPBearer` security scheme. Do not `raise` when there's an incorrect `Authorization` header if `auto_error=False`. PR [#282](https://github.com/tiangolo/fastapi/pull/282).
|
||||
|
||||
9
docs/src/query_params_str_validations/tutorial013.py
Normal file
9
docs/src/query_params_str_validations/tutorial013.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from fastapi import FastAPI, Query
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/items/")
|
||||
async def read_items(q: list = Query(None)):
|
||||
query_items = {"q": q}
|
||||
return query_items
|
||||
@@ -183,6 +183,19 @@ the default of `q` will be: `["foo", "bar"]` and your response will be:
|
||||
}
|
||||
```
|
||||
|
||||
#### Using `list`
|
||||
|
||||
You can also use `list` directly instead of `List[str]`:
|
||||
|
||||
```Python hl_lines="7"
|
||||
{!./src/query_params_str_validations/tutorial013.py!}
|
||||
```
|
||||
|
||||
!!! note
|
||||
Have in mind that in this case, FastAPI won't check the contents of the list.
|
||||
|
||||
For example, `List[int]` would check (and document) that the contents of the list are integers. But `list` alone wouldn't.
|
||||
|
||||
## Declare more metadata
|
||||
|
||||
You can add more information about the parameter.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
|
||||
|
||||
__version__ = "0.27.1"
|
||||
__version__ = "0.27.2"
|
||||
|
||||
from starlette.background import BackgroundTasks
|
||||
|
||||
|
||||
@@ -127,12 +127,13 @@ def is_scalar_field(field: Field) -> bool:
|
||||
return (
|
||||
field.shape == Shape.SINGLETON
|
||||
and not lenient_issubclass(field.type_, BaseModel)
|
||||
and not lenient_issubclass(field.type_, sequence_types + (dict,))
|
||||
and not isinstance(field.schema, params.Body)
|
||||
)
|
||||
|
||||
|
||||
def is_scalar_sequence_field(field: Field) -> bool:
|
||||
if field.shape in sequence_shapes and not lenient_issubclass(
|
||||
if (field.shape in sequence_shapes) and not lenient_issubclass(
|
||||
field.type_, BaseModel
|
||||
):
|
||||
if field.sub_fields is not None:
|
||||
@@ -140,6 +141,8 @@ def is_scalar_sequence_field(field: Field) -> bool:
|
||||
if not is_scalar_field(sub_field):
|
||||
return False
|
||||
return True
|
||||
if lenient_issubclass(field.type_, sequence_types):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@@ -346,7 +349,7 @@ def request_params_to_args(
|
||||
values = {}
|
||||
errors = []
|
||||
for field in required_params:
|
||||
if field.shape in sequence_shapes and isinstance(
|
||||
if is_scalar_sequence_field(field) and isinstance(
|
||||
received_params, (QueryParams, Headers)
|
||||
):
|
||||
value = received_params.getlist(field.alias) or field.default
|
||||
|
||||
77
tests/test_invalid_path_param.py
Normal file
77
tests/test_invalid_path_param.py
Normal file
@@ -0,0 +1,77 @@
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
def test_invalid_sequence():
|
||||
with pytest.raises(AssertionError):
|
||||
app = FastAPI()
|
||||
|
||||
class Item(BaseModel):
|
||||
title: str
|
||||
|
||||
@app.get("/items/{id}")
|
||||
def read_items(id: List[Item]):
|
||||
pass # pragma: no cover
|
||||
|
||||
|
||||
def test_invalid_tuple():
|
||||
with pytest.raises(AssertionError):
|
||||
app = FastAPI()
|
||||
|
||||
class Item(BaseModel):
|
||||
title: str
|
||||
|
||||
@app.get("/items/{id}")
|
||||
def read_items(id: Tuple[Item, Item]):
|
||||
pass # pragma: no cover
|
||||
|
||||
|
||||
def test_invalid_dict():
|
||||
with pytest.raises(AssertionError):
|
||||
app = FastAPI()
|
||||
|
||||
class Item(BaseModel):
|
||||
title: str
|
||||
|
||||
@app.get("/items/{id}")
|
||||
def read_items(id: Dict[str, Item]):
|
||||
pass # pragma: no cover
|
||||
|
||||
|
||||
def test_invalid_simple_list():
|
||||
with pytest.raises(AssertionError):
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/items/{id}")
|
||||
def read_items(id: list):
|
||||
pass # pragma: no cover
|
||||
|
||||
|
||||
def test_invalid_simple_tuple():
|
||||
with pytest.raises(AssertionError):
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/items/{id}")
|
||||
def read_items(id: tuple):
|
||||
pass # pragma: no cover
|
||||
|
||||
|
||||
def test_invalid_simple_set():
|
||||
with pytest.raises(AssertionError):
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/items/{id}")
|
||||
def read_items(id: set):
|
||||
pass # pragma: no cover
|
||||
|
||||
|
||||
def test_invalid_simple_dict():
|
||||
with pytest.raises(AssertionError):
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/items/{id}")
|
||||
def read_items(id: dict):
|
||||
pass # pragma: no cover
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import List, Tuple
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI, Query
|
||||
@@ -27,3 +27,27 @@ def test_invalid_tuple():
|
||||
@app.get("/items/")
|
||||
def read_items(q: Tuple[Item, Item] = Query(None)):
|
||||
pass # pragma: no cover
|
||||
|
||||
|
||||
def test_invalid_dict():
|
||||
with pytest.raises(AssertionError):
|
||||
app = FastAPI()
|
||||
|
||||
class Item(BaseModel):
|
||||
title: str
|
||||
|
||||
@app.get("/items/")
|
||||
def read_items(q: Dict[str, Item] = Query(None)):
|
||||
pass # pragma: no cover
|
||||
|
||||
|
||||
def test_invalid_simple_dict():
|
||||
with pytest.raises(AssertionError):
|
||||
app = FastAPI()
|
||||
|
||||
class Item(BaseModel):
|
||||
title: str
|
||||
|
||||
@app.get("/items/")
|
||||
def read_items(q: dict = Query(None)):
|
||||
pass # pragma: no cover
|
||||
|
||||
@@ -86,3 +86,10 @@ def test_multi_query_values():
|
||||
response = client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"q": ["foo", "bar"]}
|
||||
|
||||
|
||||
def test_query_no_values():
|
||||
url = "/items/"
|
||||
response = client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"q": None}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from query_params_str_validations.tutorial013 import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
openapi_schema = {
|
||||
"openapi": "3.0.2",
|
||||
"info": {"title": "Fast API", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": {"title": "Q", "type": "array"},
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == openapi_schema
|
||||
|
||||
|
||||
def test_multi_query_values():
|
||||
url = "/items/?q=foo&q=bar"
|
||||
response = client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"q": ["foo", "bar"]}
|
||||
|
||||
|
||||
def test_query_no_values():
|
||||
url = "/items/"
|
||||
response = client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"q": None}
|
||||
Reference in New Issue
Block a user