From 97361b02bebc3a31d971fe3d8e04ba05591c3506 Mon Sep 17 00:00:00 2001 From: Volodymyr Kochetkov Date: Thu, 28 Dec 2023 10:01:33 +0200 Subject: [PATCH 1/7] feat: issue-2008 fix tests --- fastapi/applications.py | 76 ++++++++++++++++++++++++++++ fastapi/routing.py | 107 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+) diff --git a/fastapi/applications.py b/fastapi/applications.py index 3021d7593..b41d7f92b 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -1136,6 +1136,7 @@ class FastAPI(Starlette): generate_unique_id_function: Callable[[routing.APIRoute], str] = Default( generate_unique_id ), + add_auto_options_route: bool = False, ) -> None: self.router.add_api_route( path, @@ -1162,6 +1163,7 @@ class FastAPI(Starlette): name=name, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + add_auto_options_route=add_auto_options_route, ) def api_route( @@ -1192,6 +1194,7 @@ class FastAPI(Starlette): generate_unique_id_function: Callable[[routing.APIRoute], str] = Default( generate_unique_id ), + add_auto_options_route: bool = False, ) -> Callable[[DecoratedCallable], DecoratedCallable]: def decorator(func: DecoratedCallable) -> DecoratedCallable: self.router.add_api_route( @@ -1219,6 +1222,7 @@ class FastAPI(Starlette): name=name, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + add_auto_options_route=add_auto_options_route, ) return func @@ -1840,6 +1844,14 @@ class FastAPI(Starlette): """ ), ] = Default(generate_unique_id), + add_auto_options_route: Annotated[ + Optional[bool], + Doc( + """ + Auto create options route. + """ + ), + ] = False, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP GET operation. @@ -1880,6 +1892,7 @@ class FastAPI(Starlette): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + add_auto_options_route=add_auto_options_route, ) def put( @@ -2213,6 +2226,14 @@ class FastAPI(Starlette): """ ), ] = Default(generate_unique_id), + add_auto_options_route: Annotated[ + Optional[bool], + Doc( + """ + Auto create options route. + """ + ), + ] = False, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP PUT operation. @@ -2258,6 +2279,7 @@ class FastAPI(Starlette): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + add_auto_options_route=add_auto_options_route, ) def post( @@ -2591,6 +2613,14 @@ class FastAPI(Starlette): """ ), ] = Default(generate_unique_id), + add_auto_options_route: Annotated[ + Optional[bool], + Doc( + """ + Auto create options route. + """ + ), + ] = False, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP POST operation. @@ -2636,6 +2666,7 @@ class FastAPI(Starlette): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + add_auto_options_route=add_auto_options_route, ) def delete( @@ -2969,6 +3000,14 @@ class FastAPI(Starlette): """ ), ] = Default(generate_unique_id), + add_auto_options_route: Annotated[ + Optional[bool], + Doc( + """ + Auto create options route. + """ + ), + ] = False, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP DELETE operation. @@ -3009,6 +3048,7 @@ class FastAPI(Starlette): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + add_auto_options_route=add_auto_options_route, ) def options( @@ -3342,6 +3382,14 @@ class FastAPI(Starlette): """ ), ] = Default(generate_unique_id), + add_auto_options_route: Annotated[ + Optional[bool], + Doc( + """ + Auto create options route. + """ + ), + ] = False, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP OPTIONS operation. @@ -3382,6 +3430,7 @@ class FastAPI(Starlette): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + add_auto_options_route=add_auto_options_route, ) def head( @@ -3715,6 +3764,14 @@ class FastAPI(Starlette): """ ), ] = Default(generate_unique_id), + add_auto_options_route: Annotated[ + Optional[bool], + Doc( + """ + Auto create options route. + """ + ), + ] = False, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP HEAD operation. @@ -3755,6 +3812,7 @@ class FastAPI(Starlette): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + add_auto_options_route=add_auto_options_route, ) def patch( @@ -4088,6 +4146,14 @@ class FastAPI(Starlette): """ ), ] = Default(generate_unique_id), + add_auto_options_route: Annotated[ + Optional[bool], + Doc( + """ + Auto create options route. + """ + ), + ] = False, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP PATCH operation. @@ -4133,6 +4199,7 @@ class FastAPI(Starlette): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + add_auto_options_route=add_auto_options_route, ) def trace( @@ -4466,6 +4533,14 @@ class FastAPI(Starlette): """ ), ] = Default(generate_unique_id), + add_auto_options_route: Annotated[ + Optional[bool], + Doc( + """ + Auto create options route. + """ + ), + ] = False, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP TRACE operation. @@ -4506,6 +4581,7 @@ class FastAPI(Starlette): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + add_auto_options_route=add_auto_options_route, ) def websocket_route( diff --git a/fastapi/routing.py b/fastapi/routing.py index 54d53bbbf..e4b84e6cb 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -397,6 +397,7 @@ class APIRoute(routing.Route): generate_unique_id_function: Union[ Callable[["APIRoute"], str], DefaultPlaceholder ] = Default(generate_unique_id), + is_auto_options: bool = False, ) -> None: self.path = path self.endpoint = endpoint @@ -495,6 +496,7 @@ class APIRoute(routing.Route): ) self.body_field = get_body_field(dependant=self.dependant, name=self.unique_id) self.app = request_response(self.get_route_handler()) + self.is_auto_options = is_auto_options def get_route_handler(self) -> Callable[[Request], Coroutine[Any, Any, Response]]: return get_request_handler( @@ -838,6 +840,7 @@ class APIRouter(routing.Router): generate_unique_id_function: Union[ Callable[[APIRoute], str], DefaultPlaceholder ] = Default(generate_unique_id), + add_auto_options_route: bool = False, ) -> None: route_class = route_class_override or self.route_class responses = responses or {} @@ -886,6 +889,36 @@ class APIRouter(routing.Router): generate_unique_id_function=current_generate_unique_id, ) self.routes.append(route) + if add_auto_options_route: + self._update_auto_options_routes(route, path) + + def _update_auto_options_routes(self, new_route: APIRoute, path: str) -> None: + auto_options_index: Optional[int] = None + allowed_methods: Set[str] = set() + for index, route in enumerate(self.routes): + if route.path == new_route.path: + if hasattr(route, "is_auto_options") and route.is_auto_options: + auto_options_index = index + else: + allowed_methods.update(route.methods) + + if auto_options_index is not None: + self.routes.pop(auto_options_index) + + if "OPTIONS" not in new_route.methods: + + async def options_route(): + return Response(headers={"Allow": ", ".join(allowed_methods)}) + + self.routes.append( + APIRoute( + self.prefix + path, + endpoint=options_route, + methods=["OPTIONS"], + include_in_schema=False, + is_auto_options=True, + ) + ) def api_route( self, @@ -916,6 +949,7 @@ class APIRouter(routing.Router): generate_unique_id_function: Callable[[APIRoute], str] = Default( generate_unique_id ), + add_auto_options_route: bool = False, ) -> Callable[[DecoratedCallable], DecoratedCallable]: def decorator(func: DecoratedCallable) -> DecoratedCallable: self.add_api_route( @@ -944,6 +978,7 @@ class APIRouter(routing.Router): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + add_auto_options_route=add_auto_options_route, ) return func @@ -1617,6 +1652,14 @@ class APIRouter(routing.Router): """ ), ] = Default(generate_unique_id), + add_auto_options_route: Annotated[ + Optional[bool], + Doc( + """ + Auto create options route. + """ + ), + ] = False, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP GET operation. @@ -1661,6 +1704,7 @@ class APIRouter(routing.Router): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + add_auto_options_route=add_auto_options_route, ) def put( @@ -1994,6 +2038,14 @@ class APIRouter(routing.Router): """ ), ] = Default(generate_unique_id), + add_auto_options_route: Annotated[ + Optional[bool], + Doc( + """ + Auto create options route. + """ + ), + ] = False, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP PUT operation. @@ -2043,6 +2095,7 @@ class APIRouter(routing.Router): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + add_auto_options_route=add_auto_options_route, ) def post( @@ -2376,6 +2429,14 @@ class APIRouter(routing.Router): """ ), ] = Default(generate_unique_id), + add_auto_options_route: Annotated[ + Optional[bool], + Doc( + """ + Auto create options route. + """ + ), + ] = False, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP POST operation. @@ -2425,6 +2486,7 @@ class APIRouter(routing.Router): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + add_auto_options_route=add_auto_options_route, ) def delete( @@ -2758,6 +2820,14 @@ class APIRouter(routing.Router): """ ), ] = Default(generate_unique_id), + add_auto_options_route: Annotated[ + Optional[bool], + Doc( + """ + Auto create options route. + """ + ), + ] = False, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP DELETE operation. @@ -2802,6 +2872,7 @@ class APIRouter(routing.Router): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + add_auto_options_route=add_auto_options_route, ) def options( @@ -3135,6 +3206,14 @@ class APIRouter(routing.Router): """ ), ] = Default(generate_unique_id), + add_auto_options_route: Annotated[ + Optional[bool], + Doc( + """ + Auto create options route. + """ + ), + ] = False, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP OPTIONS operation. @@ -3179,6 +3258,7 @@ class APIRouter(routing.Router): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + add_auto_options_route=add_auto_options_route, ) def head( @@ -3512,6 +3592,14 @@ class APIRouter(routing.Router): """ ), ] = Default(generate_unique_id), + add_auto_options_route: Annotated[ + Optional[bool], + Doc( + """ + Auto create options route. + """ + ), + ] = False, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP HEAD operation. @@ -3561,6 +3649,7 @@ class APIRouter(routing.Router): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + add_auto_options_route=add_auto_options_route, ) def patch( @@ -3894,6 +3983,14 @@ class APIRouter(routing.Router): """ ), ] = Default(generate_unique_id), + add_auto_options_route: Annotated[ + Optional[bool], + Doc( + """ + Auto create options route. + """ + ), + ] = False, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP PATCH operation. @@ -3943,6 +4040,7 @@ class APIRouter(routing.Router): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + add_auto_options_route=add_auto_options_route, ) def trace( @@ -4276,6 +4374,14 @@ class APIRouter(routing.Router): """ ), ] = Default(generate_unique_id), + add_auto_options_route: Annotated[ + Optional[bool], + Doc( + """ + Auto create options route. + """ + ), + ] = False, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP TRACE operation. @@ -4325,6 +4431,7 @@ class APIRouter(routing.Router): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + add_auto_options_route=add_auto_options_route, ) @deprecated( From edd8a015362a8417fa690e4d4c290471af447293 Mon Sep 17 00:00:00 2001 From: Volodymyr Kochetkov Date: Thu, 28 Dec 2023 11:31:42 +0200 Subject: [PATCH 2/7] feat: issue-2008 fix tests --- fastapi/routing.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fastapi/routing.py b/fastapi/routing.py index e4b84e6cb..b86675202 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -840,7 +840,7 @@ class APIRouter(routing.Router): generate_unique_id_function: Union[ Callable[[APIRoute], str], DefaultPlaceholder ] = Default(generate_unique_id), - add_auto_options_route: bool = False, + add_auto_options_route: Optional[bool] = False, ) -> None: route_class = route_class_override or self.route_class responses = responses or {} @@ -907,7 +907,7 @@ class APIRouter(routing.Router): if "OPTIONS" not in new_route.methods: - async def options_route(): + async def options_route() -> Response: return Response(headers={"Allow": ", ".join(allowed_methods)}) self.routes.append( @@ -949,7 +949,7 @@ class APIRouter(routing.Router): generate_unique_id_function: Callable[[APIRoute], str] = Default( generate_unique_id ), - add_auto_options_route: bool = False, + add_auto_options_route: Optional[bool] = False, ) -> Callable[[DecoratedCallable], DecoratedCallable]: def decorator(func: DecoratedCallable) -> DecoratedCallable: self.add_api_route( From 7e11cd5df25eb44b7371fc7fee4d23d67054c554 Mon Sep 17 00:00:00 2001 From: Volodymyr Kochetkov Date: Sat, 30 Dec 2023 13:06:34 +0200 Subject: [PATCH 3/7] feat: issue-2008 update include_in_schema for options route --- fastapi/routing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastapi/routing.py b/fastapi/routing.py index 95997401c..41f83fff0 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -950,7 +950,7 @@ class APIRouter(routing.Router): self.prefix + path, endpoint=options_route, methods=["OPTIONS"], - include_in_schema=False, + include_in_schema=True, is_auto_options=True, ) ) From 45acd328f2cd328a8c1693bd736e0718ccaefa4d Mon Sep 17 00:00:00 2001 From: Volodymyr Kochetkov Date: Sat, 30 Dec 2023 13:17:57 +0200 Subject: [PATCH 4/7] feat: issue-2008 fix linter --- fastapi/routing.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/fastapi/routing.py b/fastapi/routing.py index 41f83fff0..d1ecab7d0 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -931,11 +931,12 @@ class APIRouter(routing.Router): auto_options_index: Optional[int] = None allowed_methods: Set[str] = set() for index, route in enumerate(self.routes): - if route.path == new_route.path: - if hasattr(route, "is_auto_options") and route.is_auto_options: - auto_options_index = index - else: - allowed_methods.update(route.methods) + if isinstance(route, APIRoute): + if route.path == new_route.path: + if hasattr(route, "is_auto_options") and route.is_auto_options: + auto_options_index = index + else: + allowed_methods.update(route.methods) if auto_options_index is not None: self.routes.pop(auto_options_index) From 01a33294c383cea24dd7ec2ec16cf4beed2656fb Mon Sep 17 00:00:00 2001 From: Volodymyr Kochetkov Date: Sun, 14 Jan 2024 10:20:53 +0200 Subject: [PATCH 5/7] feat: issue-2008 add tests --- tests/test_auto_options_route.py | 284 +++++++++++++++++++++++++++++++ 1 file changed, 284 insertions(+) create mode 100644 tests/test_auto_options_route.py diff --git a/tests/test_auto_options_route.py b/tests/test_auto_options_route.py new file mode 100644 index 000000000..08595a89f --- /dev/null +++ b/tests/test_auto_options_route.py @@ -0,0 +1,284 @@ +from fastapi import FastAPI +from fastapi.responses import JSONResponse +from fastapi.testclient import TestClient +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + + +@app.get("/home", add_auto_options_route=True) +def get_home(): + return {"hello": "world"} + + +@app.post("/items/", add_auto_options_route=True) +def create_item(item: Item): + return item + + +@app.head("/items/{item_id}", add_auto_options_route=True) +def head_item(item_id: str): + return JSONResponse(None, headers={"x-fastapi-item-id": item_id}) + + +@app.patch("/items/{item_id}", add_auto_options_route=True) +def patch_item(item_id: str, item: Item): + return {"item_id": item_id, "item": item} + + +@app.trace("/items/{item_id}", add_auto_options_route=True) +def trace_item(item_id: str): + return JSONResponse(None, media_type="message/http") + + +client = TestClient(app) + + +def test_get_api_route(): + response = client.get("/home") + assert response.status_code == 200, response.text + assert response.json() == {"hello": "world"} + + +def test_get_auto_options(): + response = client.options("/home") + assert response.status_code == 200, response.text + assert response.headers.raw[0][0].decode("utf-8") == "allow" + assert response.headers.raw[0][1].decode("utf-8") == "GET" + + +def test_post_auto_options(): + response = client.options("/items/") + assert response.status_code == 200, response.text + assert response.headers.raw[0][0].decode("utf-8") == "allow" + assert response.headers.raw[0][1].decode("utf-8") == "POST" + + +def test_other_auto_options(): + response = client.options("/items/foo") + assert response.status_code == 200, response.text + assert response.headers.raw[0][0].decode("utf-8") == "allow" + assert set(response.headers.raw[0][1].decode("utf-8").split(", ")) == { + "HEAD", + "PATCH", + "TRACE", + } + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + + assert response.json() == { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/home": { + "get": { + "summary": "Get Home", + "operationId": "get_home_home_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + }, + "options": { + "summary": "Options Route", + "operationId": "options_route_home_options", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + }, + }, + "/items/": { + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "options": { + "summary": "Options Route", + "operationId": "options_route_items__options", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + }, + }, + "/items/{item_id}": { + "head": { + "summary": "Head Item", + "operationId": "head_item_items__item_id__head", + "parameters": [ + { + "name": "item_id", + "in": "path", + "required": True, + "schema": {"type": "string", "title": "Item Id"}, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "patch": { + "summary": "Patch Item", + "operationId": "patch_item_items__item_id__patch", + "parameters": [ + { + "name": "item_id", + "in": "path", + "required": True, + "schema": {"type": "string", "title": "Item Id"}, + } + ], + "requestBody": { + "required": True, + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "trace": { + "summary": "Trace Item", + "operationId": "trace_item_items__item_id__trace", + "parameters": [ + { + "name": "item_id", + "in": "path", + "required": True, + "schema": {"type": "string", "title": "Item Id"}, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "options": { + "summary": "Options Route", + "operationId": "options_route_items__item_id__options", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + }, + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": {"$ref": "#/components/schemas/ValidationError"}, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "Item": { + "properties": {"name": {"type": "string", "title": "Name"}}, + "type": "object", + "required": ["name"], + "title": "Item", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } From a52811c05a04ca578db0052fd5548d4d96470ea4 Mon Sep 17 00:00:00 2001 From: Volodymyr Kochetkov Date: Sun, 14 Jan 2024 10:44:23 +0200 Subject: [PATCH 6/7] feat: issue-2008 update tests --- tests/test_auto_options_route.py | 78 ++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/tests/test_auto_options_route.py b/tests/test_auto_options_route.py index 08595a89f..8139a257f 100644 --- a/tests/test_auto_options_route.py +++ b/tests/test_auto_options_route.py @@ -20,6 +20,11 @@ def create_item(item: Item): return item +@app.delete("/items/{item_id}") +def delete_item(item_id: str, item: Item): + return {"item_id": item_id, "item": item} + + @app.head("/items/{item_id}", add_auto_options_route=True) def head_item(item_id: str): return JSONResponse(None, headers={"x-fastapi-item-id": item_id}) @@ -44,6 +49,36 @@ def test_get_api_route(): assert response.json() == {"hello": "world"} +def test_post_api_route(): + response = client.post("/items/", json={"name": "CoolItem"}) + assert response.status_code == 200, response.text + assert response.json() == {"name": "CoolItem"} + + +def test_delete(): + response = client.request("DELETE", "/items/foo", json={"name": "Foo"}) + assert response.status_code == 200, response.text + assert response.json() == {"item_id": "foo", "item": {"name": "Foo"}} + + +def test_head(): + response = client.head("/items/foo") + assert response.status_code == 200, response.text + assert response.headers["x-fastapi-item-id"] == "foo" + + +def test_patch(): + response = client.patch("/items/foo", json={"name": "Foo"}) + assert response.status_code == 200, response.text + assert response.json() == {"item_id": "foo", "item": {"name": "Foo"}} + + +def test_trace(): + response = client.request("trace", "/items/foo") + assert response.status_code == 200, response.text + assert response.headers["content-type"] == "message/http" + + def test_get_auto_options(): response = client.options("/home") assert response.status_code == 200, response.text @@ -58,11 +93,18 @@ def test_post_auto_options(): assert response.headers.raw[0][1].decode("utf-8") == "POST" +def test_head_auto_options(): + response = client.head("/items/foo") + assert response.status_code == 200, response.text + assert response.headers["x-fastapi-item-id"] == "foo" + + def test_other_auto_options(): response = client.options("/items/foo") assert response.status_code == 200, response.text assert response.headers.raw[0][0].decode("utf-8") == "allow" assert set(response.headers.raw[0][1].decode("utf-8").split(", ")) == { + "DELETE", "HEAD", "PATCH", "TRACE", @@ -140,6 +182,42 @@ def test_openapi_schema(): }, }, "/items/{item_id}": { + "delete": { + "summary": "Delete Item", + "operationId": "delete_item_items__item_id__delete", + "parameters": [ + { + "name": "item_id", + "in": "path", + "required": True, + "schema": {"type": "string", "title": "Item Id"}, + } + ], + "requestBody": { + "required": True, + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, "head": { "summary": "Head Item", "operationId": "head_item_items__item_id__head", From 4cc72c2647b6a58a20124b0228268bb6b90e7b37 Mon Sep 17 00:00:00 2001 From: Volodymyr Kochetkov Date: Mon, 19 Feb 2024 20:07:24 +0200 Subject: [PATCH 7/7] feat: issue-2008 update doc strings --- fastapi/applications.py | 16 ++++++++-------- fastapi/routing.py | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/fastapi/applications.py b/fastapi/applications.py index 5304d57ef..8493851c6 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -1796,7 +1796,7 @@ class FastAPI(Starlette): Optional[bool], Doc( """ - Auto create options route. + Automatically create a route to handle the OPTIONS HTTP verb. """ ), ] = False, @@ -2178,7 +2178,7 @@ class FastAPI(Starlette): Optional[bool], Doc( """ - Auto create options route. + Automatically create a route to handle the OPTIONS HTTP verb. """ ), ] = False, @@ -2565,7 +2565,7 @@ class FastAPI(Starlette): Optional[bool], Doc( """ - Auto create options route. + Automatically create a route to handle the OPTIONS HTTP verb. """ ), ] = False, @@ -2952,7 +2952,7 @@ class FastAPI(Starlette): Optional[bool], Doc( """ - Auto create options route. + Automatically create a route to handle the OPTIONS HTTP verb. """ ), ] = False, @@ -3334,7 +3334,7 @@ class FastAPI(Starlette): Optional[bool], Doc( """ - Auto create options route. + Automatically create a route to handle the OPTIONS HTTP verb. """ ), ] = False, @@ -3716,7 +3716,7 @@ class FastAPI(Starlette): Optional[bool], Doc( """ - Auto create options route. + Automatically create a route to handle the OPTIONS HTTP verb. """ ), ] = False, @@ -4098,7 +4098,7 @@ class FastAPI(Starlette): Optional[bool], Doc( """ - Auto create options route. + Automatically create a route to handle the OPTIONS HTTP verb. """ ), ] = False, @@ -4485,7 +4485,7 @@ class FastAPI(Starlette): Optional[bool], Doc( """ - Auto create options route. + Automatically create a route to handle the OPTIONS HTTP verb. """ ), ] = False, diff --git a/fastapi/routing.py b/fastapi/routing.py index 8c6c7e67e..e52b633a1 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -1692,7 +1692,7 @@ class APIRouter(routing.Router): Optional[bool], Doc( """ - Auto create options route. + Automatically create a route to handle the OPTIONS HTTP verb. """ ), ] = False, @@ -2078,7 +2078,7 @@ class APIRouter(routing.Router): Optional[bool], Doc( """ - Auto create options route. + Automatically create a route to handle the OPTIONS HTTP verb. """ ), ] = False, @@ -2469,7 +2469,7 @@ class APIRouter(routing.Router): Optional[bool], Doc( """ - Auto create options route. + Automatically create a route to handle the OPTIONS HTTP verb. """ ), ] = False, @@ -2860,7 +2860,7 @@ class APIRouter(routing.Router): Optional[bool], Doc( """ - Auto create options route. + Automatically create a route to handle the OPTIONS HTTP verb. """ ), ] = False, @@ -3246,7 +3246,7 @@ class APIRouter(routing.Router): Optional[bool], Doc( """ - Auto create options route. + Automatically create a route to handle the OPTIONS HTTP verb. """ ), ] = False, @@ -3632,7 +3632,7 @@ class APIRouter(routing.Router): Optional[bool], Doc( """ - Auto create options route. + Automatically create a route to handle the OPTIONS HTTP verb. """ ), ] = False, @@ -4023,7 +4023,7 @@ class APIRouter(routing.Router): Optional[bool], Doc( """ - Auto create options route. + Automatically create a route to handle the OPTIONS HTTP verb. """ ), ] = False, @@ -4414,7 +4414,7 @@ class APIRouter(routing.Router): Optional[bool], Doc( """ - Auto create options route. + Automatically create a route to handle the OPTIONS HTTP verb. """ ), ] = False,