-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* initial exploration of hooks * handle empty hook list * completed suggestion for initial hook implementation * lint * refactorings and fixes from review comments * fix older python typing incompatabiliy * reverted old-python fix * fixed docs typo * fix type hint mismatch * doc typos and corrections
- Loading branch information
1 parent
cd3bc74
commit bed6678
Showing
5 changed files
with
374 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
Hooks | ||
===== | ||
|
||
Hooks allow executing custom code as part of processing a crud request. You can use this to validate data, call another custom api, place messages on queues and many other things. | ||
|
||
Enabling a hook | ||
--------------- | ||
|
||
Define a method, and register it with PiccoloCRUD: | ||
|
||
.. code-block:: python | ||
# app.py | ||
from piccolo_api.crud.endpoints import PiccoloCRUD | ||
from movies.tables import Movie | ||
# set movie rating to 10 before saving | ||
async def set_movie_rating_10(row: Movie): | ||
row.rating = 10 | ||
return row | ||
# set movie rating to 20 before saving | ||
async def set_movie_rating_10(row: Movie): | ||
row.rating = 20 | ||
return row | ||
async def pre_delete(row_id): | ||
pass | ||
# Register one or multiple hooks | ||
app = PiccoloCRUD(table=Movie, read_only=False, hooks=[ | ||
Hook(hook_type=HookType.pre_save, callable=set_movie_rating_10), | ||
Hook(hook_type=HookType.pre_save, callable=set_movie_rating_20), | ||
Hook(hook_type=HookType.pre_delete, callable=pre_delete) | ||
] | ||
) | ||
You can specify multiple hooks (also per hook_type). Hooks are executed in order. | ||
You can use either async or regular functions. | ||
|
||
Hook types | ||
---------- | ||
|
||
There are different hook types, and each type takes a slightly different set of inputs. | ||
It's also important to return the expected data from your hook. | ||
|
||
pre_save | ||
~~~~~~~~ | ||
|
||
This hook runs during POST requests, prior to inserting data into the database. | ||
It takes a single parameter, ``row``, and should return the same: | ||
|
||
.. code-block:: python | ||
async def set_movie_rating_10(row: Movie): | ||
row.rating = 10 | ||
return row | ||
app = PiccoloCRUD(table=Movie, read_only=False, hooks=[ | ||
Hook(hook_type=HookType.pre_save, callable=set_movie_rating_10) | ||
] | ||
) | ||
pre_patch | ||
~~~~~~~~~ | ||
|
||
This hook runs during PATCH requests, prior to changing the specified row in the database. | ||
It takes two parameters, ``row_id`` which is the id of the row to be changed, and ``values`` which is a dictionary of incoming values. | ||
Each function must return a dictionary which represent the data to be modified. | ||
|
||
.. code-block:: python | ||
async def reset_name(row_id: int, values: dict): | ||
current_db_row = await Movie.objects().get(Movie.id==row_id).run() | ||
if values.get("name"): | ||
values["name"] = values["name"].replace(" ", "") | ||
return values | ||
app = PiccoloCRUD(table=Movie, read_only=False, hooks=[ | ||
Hook(hook_type=HookType.pre_patch, callable=reset_name) | ||
] | ||
) | ||
pre_delete | ||
~~~~~~~~~~ | ||
|
||
This hook runs during DELETE requests, prior to deleting the specified row in the database. | ||
It takes one parameter, ``row_id`` which is the id of the row to be deleted. | ||
pre_delete hooks should not return data | ||
|
||
.. code-block:: python | ||
async def pre_delete(row_id: int): | ||
pass | ||
app = PiccoloCRUD(table=Movie, read_only=False, hooks=[ | ||
Hook(hook_type=HookType.pre_delete, callable=pre_delete) | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,3 +9,4 @@ Piccolo tables into a powerful REST API. | |
|
||
./piccolo_crud | ||
./serializers | ||
./hooks |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import inspect | ||
import typing as t | ||
from enum import Enum | ||
|
||
from piccolo.table import Table | ||
|
||
|
||
class HookType(Enum): | ||
pre_save = "pre_save" | ||
pre_patch = "pre_patch" | ||
pre_delete = "pre_delete" | ||
|
||
|
||
class Hook: | ||
def __init__(self, hook_type: HookType, callable: t.Callable) -> None: | ||
self.hook_type = hook_type | ||
self.callable = callable | ||
|
||
|
||
async def execute_post_hooks( | ||
hooks: t.Dict[HookType, t.List[Hook]], hook_type: HookType, row: Table | ||
): | ||
for hook in hooks.get(hook_type, []): | ||
if inspect.iscoroutinefunction(hook.callable): | ||
row = await hook.callable(row) | ||
else: | ||
row = hook.callable(row) | ||
return row | ||
|
||
|
||
async def execute_patch_hooks( | ||
hooks: t.Dict[HookType, t.List[Hook]], | ||
hook_type: HookType, | ||
row_id: t.Any, | ||
values: t.Dict[t.Any, t.Any], | ||
) -> t.Dict[t.Any, t.Any]: | ||
for hook in hooks.get(hook_type, []): | ||
if inspect.iscoroutinefunction(hook.callable): | ||
values = await hook.callable(row_id=row_id, values=values) | ||
else: | ||
values = hook.callable(row_id=row_id, values=values) | ||
return values | ||
|
||
|
||
async def execute_delete_hooks( | ||
hooks: t.Dict[HookType, t.List[Hook]], hook_type: HookType, row_id: t.Any | ||
): | ||
for hook in hooks.get(hook_type, []): | ||
if inspect.iscoroutinefunction(hook.callable): | ||
await hook.callable(row_id=row_id) | ||
else: | ||
hook.callable(row_id=row_id) |
Oops, something went wrong.