-
Notifications
You must be signed in to change notification settings - Fork 85
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 callbacks at Table level for create update delete #987
Comments
@sarvesh4396 At the ORM level, there are callback method, but only for |
@sinisaos, yeah there are callbacks and hooks for api. I have used them. |
@sarvesh4396 I think the best way is to try to override the |
@sarvesh4396 I tried some code based on this example and it works. Something like this: import asyncio
import time
import typing as t
import uuid
from enum import Enum
import uvicorn
from fastapi import FastAPI
from piccolo.apps.user.tables import BaseUser
from piccolo.columns import Real, Text, Timestamp, Varchar
from piccolo.table import Table
from piccolo_admin.endpoints import create_admin
from piccolo_api.session_auth.tables import SessionsBase
from starlette.routing import Mount
from piccolo_conf import DB
class MonitorLog(Table, db=DB):
class ActionType(str, Enum):
"""An enumeration of MonitorLog table actions type."""
creating = "creating"
updating = "updating"
deleting = "deleting"
action_time = Timestamp()
action_type = Varchar(choices=ActionType)
table_name = Varchar()
change_message = Text()
execution_time = Real()
@classmethod
async def record_save_action(
cls,
table: t.Type[Table],
execution_time: float,
new_row_id=t.Union[str, uuid.UUID, int],
):
result = cls(
action_type=cls.ActionType.creating,
table_name=table._meta.tablename.title(),
change_message=f"Create row {new_row_id} in "
f"{table._meta.tablename.title()} table",
execution_time=execution_time,
)
await result.save().run()
@classmethod
async def record_patch_action(
cls,
table: t.Type[Table],
execution_time: float,
row_id: t.Union[str, uuid.UUID, int],
):
result = cls(
action_type=cls.ActionType.updating,
table_name=table._meta.tablename.title(),
change_message=f"Update row "
f"{row_id} in {table._meta.tablename.title()} table",
execution_time=execution_time,
)
await result.save().run()
@classmethod
async def record_delete_action(
cls,
table: t.Type[Table],
execution_time: float,
row_id: t.Union[str, uuid.UUID, int],
):
result = cls(
action_type=cls.ActionType.deleting,
table_name=table._meta.tablename.title(),
change_message=f"Delete row {row_id} "
f"in {table._meta.tablename.title()} table",
execution_time=execution_time,
)
await result.save().run()
class Manager(Table, db=DB):
name = Varchar(length=100)
# override save method for create and update objects
def save(self, columns=None):
pk = self.id
save_ = super().save
class Save:
async def run(self, *args, **kwargs):
# create
if columns is None:
start_time = time.time()
new_row = await save_(columns=columns).run(*args, **kwargs)
end_time = time.time() - start_time
await MonitorLog.record_save_action(
Manager,
new_row_id=new_row[0]["id"],
execution_time=end_time,
)
# update
else:
start_time = time.time()
await save_(columns=columns).run(*args, **kwargs)
end_time = time.time() - start_time
await MonitorLog.record_patch_action(
Manager,
row_id=pk,
execution_time=end_time,
)
def __await__(self):
return self.run().__await__()
return Save()
# override remove method for delete objects
def remove(self):
pk = self.id
remove_ = super().remove
class Remove:
async def run(self, *args, **kwargs):
start_time = time.time()
await remove_().run(*args, **kwargs)
end_time = time.time() - start_time
await MonitorLog.record_delete_action(
Manager,
row_id=pk,
execution_time=end_time,
)
def __await__(self):
return self.run().__await__()
return Remove()
# FastAPI app instantiation and mounting admin
app = FastAPI(
routes=[
Mount(
"/admin/",
create_admin(
tables=[Manager, MonitorLog],
),
)
],
)
async def main():
# Tables creating
await BaseUser.create_table(if_not_exists=True)
await SessionsBase.create_table(if_not_exists=True)
await Manager.create_table(if_not_exists=True)
await MonitorLog.create_table(if_not_exists=True)
# Creating admin user
if not await BaseUser.exists().where(BaseUser.email == "admin@test.com"):
user = BaseUser(
username="piccolo",
password="piccolo123",
email="admin@test.com",
admin=True,
active=True,
superuser=True,
)
await user.save()
# create managers
for i in range(1, 11):
manager = Manager(name=f"Manager {i}")
await manager.save()
# update manager 3
manager = await Manager.objects().where(Manager.id == 3).first()
manager.name = "Manager 3333333"
await manager.save(columns=[Manager.name])
# update manager 5
manager = await Manager.objects().where(Manager.id == 5).first()
manager.name = "Manager 5555555"
await manager.save(columns=[Manager.name])
# delete manager 10
manager = await Manager.objects().where(Manager.id == 10).first()
await manager.remove()
# delete manager 9
manager = await Manager.objects().where(Manager.id == 9).first()
await manager.remove()
print(await MonitorLog.select())
if __name__ == "__main__":
asyncio.run(main())
uvicorn.run(app, host="127.0.0.1", port=8000) I hope you find it useful. |
@sinisaos , It works I have tried to make some changes to retain the history and a custom base table for this. import typing as t
import uuid
from enum import Enum
from piccolo.columns import Timestamp, Varchar, Integer, JSON
from piccolo.table import Table
from piccolo.querystring import Unquoted
from piccolo_conf import DB
class ActionType(str, Enum):
CREATE = "CREATE"
UPDATE = "UPDATE"
DELETE = "DELETE"
class Monitor(Table, db=DB):
action_time = Timestamp()
action_type = Varchar(choices=ActionType)
table_name = Varchar()
row_id = Integer()
history = JSON()
@classmethod
async def get_first(cls, table, row_id):
return (await table.objects().where(table.id == row_id).first()).to_dict()
@classmethod
async def record_save_action(
cls,
table: t.Type[Table],
row_id=t.Union[str, uuid.UUID, int],
):
row = await cls.get_first(table, row_id)
result = cls(
action_type=ActionType.CREATE,
table_name=table._meta.tablename.title(),
row_id=row_id,
history=row,
)
await result.save().run()
@classmethod
async def record_patch_action(
cls,
table: t.Type[Table],
row_id=t.Union[str, uuid.UUID, int],
):
row = await cls.get_first(table, row_id)
result = cls(
action_type=ActionType.UPDATE,
table_name=table._meta.tablename.title(),
row_id=row_id,
history=row,
)
await result.save().run()
@classmethod
async def record_delete_action(
cls,
table: t.Type[Table],
row_id=t.Union[str, uuid.UUID, int],
):
result = cls(
action_type=ActionType.DELETE,
table_name=table._meta.tablename.title(),
row_id=row_id,
)
await result.save().run()
class BaseTable(Table, db=DB):
__monitor__ = False
# override save method for create and update objects
def save(self, columns=None):
table = self.__class__
pk = self.id
save_ = super().save
monitor = self.__monitor__
class Save:
async def run(self, *args, **kwargs):
saved = await save_(columns=columns).run(*args, **kwargs)
print(columns)
if monitor:
if isinstance(pk, Unquoted): # create null
await Monitor.record_save_action(table, saved[0]["id"])
else:
await Monitor.record_patch_action(table, pk)
def __await__(self):
return self.run().__await__()
return Save()
# override remove method for delete objects
def remove(self):
pk = self.id
table = self.__class__
monitor = self.__monitor__
remove_ = super().remove
class Remove:
async def run(self, *args, **kwargs):
await remove_().run(*args, **kwargs)
if monitor:
await Monitor.record_delete_action(
table,
row_id=pk,
)
def __await__(self):
return self.run().__await__()
return Remove()
class Manager(BaseTable, db=DB):
name = Varchar(length=100)
__monitor__ = True
await Manager.create_table(if_not_exists=True)
await Monitor.create_table(if_not_exists=True)
await Manager(name="Sarvesh").save()
managers = await Manager.objects()
managers[0].name = "Updated Name"
await managers[0].save()
await managers[0].remove()
Monitor.select().run_sync()
|
@sarvesh4396 I tried your changes and they work great. I'm glad you found my example useful and successfully modified it to suit your needs. Cheers. |
Hey, is there support for callbacks at Table level for these crud.
Similar is implemented in rails active record.
Or are there any custom implementation regarding the same. Where I can make a base table inheriting piccolo table and implement the same.
The text was updated successfully, but these errors were encountered: