From d4f635e1ef93f798e6003a6304e03cceb7c4a9e9 Mon Sep 17 00:00:00 2001 From: Haffi Mazhar Date: Sat, 5 Nov 2022 18:50:41 +0000 Subject: [PATCH 1/2] initial commit for adding custom callbacks --- piccolo_api/fastapi/endpoints.py | 64 ++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/piccolo_api/fastapi/endpoints.py b/piccolo_api/fastapi/endpoints.py index 973b1d07..38acb36a 100644 --- a/piccolo_api/fastapi/endpoints.py +++ b/piccolo_api/fastapi/endpoints.py @@ -14,6 +14,7 @@ from fastapi.params import Query from pydantic import BaseModel as PydanticBaseModel from pydantic.main import BaseModel +from starlette.responses import JSONResponse from piccolo_api.crud.endpoints import PiccoloCRUD @@ -75,6 +76,14 @@ class ReferencesModel(BaseModel): references: t.List[ReferenceModel] +class CallbackModel(BaseModel): + callback_name: str + + +class ExecuteCallbackModel(BaseModel): + callback_response: str + + class FastAPIWrapper: """ Wraps ``PiccoloCRUD`` so it can easily be integrated into FastAPI. @@ -94,6 +103,8 @@ class FastAPIWrapper: and ``allow_bulk_delete``. :param fastapi_kwargs: Specifies the extra kwargs to pass to FastAPI's ``add_api_route``. + :param callback + Callback fn passed in via create_admin TableConfig """ @@ -103,6 +114,7 @@ def __init__( fastapi_app: t.Union[FastAPI, APIRouter], piccolo_crud: PiccoloCRUD, fastapi_kwargs: t.Optional[FastAPIKwargs] = None, + callback: t.Optional[t.Callable] = None, ): fastapi_kwargs = fastapi_kwargs or FastAPIKwargs() @@ -110,6 +122,7 @@ def __init__( self.fastapi_app = fastapi_app self.piccolo_crud = piccolo_crud self.fastapi_kwargs = fastapi_kwargs + self.callback = callback self.ModelOut = piccolo_crud.pydantic_model_output self.ModelIn = piccolo_crud.pydantic_model @@ -243,6 +256,57 @@ async def references(request: Request): **fastapi_kwargs.get_kwargs("get"), ) + ####################################################################### + # Root - Callback + + async def get_callback(request: Request) -> JSONResponse: + """ + Return the name of the callback function + This is specified on the table config + """ + if self.callback: + return JSONResponse( + f"Configured callback for table: {self.callback.__name__}" + ) + else: + return JSONResponse( + content="No callback configured", status_code=500 + ) + + if self.callback: + fastapi_app.add_api_route( + path=self.join_urls(root_url, "/callback"), + endpoint=get_callback, + methods=["GET"], + response_model=CallbackModel, + **fastapi_kwargs.get_kwargs("get"), + ) + + ####################################################################### + # Root - Callback execute + + async def run_callback(request: Request) -> JSONResponse: + """ + Execute the configured callback for this table + :param request_params: + The request params must contain the arguments + required by the callback + """ + if self.callback: + return await self.callback(request_params=request.json()) + else: + return JSONResponse( + content="No callback configured", status_code=500 + ) + + if self.callback: + fastapi_app.add_api_route( + path=self.join_urls(root_url, "/callback/execute"), + endpoint=run_callback, + methods=["POST"], + response_model=ExecuteCallbackModel, + **fastapi_kwargs.get_kwargs("post"), + ) ####################################################################### # Root - DELETE From d9e3e91bc40e485ad0540b708079b6ad77fb4213 Mon Sep 17 00:00:00 2001 From: Haffi Mazhar Date: Sat, 19 Nov 2022 18:39:02 +0000 Subject: [PATCH 2/2] Actions for multiple rows --- piccolo_api/fastapi/endpoints.py | 87 ++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 33 deletions(-) diff --git a/piccolo_api/fastapi/endpoints.py b/piccolo_api/fastapi/endpoints.py index 38acb36a..654ffa73 100644 --- a/piccolo_api/fastapi/endpoints.py +++ b/piccolo_api/fastapi/endpoints.py @@ -1,3 +1,4 @@ + """ Enhancing Piccolo integration with FastAPI. """ @@ -8,12 +9,13 @@ from collections import defaultdict from decimal import Decimal from enum import Enum -from inspect import Parameter, Signature +from inspect import Parameter, Signature, iscoroutinefunction from fastapi import APIRouter, FastAPI, Request from fastapi.params import Query from pydantic import BaseModel as PydanticBaseModel from pydantic.main import BaseModel +from pydantic import parse_obj_as from starlette.responses import JSONResponse from piccolo_api.crud.endpoints import PiccoloCRUD @@ -25,6 +27,9 @@ class HTTPMethod(str, Enum): get = "GET" delete = "DELETE" +class TableRowDataSchema(PydanticBaseModel): + table_name: str + row_ids: list[str] class FastAPIKwargs: """ @@ -76,12 +81,13 @@ class ReferencesModel(BaseModel): references: t.List[ReferenceModel] -class CallbackModel(BaseModel): - callback_name: str +class ActionsModel(BaseModel): + action_id: int + action_name: str -class ExecuteCallbackModel(BaseModel): - callback_response: str +class ExecuteActionsModel(BaseModel): + actions_response: str class FastAPIWrapper: @@ -103,8 +109,8 @@ class FastAPIWrapper: and ``allow_bulk_delete``. :param fastapi_kwargs: Specifies the extra kwargs to pass to FastAPI's ``add_api_route``. - :param callback - Callback fn passed in via create_admin TableConfig + :param actions + List of action handlers passed in via create_admin TableConfig """ @@ -114,7 +120,7 @@ def __init__( fastapi_app: t.Union[FastAPI, APIRouter], piccolo_crud: PiccoloCRUD, fastapi_kwargs: t.Optional[FastAPIKwargs] = None, - callback: t.Optional[t.Callable] = None, + actions: t.Optional[t.List[t.Callable]] = None, ): fastapi_kwargs = fastapi_kwargs or FastAPIKwargs() @@ -122,7 +128,8 @@ def __init__( self.fastapi_app = fastapi_app self.piccolo_crud = piccolo_crud self.fastapi_kwargs = fastapi_kwargs - self.callback = callback + self.actions = actions + self._actions_map = [] self.ModelOut = piccolo_crud.pydantic_model_output self.ModelIn = piccolo_crud.pydantic_model @@ -257,55 +264,69 @@ async def references(request: Request): ) ####################################################################### - # Root - Callback + # Root - Actions - async def get_callback(request: Request) -> JSONResponse: + async def get_actions(request: Request) -> JSONResponse: """ - Return the name of the callback function + Return the names of the actions This is specified on the table config """ - if self.callback: - return JSONResponse( - f"Configured callback for table: {self.callback.__name__}" - ) + if self.actions: + actions_list = [] + for action in self._actions_map: + actions_list.append({"action_id": action['action_id'], "action_name": action['action_handler'].__name__}) + + print(actions_list) + return actions_list else: return JSONResponse( - content="No callback configured", status_code=500 + content="No actions configured", status_code=500 ) - if self.callback: + if self.actions: + for action_id, action in enumerate(actions): + self._actions_map.append({"action_id": action_id, "action_handler": action}) + fastapi_app.add_api_route( - path=self.join_urls(root_url, "/callback"), - endpoint=get_callback, + path=self.join_urls(root_url, "/actions"), + endpoint=get_actions, methods=["GET"], - response_model=CallbackModel, + response_model=t.List[ActionsModel], **fastapi_kwargs.get_kwargs("get"), ) ####################################################################### - # Root - Callback execute + # Root - Actions execute - async def run_callback(request: Request) -> JSONResponse: + async def run_action(request: Request) -> JSONResponse: """ - Execute the configured callback for this table + Execute the configured actions for this table :param request_params: The request params must contain the arguments - required by the callback + required by the actions handler function """ - if self.callback: - return await self.callback(request_params=request.json()) + action_id = request.path_params.get("action_id", None) + if self._actions_map and action_id: + for action in self._actions_map: + if action['action_id'] == int(action_id): + action_handler = action['action_handler'] + req_data = await request.json() + if iscoroutinefunction(action_handler): + return await action_handler(data=parse_obj_as(TableRowDataSchema, req_data)) + else: + return action_handler(data=parse_obj_as(TableRowDataSchema, req_data)) else: return JSONResponse( - content="No callback configured", status_code=500 + content="No actions registered", status_code=500 ) - if self.callback: + if self.actions: fastapi_app.add_api_route( - path=self.join_urls(root_url, "/callback/execute"), - endpoint=run_callback, + path=self.join_urls(root_url, "/actions/{action_id:str}/execute"), + endpoint=run_action, methods=["POST"], - response_model=ExecuteCallbackModel, - **fastapi_kwargs.get_kwargs("post"), + response_model=ExecuteActionsModel, + **fastapi_kwargs.get_kwargs("POST"), ) ####################################################################### # Root - DELETE