Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for custom actions #200

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 86 additions & 1 deletion piccolo_api/fastapi/endpoints.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

"""
Enhancing Piccolo integration with FastAPI.
"""
Expand All @@ -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

Expand All @@ -24,6 +27,9 @@ class HTTPMethod(str, Enum):
get = "GET"
delete = "DELETE"

class TableRowDataSchema(PydanticBaseModel):
table_name: str
row_ids: list[str]

class FastAPIKwargs:
"""
Expand Down Expand Up @@ -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.
Expand All @@ -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

"""

Expand All @@ -103,13 +120,16 @@ 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()

self.root_url = root_url
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
Expand Down Expand Up @@ -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:

Check notice

Code scanning / CodeQL

Explicit returns mixed with implicit (fall through) returns

Mixing implicit and explicit returns may indicate an error as implicit returns always return None.
"""
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

Expand Down