feat: entity events
All checks were successful
Build Docs / changes (push) Successful in 20s
Build Docs / build-docs (push) Has been skipped
Build Docs / deploy-docs (push) Has been skipped

This commit is contained in:
Alexander Kalinovsky
2025-03-10 22:10:09 +07:00
parent dd56d3f312
commit 02aec23b84
5 changed files with 39 additions and 4 deletions

View File

@@ -15,6 +15,7 @@ from .model.descriptors import (
Filter as Filter, Filter as Filter,
EntityPermission as EntityPermission, EntityPermission as EntityPermission,
CommandCallbackContext as CommandCallbackContext, CommandCallbackContext as CommandCallbackContext,
EntityEventContext as EntityEventContext,
CommandButton as CommandButton, CommandButton as CommandButton,
FieldEditButton as FieldEditButton, FieldEditButton as FieldEditButton,
InlineButton as InlineButton, InlineButton as InlineButton,

View File

@@ -1,3 +1,4 @@
from inspect import iscoroutinefunction
from aiogram import Router, F from aiogram import Router, F
from aiogram.types import Message, CallbackQuery from aiogram.types import Message, CallbackQuery
from aiogram.fsm.context import FSMContext from aiogram.fsm.context import FSMContext
@@ -12,7 +13,7 @@ from ..user_handlers.main import cammand_handler
from ....model import EntityPermission from ....model import EntityPermission
from ....model.user import UserBase from ....model.user import UserBase
from ....model.settings import Settings from ....model.settings import Settings
from ....model.descriptors import FieldDescriptor from ....model.descriptors import EntityEventContext, FieldDescriptor
from ....model.language import LanguageBase from ....model.language import LanguageBase
from ....auth import authorize_command from ....auth import authorize_command
from ....utils.main import ( from ....utils.main import (
@@ -281,6 +282,12 @@ async def process_field_edit_callback(message: Message | CallbackQuery, **kwargs
commit=True, commit=True,
) )
if entity_descriptor.on_created:
if iscoroutinefunction(entity_descriptor.on_created):
await entity_descriptor.on_created(new_entity, EntityEventContext(db_session=db_session, app=app))
else:
entity_descriptor.on_created(new_entity, EntityEventContext(db_session=db_session, app=app))
form_name = ( form_name = (
callback_data.form_params.split("&")[0] callback_data.form_params.split("&")[0]
if callback_data.form_params if callback_data.form_params
@@ -325,6 +332,13 @@ async def process_field_edit_callback(message: Message | CallbackQuery, **kwargs
setattr(entity, key, value) setattr(entity, key, value)
await db_session.commit() await db_session.commit()
await db_session.refresh(entity)
if entity_descriptor.on_updated:
if iscoroutinefunction(entity_descriptor.on_updated):
await entity_descriptor.on_updated(entity, EntityEventContext(db_session=db_session, app=app))
else:
entity_descriptor.on_updated(entity, EntityEventContext(db_session=db_session, app=app))
elif callback_data.context == CommandContext.COMMAND_FORM: elif callback_data.context == CommandContext.COMMAND_FORM:
clear_state(state_data=state_data) clear_state(state_data=state_data)

View File

@@ -1,3 +1,4 @@
from inspect import iscoroutinefunction
from aiogram import Router, F from aiogram import Router, F
from aiogram.fsm.context import FSMContext from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery, InlineKeyboardButton from aiogram.types import CallbackQuery, InlineKeyboardButton
@@ -5,6 +6,8 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
from sqlmodel.ext.asyncio.session import AsyncSession from sqlmodel.ext.asyncio.session import AsyncSession
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from qbot.model.descriptors import EntityEventContext
from ..context import ContextData, CallbackCommand from ..context import ContextData, CallbackCommand
from ....model.user import UserBase from ....model.user import UserBase
from ....model.settings import Settings from ....model.settings import Settings
@@ -47,10 +50,18 @@ async def entity_delete_callback(query: CallbackQuery, **kwargs):
) )
if callback_data.data == "yes": if callback_data.data == "yes":
await entity_descriptor.type_.remove(
entity = await entity_descriptor.type_.remove(
session=db_session, id=int(callback_data.entity_id), commit=True session=db_session, id=int(callback_data.entity_id), commit=True
) )
if entity_descriptor.on_deleted:
if iscoroutinefunction(entity_descriptor.on_created):
await entity_descriptor.on_deleted(entity, EntityEventContext(db_session=db_session, app=app))
else:
entity_descriptor.on_deleted(entity, EntityEventContext(db_session=db_session, app=app))
await route_callback(message=query, **kwargs) await route_callback(message=query, **kwargs)
elif not callback_data.data: elif not callback_data.data:

View File

@@ -31,7 +31,7 @@ class Config(BaseSettings):
def API_DOMAIN(self) -> str: def API_DOMAIN(self) -> str:
if self.ENVIRONMENT == "local": if self.ENVIRONMENT == "local":
return self.DOMAIN return self.DOMAIN
return f"api.{self.DOMAIN}" return f"{self.DOMAIN}"
@computed_field @computed_field
@property @property
@@ -39,7 +39,7 @@ class Config(BaseSettings):
if self.USE_NGROK: if self.USE_NGROK:
return self.NGROK_URL return self.NGROK_URL
return ( return (
f"{'http' if self.ENVIRONMENT == 'local' else 'https'}://{self.API_DOMAIN}" f"https://{self.API_DOMAIN}"
) )
API_PORT: int = 8000 API_PORT: int = 8000

View File

@@ -163,6 +163,9 @@ class _BaseEntityDescriptor:
EntityPermission.DELETE_ALL: [RoleBase.SUPER_USER], EntityPermission.DELETE_ALL: [RoleBase.SUPER_USER],
} }
) )
on_created: Callable[["BotEntity", "EntityEventContext"], None] | None = None
on_deleted: Callable[["BotEntity", "EntityEventContext"], None] | None = None
on_updated: Callable[["BotEntity", "EntityEventContext"], None] | None = None
@dataclass(kw_only=True) @dataclass(kw_only=True)
@@ -197,6 +200,12 @@ class CommandCallbackContext[UT: UserBase]:
kwargs: dict[str, Any] = field(default_factory=dict) kwargs: dict[str, Any] = field(default_factory=dict)
@dataclass(kw_only=True)
class EntityEventContext:
db_session: AsyncSession
app: "QBotApp"
@dataclass(kw_only=True) @dataclass(kw_only=True)
class BotCommand: class BotCommand:
name: str name: str