From ca374cdea092766785d9d729a987e2960697835c Mon Sep 17 00:00:00 2001 From: Alexander Kalinovsky Date: Thu, 13 Feb 2025 02:00:20 +0100 Subject: [PATCH] upd get_callable_str async --- bot/handlers/common/filtering.py | 4 +-- bot/handlers/context.py | 1 - bot/handlers/editors/common.py | 20 ++++++++--- bot/handlers/editors/date.py | 12 +++++-- bot/handlers/editors/entity.py | 4 +-- bot/handlers/forms/entity_form.py | 40 +++++++++++---------- bot/handlers/forms/entity_form_callbacks.py | 2 +- bot/handlers/forms/entity_list.py | 10 +++--- bot/handlers/menu/parameters.py | 6 ++-- bot/handlers/start.py | 26 ++++++++++++-- main.py | 39 +++++++------------- utils/main.py | 36 ++++++++++++------- 12 files changed, 117 insertions(+), 83 deletions(-) diff --git a/bot/handlers/common/filtering.py b/bot/handlers/common/filtering.py index 49b7121..27a12e4 100644 --- a/bot/handlers/common/filtering.py +++ b/bot/handlers/common/filtering.py @@ -6,7 +6,7 @@ from ....utils.main import get_callable_str from ..context import ContextData, CallbackCommand -def add_filter_controls( +async def add_filter_controls( keyboard_builder: InlineKeyboardBuilder, entity_descriptor: EntityDescriptor, filter: str = None, @@ -15,7 +15,7 @@ def add_filter_controls( ): caption = ", ".join( [ - get_callable_str( + await get_callable_str( entity_descriptor.fields_descriptors[field_name].caption, entity_descriptor, ) diff --git a/bot/handlers/context.py b/bot/handlers/context.py index a505be8..e389187 100644 --- a/bot/handlers/context.py +++ b/bot/handlers/context.py @@ -17,7 +17,6 @@ class CallbackCommand(StrEnum): DATE_PICKER_MONTH = "dm" DATE_PICKER_YEAR = "dy" TIME_PICKER = "tp" - # STRING_EDITOR_LOCALE = "sl" ENTITY_PICKER_PAGE = "ep" ENTITY_PICKER_TOGGLE_ITEM = "et" VIEW_FILTER_EDIT = "vf" diff --git a/bot/handlers/editors/common.py b/bot/handlers/editors/common.py index b7ada2c..1820b0a 100644 --- a/bot/handlers/editors/common.py +++ b/bot/handlers/editors/common.py @@ -25,13 +25,23 @@ async def show_editor(message: Message | CallbackQuery, **kwargs): value_type = field_descriptor.type_base if field_descriptor.edit_prompt: - edit_prompt = get_callable_str( - field_descriptor.edit_prompt, field_descriptor, None, current_value + edit_prompt = await get_callable_str( + field_descriptor.edit_prompt, + field_descriptor, + callback_data + if callback_data.context == CommandContext.COMMAND_FORM + else None, + current_value, ) else: if field_descriptor.caption: - caption_str = get_callable_str( - field_descriptor.caption, field_descriptor, None, current_value + caption_str = await get_callable_str( + field_descriptor.caption, + field_descriptor, + callback_data + if callback_data.context == CommandContext.COMMAND_FORM + else None, + current_value, ) else: caption_str = field_descriptor.name @@ -42,7 +52,7 @@ async def show_editor(message: Message | CallbackQuery, **kwargs): ) ).format( name=caption_str, - value=get_value_repr(current_value, field_descriptor, user.lang), + value=await get_value_repr(current_value, field_descriptor, user.lang), ) else: edit_prompt = ( diff --git a/bot/handlers/editors/date.py b/bot/handlers/editors/date.py index 024e8e6..960b5b9 100644 --- a/bot/handlers/editors/date.py +++ b/bot/handlers/editors/date.py @@ -56,12 +56,20 @@ async def time_picker( if not current_value: current_value = time(0, 0) + is_datetime = False else: + is_datetime = isinstance(current_value, datetime) + if not is_datetime: + current_time = datetime.combine(datetime.now(), current_value) remainder = current_value.minute % 5 if remainder >= 3: - current_value += timedelta(minutes=(5 - remainder)) + current_time += timedelta(minutes=(5 - remainder)) else: - current_value -= timedelta(minutes=remainder) + current_time -= timedelta(minutes=remainder) + if is_datetime: + current_value = datetime.combine(current_value.date(), current_time.time()) + else: + current_value = current_time.time() for i in range(12): keyboard_builder.row( diff --git a/bot/handlers/editors/entity.py b/bot/handlers/editors/entity.py index 0134e3f..776df11 100644 --- a/bot/handlers/editors/entity.py +++ b/bot/handlers/editors/entity.py @@ -209,7 +209,7 @@ async def render_entity_picker( type_.bot_entity_descriptor, item ) if type_.bot_entity_descriptor.item_repr - else get_callable_str( + else await get_callable_str( type_.bot_entity_descriptor.full_name, type_.bot_entity_descriptor, item, @@ -259,7 +259,7 @@ async def render_entity_picker( and form_list.filtering and form_list.filtering_fields ): - add_filter_controls( + await add_filter_controls( keyboard_builder=keyboard_builder, entity_descriptor=type_.bot_entity_descriptor, filter=entity_filter, diff --git a/bot/handlers/forms/entity_form.py b/bot/handlers/forms/entity_form.py index 076f195..add27c2 100644 --- a/bot/handlers/forms/entity_form.py +++ b/bot/handlers/forms/entity_form.py @@ -100,13 +100,13 @@ async def entity_item( ] field_value = getattr(entity_item, field_descriptor.field_name) if btn_caption: - btn_text = get_callable_str( + btn_text = await get_callable_str( btn_caption, field_descriptor, entity_item, field_value ) else: if field_descriptor.type_base is bool: btn_text = f"{'【✔︎】 ' if field_value else '【 】 '}{ - get_callable_str( + await get_callable_str( field_descriptor.caption, field_descriptor, entity_item, @@ -116,18 +116,20 @@ async def entity_item( else field_name }" else: - btn_text = ( - f"✏️ { - get_callable_str( - field_descriptor.caption, - field_descriptor, - entity_item, - field_value, - ) - }" + btn_text = f"{ + field_descriptor.icon + if field_descriptor.icon + else '✏️' + } { + await get_callable_str( + field_descriptor.caption, + field_descriptor, + entity_item, + field_value, + ) if field_descriptor.caption - else f"✏️ {field_name}" - ) + else field_name + }" btn_row.append( InlineKeyboardButton( text=btn_text, @@ -144,7 +146,7 @@ async def entity_item( elif isinstance(button, CommandButton): btn_caption = button.caption - btn_text = get_callable_str( + btn_text = await get_callable_str( btn_caption, entity_descriptor, entity_item ) @@ -215,7 +217,7 @@ async def entity_item( item_text = form.item_repr(entity_descriptor, entity_item) else: entity_caption = ( - get_callable_str( + await get_callable_str( entity_descriptor.full_name, entity_descriptor, entity_item ) if entity_descriptor.full_name @@ -223,7 +225,7 @@ async def entity_item( ) entity_item_repr = ( - get_callable_str( + await get_callable_str( entity_descriptor.item_repr, entity_descriptor, entity_item ) if entity_descriptor.item_repr @@ -234,18 +236,18 @@ async def entity_item( for field_descriptor in entity_descriptor.fields_descriptors.values(): if field_descriptor.is_visible: - field_caption = get_callable_str( + field_caption = await get_callable_str( field_descriptor.caption, field_descriptor, entity_item ) if field_descriptor.caption_value: - value = get_callable_str( + value = await get_callable_str( field_descriptor.caption_value, field_descriptor, entity_item, getattr(entity_item, field_descriptor.field_name), ) else: - value = get_value_repr( + value = await get_value_repr( value=getattr(entity_item, field_descriptor.field_name), field_descriptor=field_descriptor, locale=user.lang, diff --git a/bot/handlers/forms/entity_form_callbacks.py b/bot/handlers/forms/entity_form_callbacks.py index 55e4af7..b82fb0a 100644 --- a/bot/handlers/forms/entity_form_callbacks.py +++ b/bot/handlers/forms/entity_form_callbacks.py @@ -61,7 +61,7 @@ async def entity_delete_callback(query: CallbackQuery, **kwargs): return await query.message.edit_text( text=( await Settings.get(Settings.APP_STRINGS_CONFIRM_DELETE_P_NAME) - ).format(name=get_entity_item_repr(entity=entity)), + ).format(name=await get_entity_item_repr(entity=entity)), reply_markup=InlineKeyboardBuilder() .row( InlineKeyboardButton( diff --git a/bot/handlers/forms/entity_list.py b/bot/handlers/forms/entity_list.py index 9d11beb..aa22d88 100644 --- a/bot/handlers/forms/entity_list.py +++ b/bot/handlers/forms/entity_list.py @@ -197,7 +197,7 @@ async def entity_list( caption = entity_descriptor.item_repr(entity_descriptor, item) elif entity_descriptor.full_name: caption = f"{ - get_callable_str( + await get_callable_str( callable_str=entity_descriptor.full_name, descriptor=entity_descriptor, entity=item, @@ -228,7 +228,7 @@ async def entity_list( ) if form_list.filtering and form_list.filtering_fields: - add_filter_controls( + await add_filter_controls( keyboard_builder=keyboard_builder, entity_descriptor=entity_descriptor, filter=entity_filter, @@ -245,17 +245,17 @@ async def entity_list( ) if form_list.caption: - entity_text = get_callable_str(form_list.caption, entity_descriptor) + entity_text = await get_callable_str(form_list.caption, entity_descriptor) else: if entity_descriptor.full_name_plural: - entity_text = get_callable_str( + entity_text = await get_callable_str( entity_descriptor.full_name_plural, entity_descriptor ) else: entity_text = entity_descriptor.name if entity_descriptor.description: - entity_text = f"{entity_text} {get_callable_str(entity_descriptor.description, entity_descriptor)}" + entity_text = f"{entity_text} {await get_callable_str(entity_descriptor.description, entity_descriptor)}" state: FSMContext = kwargs["state"] state_data = kwargs["state_data"] diff --git a/bot/handlers/menu/parameters.py b/bot/handlers/menu/parameters.py index 1e676cc..97d4c1c 100644 --- a/bot/handlers/menu/parameters.py +++ b/bot/handlers/menu/parameters.py @@ -54,12 +54,12 @@ async def parameters_menu( continue if key.caption_value: - caption = get_callable_str( + caption = await get_callable_str( callable_str=key.caption_value, descriptor=key, entity=None, value=value ) else: if key.caption: - caption = get_callable_str( + caption = await get_callable_str( callable_str=key.caption, descriptor=key, entity=None, value=value ) else: @@ -68,7 +68,7 @@ async def parameters_menu( if key.type_ is bool: caption = f"{'【✔︎】' if value else '【 】'} {caption}" else: - caption = f"{caption}: {get_value_repr(value=value, field_descriptor=key, locale=user.lang)}" + caption = f"{caption}: {await get_value_repr(value=value, field_descriptor=key, locale=user.lang)}" keyboard_builder.row( InlineKeyboardButton( diff --git a/bot/handlers/start.py b/bot/handlers/start.py index 691f8f4..fa632f5 100644 --- a/bot/handlers/start.py +++ b/bot/handlers/start.py @@ -8,6 +8,7 @@ from sqlmodel.ext.asyncio.session import AsyncSession from ...main import QBotApp from ...model.settings import Settings from ...model.language import LanguageBase +from ...model.user import UserBase from ...utils.main import clear_state @@ -16,9 +17,24 @@ router = Router() @router.message(CommandStart()) -async def start( - message: Message, db_session: AsyncSession, app: QBotApp, state: FSMContext -): +async def start(message: Message, **kwargs): + app: QBotApp = kwargs["app"] + + if app.start_handler: + await app.start_handler( + default_start_handler, message, **kwargs + ) + else: + await default_start_handler(message, **kwargs) + + +async def default_start_handler[UserType: UserBase]( + message: Message, + db_session: AsyncSession, + app: QBotApp, + state: FSMContext, + **kwargs, +) -> tuple[UserType, bool]: state_data = await state.get_data() clear_state(state_data=state_data, clear_nav=True) @@ -27,6 +43,7 @@ async def start( user = await User.get(session=db_session, id=message.from_user.id) if not user: + is_new = True msg_text = (await Settings.get(Settings.APP_STRINGS_WELCOME_P_NAME)).format( name=message.from_user.full_name ) @@ -61,6 +78,7 @@ async def start( return else: + is_new = False if user.is_active: msg_text = ( await Settings.get(Settings.APP_STRINGS_GREETING_P_NAME) @@ -71,3 +89,5 @@ async def start( ).format(name=user.name) await message.answer(msg_text) + + return user, is_new diff --git a/main.py b/main.py index e12ce1c..3fcb2b8 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,4 @@ -from typing import Annotated, Callable, Any -from typing_extensions import Doc +from typing import Callable, Any from aiogram import Bot, Dispatcher from aiogram.client.default import DefaultBotProperties from aiogram.types import Message @@ -18,37 +17,23 @@ from .model.descriptors import BotCommand from .router import Router -class QBotApp(FastAPI): +class QBotApp[UserType: UserBase](FastAPI): """ Main class for the QBot application """ - def __init__[UserType: UserBase]( + def __init__( self, - user_class: ( - Annotated[ - type[UserType], Doc("User class that will be used in the application") - ] - | None - ) = None, + user_class: UserType = None, config: Config | None = None, - bot_start: ( - Annotated[ - Callable[ - [ - Annotated[ - Callable[[Message, Any], None], - Doc("Default handler for the start command"), - ], - Message, - Any, - ], - None, - ], - Doc("Handler for the start command"), - ] - | None - ) = None, + bot_start: Callable[ + [ + Callable[[Message, Any], tuple[UserType, bool]], + Message, + Any, + ], + None, + ] = None, lifespan: Lifespan[AppType] | None = None, *args, **kwargs, diff --git a/utils/main.py b/utils/main.py index ba09c13..46a7f03 100644 --- a/utils/main.py +++ b/utils/main.py @@ -1,5 +1,5 @@ from babel.support import LazyProxy -from inspect import signature +from inspect import iscoroutinefunction, signature from aiogram.types import Message, CallbackQuery from aiogram.utils.i18n import I18n from typing import Any, TYPE_CHECKING @@ -105,7 +105,7 @@ def clear_state(state_data: dict, clear_nav: bool = False): state_data["navigation_context"] = context -def get_entity_item_repr( +async def get_entity_item_repr( entity: BotEntity, item_repr: EntityItemCaptionCallable | None = None ) -> str: descr = entity.bot_entity_descriptor @@ -115,14 +115,14 @@ def get_entity_item_repr( descr.item_repr(descr, entity) if descr.item_repr else f"{ - get_callable_str(descr.full_name, descr, entity) + await get_callable_str(descr.full_name, descr, entity) if descr.full_name else descr.name }: {str(entity.id)}" ) -def get_value_repr( +async def get_value_repr( value: Any, field_descriptor: FieldDescriptor, locale: str | None = None ) -> str: if value is None: @@ -133,7 +133,9 @@ def get_value_repr( return "【✔︎】" if value else "【 】" elif field_descriptor.is_list: if issubclass(type_, BotEntity): - return f"[{', '.join([get_entity_item_repr(item) for item in value])}]" + return ( + f"[{', '.join([await get_entity_item_repr(item) for item in value])}]" + ) elif issubclass(type_, BotEnum): return f"[{', '.join(item.localized(locale) for item in value)}]" elif type_ is str: @@ -141,7 +143,7 @@ def get_value_repr( else: return f"[{', '.join([str(item) for item in value])}]" elif issubclass(type_, BotEntity): - return get_entity_item_repr(value) + return await get_entity_item_repr(value) elif issubclass(type_, BotEnum): return value.localized(locale) elif isinstance(value, str): @@ -156,7 +158,7 @@ def get_value_repr( return str(value) -def get_callable_str( +async def get_callable_str( callable_str: ( str | LazyProxy @@ -174,12 +176,20 @@ def get_callable_str( return callable_str.value elif callable(callable_str): args = signature(callable_str).parameters - if len(args) == 1: - return callable_str(descriptor) - elif len(args) == 2: - return callable_str(descriptor, entity) - elif len(args) == 3: - return callable_str(descriptor, entity, value) + if iscoroutinefunction(callable_str): + if len(args) == 1: + return await callable_str(descriptor) + elif len(args) == 2: + return await callable_str(descriptor, entity) + elif len(args) == 3: + return await callable_str(descriptor, entity, value) + else: + if len(args) == 1: + return callable_str(descriptor) + elif len(args) == 2: + return callable_str(descriptor, entity) + elif len(args) == 3: + return callable_str(descriptor, entity, value) def get_entity_descriptor(