diff --git a/piccolo_api/fastapi/endpoints.py b/piccolo_api/fastapi/endpoints.py index 973b1d07..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,14 @@ 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 @@ -24,6 +27,9 @@ class HTTPMethod(str, Enum): get = "GET" delete = "DELETE" +class TableRowDataSchema(PydanticBaseModel): + table_name: str + row_ids: list[str] class FastAPIKwargs: """ @@ -75,6 +81,15 @@ class ReferencesModel(BaseModel): references: t.List[ReferenceModel] +class ActionsModel(BaseModel): + action_id: int + action_name: str + + +class ExecuteActionsModel(BaseModel): + actions_response: str + + class FastAPIWrapper: """ Wraps ``PiccoloCRUD`` so it can easily be integrated into FastAPI. @@ -94,6 +109,8 @@ class FastAPIWrapper: and ``allow_bulk_delete``. :param fastapi_kwargs: Specifies the extra kwargs to pass to FastAPI's ``add_api_route``. + :param actions + List of action handlers passed in via create_admin TableConfig """ @@ -103,6 +120,7 @@ def __init__( fastapi_app: t.Union[FastAPI, APIRouter], piccolo_crud: PiccoloCRUD, fastapi_kwargs: t.Optional[FastAPIKwargs] = None, + actions: t.Optional[t.List[t.Callable]] = None, ): fastapi_kwargs = fastapi_kwargs or FastAPIKwargs() @@ -110,6 +128,8 @@ def __init__( self.fastapi_app = fastapi_app self.piccolo_crud = piccolo_crud self.fastapi_kwargs = fastapi_kwargs + self.actions = actions + self._actions_map = [] self.ModelOut = piccolo_crud.pydantic_model_output self.ModelIn = piccolo_crud.pydantic_model @@ -243,6 +263,71 @@ async def references(request: Request): **fastapi_kwargs.get_kwargs("get"), ) + ####################################################################### + # Root - Actions + + async def get_actions(request: Request) -> JSONResponse: + """ + Return the names of the actions + This is specified on the table config + """ + 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 actions configured", status_code=500 + ) + + 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, "/actions"), + endpoint=get_actions, + methods=["GET"], + response_model=t.List[ActionsModel], + **fastapi_kwargs.get_kwargs("get"), + ) + + ####################################################################### + # Root - Actions execute + + async def run_action(request: Request) -> JSONResponse: + """ + Execute the configured actions for this table + :param request_params: + The request params must contain the arguments + required by the actions handler function + """ + 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 actions registered", status_code=500 + ) + + if self.actions: + fastapi_app.add_api_route( + path=self.join_urls(root_url, "/actions/{action_id:str}/execute"), + endpoint=run_action, + methods=["POST"], + response_model=ExecuteActionsModel, + **fastapi_kwargs.get_kwargs("POST"), + ) ####################################################################### # Root - DELETE