From 97361b02bebc3a31d971fe3d8e04ba05591c3506 Mon Sep 17 00:00:00 2001 From: Volodymyr Kochetkov Date: Thu, 28 Dec 2023 10:01:33 +0200 Subject: [PATCH] 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(