mirror of
https://github.com/fastapi/fastapi.git
synced 2025-12-25 15:18:36 -05:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2aa3593be | ||
|
|
ed0fcba7cb | ||
|
|
22a155efc1 | ||
|
|
306326a213 | ||
|
|
4fec12b354 | ||
|
|
275306c369 | ||
|
|
4d270463af | ||
|
|
6620273083 | ||
|
|
0b4fe10c8f | ||
|
|
c4007cb9ec | ||
|
|
3ec498af63 | ||
|
|
895789bed0 | ||
|
|
ef5d6181b6 |
@@ -19,7 +19,7 @@ repos:
|
||||
- --py3-plus
|
||||
- --keep-runtime-typing
|
||||
- repo: https://github.com/myint/autoflake
|
||||
rev: v1.5.1
|
||||
rev: v1.5.3
|
||||
hooks:
|
||||
- id: autoflake
|
||||
args:
|
||||
@@ -43,7 +43,7 @@ repos:
|
||||
name: isort (pyi)
|
||||
types: [pyi]
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.6.0
|
||||
rev: 22.8.0
|
||||
hooks:
|
||||
- id: black
|
||||
ci:
|
||||
|
||||
@@ -6,7 +6,7 @@ Learn more about it below. 👇
|
||||
|
||||
## Versions
|
||||
|
||||
The latest versions of FastAPI are supported.
|
||||
The latest version of FastAPI is supported.
|
||||
|
||||
You are encouraged to [write tests](https://fastapi.tiangolo.com/tutorial/testing/) for your application and update your FastAPI version frequently after ensuring that your tests are passing. This way you will benefit from the latest features, bug fixes, and **security fixes**.
|
||||
|
||||
|
||||
@@ -3,6 +3,31 @@
|
||||
## Latest Changes
|
||||
|
||||
|
||||
## 0.83.0
|
||||
|
||||
🚨 This is probably the last release (or one of the last releases) to support Python 3.6. 🔥
|
||||
|
||||
Python 3.6 reached the [end-of-life and is no longer supported by Python](https://www.python.org/downloads/release/python-3615/) since around a year ago.
|
||||
|
||||
You hopefully updated to a supported version of Python a while ago. If you haven't, you really should.
|
||||
|
||||
### Features
|
||||
|
||||
* ✨ Add support in `jsonable_encoder` for include and exclude with dataclasses. PR [#4923](https://github.com/tiangolo/fastapi/pull/4923) by [@DCsunset](https://github.com/DCsunset).
|
||||
|
||||
### Fixes
|
||||
|
||||
* 🐛 Fix `RuntimeError` raised when `HTTPException` has a status code with no content. PR [#5365](https://github.com/tiangolo/fastapi/pull/5365) by [@iudeen](https://github.com/iudeen).
|
||||
* 🐛 Fix empty reponse body when default `status_code` is empty but the a `Response` parameter with `response.status_code` is set. PR [#5360](https://github.com/tiangolo/fastapi/pull/5360) by [@tmeckel](https://github.com/tmeckel).
|
||||
|
||||
### Docs
|
||||
|
||||
* 📝 Update `SECURITY.md`. PR [#5377](https://github.com/tiangolo/fastapi/pull/5377) by [@Kludex](https://github.com/Kludex).
|
||||
|
||||
### Internal
|
||||
|
||||
* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#5352](https://github.com/tiangolo/fastapi/pull/5352) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
|
||||
|
||||
## 0.82.0
|
||||
|
||||
🚨 This is probably the last release (or one of the last releases) to support Python 3.6. 🔥
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
|
||||
|
||||
__version__ = "0.82.0"
|
||||
__version__ = "0.83.0"
|
||||
|
||||
from starlette import status as status
|
||||
|
||||
|
||||
@@ -74,8 +74,12 @@ def jsonable_encoder(
|
||||
obj_dict = dataclasses.asdict(obj)
|
||||
return jsonable_encoder(
|
||||
obj_dict,
|
||||
exclude_none=exclude_none,
|
||||
include=include,
|
||||
exclude=exclude,
|
||||
by_alias=by_alias,
|
||||
exclude_unset=exclude_unset,
|
||||
exclude_defaults=exclude_defaults,
|
||||
exclude_none=exclude_none,
|
||||
custom_encoder=custom_encoder,
|
||||
sqlalchemy_safe=sqlalchemy_safe,
|
||||
)
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.utils import is_body_allowed_for_status_code
|
||||
from starlette.exceptions import HTTPException
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import JSONResponse
|
||||
from starlette.responses import JSONResponse, Response
|
||||
from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
|
||||
async def http_exception_handler(request: Request, exc: HTTPException) -> JSONResponse:
|
||||
async def http_exception_handler(request: Request, exc: HTTPException) -> Response:
|
||||
headers = getattr(exc, "headers", None)
|
||||
if headers:
|
||||
return JSONResponse(
|
||||
{"detail": exc.detail}, status_code=exc.status_code, headers=headers
|
||||
)
|
||||
else:
|
||||
return JSONResponse({"detail": exc.detail}, status_code=exc.status_code)
|
||||
if not is_body_allowed_for_status_code(exc.status_code):
|
||||
return Response(status_code=exc.status_code, headers=headers)
|
||||
return JSONResponse(
|
||||
{"detail": exc.detail}, status_code=exc.status_code, headers=headers
|
||||
)
|
||||
|
||||
|
||||
async def request_validation_exception_handler(
|
||||
|
||||
@@ -258,7 +258,7 @@ def get_request_handler(
|
||||
is_coroutine=is_coroutine,
|
||||
)
|
||||
response = actual_response_class(content, **response_args)
|
||||
if not is_body_allowed_for_status_code(status_code):
|
||||
if not is_body_allowed_for_status_code(response.status_code):
|
||||
response.body = b""
|
||||
response.headers.raw.extend(sub_response.headers.raw)
|
||||
return response
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
from enum import Enum
|
||||
from pathlib import PurePath, PurePosixPath, PureWindowsPath
|
||||
@@ -19,6 +20,12 @@ class Pet:
|
||||
self.name = name
|
||||
|
||||
|
||||
@dataclass
|
||||
class Item:
|
||||
name: str
|
||||
count: int
|
||||
|
||||
|
||||
class DictablePerson(Person):
|
||||
def __iter__(self):
|
||||
return ((k, v) for k, v in self.__dict__.items())
|
||||
@@ -131,6 +138,15 @@ def test_encode_dictable():
|
||||
}
|
||||
|
||||
|
||||
def test_encode_dataclass():
|
||||
item = Item(name="foo", count=100)
|
||||
assert jsonable_encoder(item) == {"name": "foo", "count": 100}
|
||||
assert jsonable_encoder(item, include={"name"}) == {"name": "foo"}
|
||||
assert jsonable_encoder(item, exclude={"count"}) == {"name": "foo"}
|
||||
assert jsonable_encoder(item, include={}) == {}
|
||||
assert jsonable_encoder(item, exclude={}) == {"name": "foo", "count": 100}
|
||||
|
||||
|
||||
def test_encode_unsupported():
|
||||
unserializable = Unserializable()
|
||||
with pytest.raises(ValueError):
|
||||
|
||||
97
tests/test_reponse_set_reponse_code_empty.py
Normal file
97
tests/test_reponse_set_reponse_code_empty.py
Normal file
@@ -0,0 +1,97 @@
|
||||
from typing import Any
|
||||
|
||||
from fastapi import FastAPI, Response
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.delete(
|
||||
"/{id}",
|
||||
status_code=204,
|
||||
)
|
||||
async def delete_deployment(
|
||||
id: int,
|
||||
response: Response,
|
||||
) -> Any:
|
||||
response.status_code = 400
|
||||
return {"msg": "Status overwritten", "id": id}
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
openapi_schema = {
|
||||
"openapi": "3.0.2",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/{id}": {
|
||||
"delete": {
|
||||
"summary": "Delete Deployment",
|
||||
"operationId": "delete_deployment__id__delete",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Id", "type": "integer"},
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {"description": "Successful Response"},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == openapi_schema
|
||||
|
||||
|
||||
def test_dependency_set_status_code():
|
||||
response = client.delete("/1")
|
||||
assert response.status_code == 400 and response.content
|
||||
assert response.json() == {"msg": "Status overwritten", "id": 1}
|
||||
@@ -18,6 +18,16 @@ async def read_item(item_id: str):
|
||||
return {"item": items[item_id]}
|
||||
|
||||
|
||||
@app.get("/http-no-body-statuscode-exception")
|
||||
async def no_body_status_code_exception():
|
||||
raise HTTPException(status_code=204)
|
||||
|
||||
|
||||
@app.get("/http-no-body-statuscode-with-detail-exception")
|
||||
async def no_body_status_code_with_detail_exception():
|
||||
raise HTTPException(status_code=204, detail="I should just disappear!")
|
||||
|
||||
|
||||
@app.get("/starlette-items/{item_id}")
|
||||
async def read_starlette_item(item_id: str):
|
||||
if item_id not in items:
|
||||
@@ -31,6 +41,30 @@ openapi_schema = {
|
||||
"openapi": "3.0.2",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/http-no-body-statuscode-exception": {
|
||||
"get": {
|
||||
"operationId": "no_body_status_code_exception_http_no_body_statuscode_exception_get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
"description": "Successful " "Response",
|
||||
}
|
||||
},
|
||||
"summary": "No Body " "Status " "Code " "Exception",
|
||||
}
|
||||
},
|
||||
"/http-no-body-statuscode-with-detail-exception": {
|
||||
"get": {
|
||||
"operationId": "no_body_status_code_with_detail_exception_http_no_body_statuscode_with_detail_exception_get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
"description": "Successful " "Response",
|
||||
}
|
||||
},
|
||||
"summary": "No Body Status Code With Detail Exception",
|
||||
}
|
||||
},
|
||||
"/items/{item_id}": {
|
||||
"get": {
|
||||
"responses": {
|
||||
@@ -154,3 +188,15 @@ def test_get_starlette_item_not_found():
|
||||
assert response.status_code == 404, response.text
|
||||
assert response.headers.get("x-error") is None
|
||||
assert response.json() == {"detail": "Item not found"}
|
||||
|
||||
|
||||
def test_no_body_status_code_exception_handlers():
|
||||
response = client.get("/http-no-body-statuscode-exception")
|
||||
assert response.status_code == 204
|
||||
assert not response.content
|
||||
|
||||
|
||||
def test_no_body_status_code_with_detail_exception_handlers():
|
||||
response = client.get("/http-no-body-statuscode-with-detail-exception")
|
||||
assert response.status_code == 204
|
||||
assert not response.content
|
||||
|
||||
Reference in New Issue
Block a user