From 256fc7ec75aa90c61d3eab585459c1bda80aff2b Mon Sep 17 00:00:00 2001 From: bomzheg Date: Tue, 30 Jul 2024 22:02:42 +0300 Subject: [PATCH 01/34] render from jinja --- shvatka/tgbot/dialogs/level_manage/dialogs.py | 9 +++++++- shvatka/tgbot/dialogs/level_manage/getters.py | 2 +- shvatka/tgbot/dialogs/level_scn/dialogs.py | 21 +++++++++++++++---- shvatka/tgbot/dialogs/level_scn/getters.py | 2 -- shvatka/tgbot/dialogs/time_hint/dialogs.py | 2 +- shvatka/tgbot/dialogs/time_hint/getters.py | 1 - shvatka/tgbot/views/jinja_filters/__init__.py | 5 +++++ shvatka/tgbot/views/utils.py | 6 +++++- 8 files changed, 37 insertions(+), 11 deletions(-) diff --git a/shvatka/tgbot/dialogs/level_manage/dialogs.py b/shvatka/tgbot/dialogs/level_manage/dialogs.py index 7b1c6e88..35c646bf 100644 --- a/shvatka/tgbot/dialogs/level_manage/dialogs.py +++ b/shvatka/tgbot/dialogs/level_manage/dialogs.py @@ -44,7 +44,14 @@ level_manage = Dialog( Window( - Jinja("Уровень {{level.name_id}}\n{{rendered}}"), + Jinja( + "Уровень {{level.name_id}}\n" + "{% if time_hints %}" + "{{time_hints | time_hints}}" + "{% else %}" + "пока нет ни одной подсказки" + "{% endif %}" + ), Button( Const("✏Редактирование"), id="level_edit", diff --git a/shvatka/tgbot/dialogs/level_manage/getters.py b/shvatka/tgbot/dialogs/level_manage/getters.py index 6037afcc..07990bd8 100644 --- a/shvatka/tgbot/dialogs/level_manage/getters.py +++ b/shvatka/tgbot/dialogs/level_manage/getters.py @@ -15,8 +15,8 @@ async def get_level_id(dao: HolderDao, dialog_manager: DialogManager, **_): hints = level.scenario.time_hints return { "level": level, + "time_hints": hints, "org": org, - "rendered": render_time_hints(hints) if hints else "пока нет ни одной подсказки", } diff --git a/shvatka/tgbot/dialogs/level_scn/dialogs.py b/shvatka/tgbot/dialogs/level_scn/dialogs.py index 4b62a8a0..2f0e5bd5 100644 --- a/shvatka/tgbot/dialogs/level_scn/dialogs.py +++ b/shvatka/tgbot/dialogs/level_scn/dialogs.py @@ -64,7 +64,11 @@ "💰Бонусных ключей: {{bonus_keys | length}}\n" "{% endif %}" "\n💡Подсказки:\n" - "{{rendered}}" + "{% if time_hints %}" + "{{time_hints | time_hints}}" + "{% else %}" + "пока нет ни одной" + "{% endif %}" ), Button(Const("🔑Ключи"), id="keys", on_click=start_keys), Button(Const("💰Бонусные ключи"), id="bonus_keys", on_click=start_bonus_keys), @@ -97,7 +101,11 @@ "💰Бонусных ключей: {{bonus_keys | length}}\n" "{% endif %}" "\n💡Подсказки:\n" - "{{rendered}}" + "{% if time_hints %}" + "{{time_hints | time_hints}}" + "{% else %}" + "пока нет ни одной" + "{% endif %}" ), Button(Const("🔑Ключи"), id="keys", on_click=start_keys), Button(Const("💰Бонусные ключи"), id="bonus_keys", on_click=start_bonus_keys), @@ -158,7 +166,13 @@ hints_dialog = Dialog( Window( Jinja("💡Подсказки уровня {{level_id}}:\n"), - Jinja("{{rendered}}"), + Jinja( + "{% if time_hints %}" + "{{time_hints | time_hints}}" + "{% else %}" + "пока нет ни одной" + "{% endif %}" + ), Button(Const("➕Добавить подсказку"), id="add_time_hint", on_click=start_add_time_hint), Button( Const("👌Достаточно подсказок"), @@ -177,7 +191,6 @@ getter=get_time_hints, preview_data={ "time_hints": [], - "rendered": RENDERED_HINTS_PREVIEW, "level_id": "Pinky Pie", }, ), diff --git a/shvatka/tgbot/dialogs/level_scn/getters.py b/shvatka/tgbot/dialogs/level_scn/getters.py index e3e42a9a..0b1398ec 100644 --- a/shvatka/tgbot/dialogs/level_scn/getters.py +++ b/shvatka/tgbot/dialogs/level_scn/getters.py @@ -38,7 +38,6 @@ async def get_level_data(dialog_manager: DialogManager, **_): "keys": dialog_data.get("keys", []), "bonus_keys": dialog_data.get("bonus_keys", []), "time_hints": hints, - "rendered": render_time_hints(hints) if hints else "пока нет ни одной", } @@ -49,5 +48,4 @@ async def get_time_hints(dialog_manager: DialogManager, **_): return { "level_id": dialog_manager.start_data["level_id"], "time_hints": hints, - "rendered": render_time_hints(hints) if hints else "пока нет ни одной", } diff --git a/shvatka/tgbot/dialogs/time_hint/dialogs.py b/shvatka/tgbot/dialogs/time_hint/dialogs.py index 52260bab..f35c1c8f 100644 --- a/shvatka/tgbot/dialogs/time_hint/dialogs.py +++ b/shvatka/tgbot/dialogs/time_hint/dialogs.py @@ -34,7 +34,7 @@ { False: Const("Присылай сообщения с подсказками (текст, фото, видео итд)"), True: Jinja( - "{{rendered}}\n" + "{{hints | hints}}\n" "Можно прислать ещё сообщения или перейти к следующей подсказке" ), }, diff --git a/shvatka/tgbot/dialogs/time_hint/getters.py b/shvatka/tgbot/dialogs/time_hint/getters.py index 04a5710a..ff33c622 100644 --- a/shvatka/tgbot/dialogs/time_hint/getters.py +++ b/shvatka/tgbot/dialogs/time_hint/getters.py @@ -26,5 +26,4 @@ async def get_hints(dialog_manager: DialogManager, **_): "hints": hints, "time": time_, "has_hints": len(hints) > 0, - "rendered": render_hints(hints), } diff --git a/shvatka/tgbot/views/jinja_filters/__init__.py b/shvatka/tgbot/views/jinja_filters/__init__.py index 1ca15366..f6e70502 100644 --- a/shvatka/tgbot/views/jinja_filters/__init__.py +++ b/shvatka/tgbot/views/jinja_filters/__init__.py @@ -6,6 +6,7 @@ from .boolean_emoji import bool_render from .game_status import to_readable_name from .timezone import datetime_filter, timedelta_filter +from ..utils import render_single_hint, render_hints, render_time_hint, render_time_hints def setup_jinja(bot: Bot): @@ -18,5 +19,9 @@ def setup_jinja(bot: Bot): "bool_emoji": bool_render, "game_status": to_readable_name, "timedelta": timedelta_filter, + "single_hint": render_single_hint, + "hints": render_hints, + "time_hint": render_time_hint, + "time_hints": render_time_hints, }, ) diff --git a/shvatka/tgbot/views/utils.py b/shvatka/tgbot/views/utils.py index f719f70d..bd462400 100644 --- a/shvatka/tgbot/views/utils.py +++ b/shvatka/tgbot/views/utils.py @@ -51,4 +51,8 @@ def render_time_hint(time_hint: TimeHint) -> str: def render_hints(hints: list[BaseHint]) -> str: - return "".join([HINTS_EMOJI[HintType[hint.type]] for hint in hints]) + return "".join([render_single_hint(hint) for hint in hints]) + + +def render_single_hint(hint: BaseHint) -> str: + return HINTS_EMOJI[HintType[hint.type]] From e56259801ec55e898baab91bacd0ea1ca11a6622 Mon Sep 17 00:00:00 2001 From: bomzheg Date: Tue, 30 Jul 2024 22:37:53 +0300 Subject: [PATCH 02/34] added dialog for hint edit --- shvatka/tgbot/dialogs/level_scn/dialogs.py | 19 +++++++++++++++---- shvatka/tgbot/dialogs/level_scn/handlers.py | 9 +++++++++ shvatka/tgbot/dialogs/preview_data.py | 1 - shvatka/tgbot/dialogs/time_hint/__init__.py | 3 ++- shvatka/tgbot/dialogs/time_hint/dialogs.py | 18 ++++++++++++++++-- shvatka/tgbot/dialogs/time_hint/handlers.py | 8 ++++++++ shvatka/tgbot/states.py | 4 ++++ 7 files changed, 54 insertions(+), 8 deletions(-) diff --git a/shvatka/tgbot/dialogs/level_scn/dialogs.py b/shvatka/tgbot/dialogs/level_scn/dialogs.py index 2f0e5bd5..af84acba 100644 --- a/shvatka/tgbot/dialogs/level_scn/dialogs.py +++ b/shvatka/tgbot/dialogs/level_scn/dialogs.py @@ -1,8 +1,8 @@ from aiogram import F from aiogram_dialog import Dialog, Window from aiogram_dialog.widgets.input import TextInput -from aiogram_dialog.widgets.kbd import Button, Cancel -from aiogram_dialog.widgets.text import Const, Jinja +from aiogram_dialog.widgets.kbd import Button, Cancel, ScrollingGroup, Select +from aiogram_dialog.widgets.text import Const, Jinja, Format from shvatka.tgbot import states from .getters import get_time_hints, get_level_id, get_level_data, get_keys, get_bonus_keys @@ -26,9 +26,8 @@ convert_bonus_keys, on_correct_bonus_keys, not_correct_bonus_keys, - start_bonus_keys, + start_bonus_keys, start_edit_time_hint, ) -from shvatka.tgbot.dialogs.preview_data import RENDERED_HINTS_PREVIEW level = Dialog( Window( @@ -173,6 +172,18 @@ "пока нет ни одной" "{% endif %}" ), + ScrollingGroup( + Select( + Jinja("{{item | time_hint}}"), + id="level_hints", + item_id_getter=lambda x: x.time, + items="time_hints", + on_click=start_edit_time_hint, + ), + id="level_hints_sg", + width=1, + height=10, + ), Button(Const("➕Добавить подсказку"), id="add_time_hint", on_click=start_add_time_hint), Button( Const("👌Достаточно подсказок"), diff --git a/shvatka/tgbot/dialogs/level_scn/handlers.py b/shvatka/tgbot/dialogs/level_scn/handlers.py index 9c67da10..f15da9de 100644 --- a/shvatka/tgbot/dialogs/level_scn/handlers.py +++ b/shvatka/tgbot/dialogs/level_scn/handlers.py @@ -133,6 +133,15 @@ async def on_start_hints_edit(start_data: dict[str, Any], manager: DialogManager manager.dialog_data["time_hints"] = start_data["time_hints"] +async def start_edit_time_hint(c: CallbackQuery, widget: Any, manager: DialogManager, hint_time: str): + dcf: Factory = manager.middleware_data["dcf"] + hints = dcf.load(manager.dialog_data.get("time_hints", []), list[scn.TimeHint]) + await manager.start( + state=states.TimeHintEditSG.details, + data={"time_hint": dcf.dump(next(filter(lambda x: x.time == int(hint_time), hints)))} + ) + + async def start_add_time_hint(c: CallbackQuery, button: Button, manager: DialogManager): dcf: Factory = manager.middleware_data["dcf"] hints = dcf.load(manager.dialog_data.get("time_hints", []), list[scn.TimeHint]) diff --git a/shvatka/tgbot/dialogs/preview_data.py b/shvatka/tgbot/dialogs/preview_data.py index aaf2ea55..0c30341c 100644 --- a/shvatka/tgbot/dialogs/preview_data.py +++ b/shvatka/tgbot/dialogs/preview_data.py @@ -34,4 +34,3 @@ levels_count=13, ) TIMES_PRESET = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60] -RENDERED_HINTS_PREVIEW = "0: 📃🪪\n10: 📃\n10: 📃\n15: 📃\n20: 📃\n25: 🪪\n30: 📡\n45: 📃" diff --git a/shvatka/tgbot/dialogs/time_hint/__init__.py b/shvatka/tgbot/dialogs/time_hint/__init__.py index 81fd4bae..49726281 100644 --- a/shvatka/tgbot/dialogs/time_hint/__init__.py +++ b/shvatka/tgbot/dialogs/time_hint/__init__.py @@ -1,7 +1,8 @@ from aiogram import Router -from .dialogs import time_hint +from .dialogs import time_hint, time_hint_edit def setup(router: Router): router.include_router(time_hint) + router.include_router(time_hint_edit) diff --git a/shvatka/tgbot/dialogs/time_hint/dialogs.py b/shvatka/tgbot/dialogs/time_hint/dialogs.py index f35c1c8f..d48b7e9d 100644 --- a/shvatka/tgbot/dialogs/time_hint/dialogs.py +++ b/shvatka/tgbot/dialogs/time_hint/dialogs.py @@ -5,7 +5,7 @@ from shvatka.tgbot import states from .getters import get_available_times, get_hints -from .handlers import process_time_message, select_time, process_hint, on_finish, hint_on_start +from .handlers import process_time_message, select_time, process_hint, on_finish, hint_on_start, hint_edit_on_start from shvatka.tgbot.dialogs.preview_data import TIMES_PRESET time_hint = Dialog( @@ -50,7 +50,21 @@ Back(text=Const("Изменить время")), getter=get_hints, state=states.TimeHintSG.hint, - preview_data={"has_hints": True, "rendered": "📃🪪"}, + preview_data={"has_hints": True}, ), on_start=hint_on_start, ) + + +time_hint_edit = Dialog( + Window( + Jinja( + "Подсказка выходящая в {{time}}:" + "{{hints | hints}}" + ), + Cancel(text=Const("Вернуться, не нужна подсказка")), + getter=get_hints, + state=states.TimeHintEditSG.details, + ), + on_start=hint_edit_on_start, +) \ No newline at end of file diff --git a/shvatka/tgbot/dialogs/time_hint/handlers.py b/shvatka/tgbot/dialogs/time_hint/handlers.py index 1b53798a..c608e097 100644 --- a/shvatka/tgbot/dialogs/time_hint/handlers.py +++ b/shvatka/tgbot/dialogs/time_hint/handlers.py @@ -5,6 +5,7 @@ from aiogram_dialog.widgets.kbd import Button from dataclass_factory import Factory +from shvatka.core.models.dto import scn from shvatka.core.models.dto.scn import TimeHint from shvatka.core.models.dto.scn.hint_part import AnyHint from shvatka.tgbot import states @@ -58,3 +59,10 @@ async def hint_on_start(start_data: dict, manager: DialogManager): manager.dialog_data["time"] = 0 await manager.switch_to(states.TimeHintSG.hint) manager.dialog_data.setdefault("hints", []) + + +async def hint_edit_on_start(start_data: dict, manager: DialogManager): + dcf: Factory = manager.middleware_data["dcf"] + hint = dcf.load(manager.start_data["time_hint"], scn.TimeHint) + manager.dialog_data["hints"] = dcf.dump(hint.hint, list[AnyHint]) + manager.dialog_data["time"] = hint.time diff --git a/shvatka/tgbot/states.py b/shvatka/tgbot/states.py index 04524197..b55b095d 100644 --- a/shvatka/tgbot/states.py +++ b/shvatka/tgbot/states.py @@ -18,6 +18,10 @@ class TimeHintSG(StatesGroup): hint = State() +class TimeHintEditSG(StatesGroup): + details = State() + + class LevelListSG(StatesGroup): levels = State() From f4c32e4a301f75d75dad26bfc2022a2043cb774f Mon Sep 17 00:00:00 2001 From: bomzheg Date: Fri, 23 Aug 2024 23:14:37 +0300 Subject: [PATCH 03/34] added select any hint --- shvatka/tgbot/dialogs/level_scn/handlers.py | 11 +++++++ shvatka/tgbot/dialogs/time_hint/dialogs.py | 35 ++++++++++++++++++--- shvatka/tgbot/dialogs/time_hint/getters.py | 1 + shvatka/tgbot/dialogs/time_hint/handlers.py | 10 ++++++ shvatka/tgbot/states.py | 1 + 5 files changed, 54 insertions(+), 4 deletions(-) diff --git a/shvatka/tgbot/dialogs/level_scn/handlers.py b/shvatka/tgbot/dialogs/level_scn/handlers.py index f15da9de..e4eb504b 100644 --- a/shvatka/tgbot/dialogs/level_scn/handlers.py +++ b/shvatka/tgbot/dialogs/level_scn/handlers.py @@ -4,6 +4,7 @@ from aiogram_dialog import Data, DialogManager from aiogram_dialog.widgets.kbd import Button from dataclass_factory import Factory +from dishka import AsyncContainer from shvatka.core.models import dto from shvatka.core.models.dto import scn @@ -16,6 +17,7 @@ ) from shvatka.infrastructure.db.dao.holder import HolderDao from shvatka.tgbot import states +from shvatka.tgbot.views.hint_sender import HintSender def check_level_id(name_id: str) -> str: @@ -142,6 +144,15 @@ async def start_edit_time_hint(c: CallbackQuery, widget: Any, manager: DialogMan ) +async def edit_single_hint(c: CallbackQuery, widget: Any, manager: DialogManager, hint_index: str): + dcf: Factory = manager.middleware_data["dcf"] + dishka: AsyncContainer = manager.middleware_data["dishka_container"] + hint = dcf.load(manager.start_data.get("time_hint"), scn.TimeHint) + hint_sender = await dishka.get(HintSender) + await hint_sender.send_hint(hint.hint[int(hint_index)], c.message.chat.id) + + + async def start_add_time_hint(c: CallbackQuery, button: Button, manager: DialogManager): dcf: Factory = manager.middleware_data["dcf"] hints = dcf.load(manager.dialog_data.get("time_hints", []), list[scn.TimeHint]) diff --git a/shvatka/tgbot/dialogs/time_hint/dialogs.py b/shvatka/tgbot/dialogs/time_hint/dialogs.py index d48b7e9d..2d0663ea 100644 --- a/shvatka/tgbot/dialogs/time_hint/dialogs.py +++ b/shvatka/tgbot/dialogs/time_hint/dialogs.py @@ -1,12 +1,14 @@ from aiogram_dialog import Dialog, Window from aiogram_dialog.widgets.input import MessageInput -from aiogram_dialog.widgets.kbd import Select, Button, Group, Back, Cancel +from aiogram_dialog.widgets.kbd import Select, Button, Group, Back, Cancel, SwitchTo, ScrollingGroup from aiogram_dialog.widgets.text import Const, Format, Case, Jinja from shvatka.tgbot import states from .getters import get_available_times, get_hints -from .handlers import process_time_message, select_time, process_hint, on_finish, hint_on_start, hint_edit_on_start +from .handlers import process_time_message, select_time, process_hint, on_finish, hint_on_start, hint_edit_on_start, \ + process_edit_time_message from shvatka.tgbot.dialogs.preview_data import TIMES_PRESET +from ..level_scn.handlers import start_edit_time_hint, edit_single_hint time_hint = Dialog( Window( @@ -62,9 +64,34 @@ "Подсказка выходящая в {{time}}:" "{{hints | hints}}" ), - Cancel(text=Const("Вернуться, не нужна подсказка")), + SwitchTo( + Const("Изменить время"), + id="change_time", + state=states.TimeHintEditSG.time, + ), + ScrollingGroup( + Select( + Jinja("{{item[1] | single_hint}}"), + id="hints", + item_id_getter=lambda x: x[0], + items="numerated_hints", + on_click=edit_single_hint, + ), + id="hints_sg", + width=1, + height=10, + ), + Cancel(text=Const("Вернуться, ничего не менять")), getter=get_hints, state=states.TimeHintEditSG.details, ), + Window( + Jinja( + "Введи новое время выхода подсказки" + ), + MessageInput(func=process_edit_time_message), + getter=get_hints, + state=states.TimeHintEditSG.time, + ), on_start=hint_edit_on_start, -) \ No newline at end of file +) diff --git a/shvatka/tgbot/dialogs/time_hint/getters.py b/shvatka/tgbot/dialogs/time_hint/getters.py index ff33c622..35f97cee 100644 --- a/shvatka/tgbot/dialogs/time_hint/getters.py +++ b/shvatka/tgbot/dialogs/time_hint/getters.py @@ -24,6 +24,7 @@ async def get_hints(dialog_manager: DialogManager, **_): time_ = dialog_data["time"] return { "hints": hints, + "numerated_hints": list(enumerate(hints)), "time": time_, "has_hints": len(hints) > 0, } diff --git a/shvatka/tgbot/dialogs/time_hint/handlers.py b/shvatka/tgbot/dialogs/time_hint/handlers.py index c608e097..7f8e85f7 100644 --- a/shvatka/tgbot/dialogs/time_hint/handlers.py +++ b/shvatka/tgbot/dialogs/time_hint/handlers.py @@ -16,6 +16,16 @@ async def select_time(c: CallbackQuery, widget: Any, manager: DialogManager, ite await set_time(int(item_id), manager) +async def process_edit_time_message(m: Message, dialog_: Any, manager: DialogManager) -> None: + try: + time_ = int(m.text) + except ValueError: + await m.answer("Некорректный формат времени. Пожалуйста введите время в формате ЧЧ:ММ") + return + manager.dialog_data["time"] = time_ + await manager.switch_to(states.TimeHintEditSG.details) + + async def process_time_message(m: Message, dialog_: Any, manager: DialogManager) -> None: try: time_ = int(m.text) diff --git a/shvatka/tgbot/states.py b/shvatka/tgbot/states.py index b55b095d..eb7d6f38 100644 --- a/shvatka/tgbot/states.py +++ b/shvatka/tgbot/states.py @@ -20,6 +20,7 @@ class TimeHintSG(StatesGroup): class TimeHintEditSG(StatesGroup): details = State() + time = State() class LevelListSG(StatesGroup): From 6c1cfd9fd20475cd1efafbd0d7b2a36f0d6461d1 Mon Sep 17 00:00:00 2001 From: bomzheg Date: Fri, 23 Aug 2024 23:25:59 +0300 Subject: [PATCH 04/34] added select any hint --- shvatka/tgbot/dialogs/game_manage/getters.py | 3 +- shvatka/tgbot/dialogs/level_manage/getters.py | 1 - shvatka/tgbot/dialogs/level_scn/dialogs.py | 5 +-- shvatka/tgbot/dialogs/level_scn/getters.py | 1 - shvatka/tgbot/dialogs/level_scn/handlers.py | 17 +++------- shvatka/tgbot/dialogs/time_hint/dialogs.py | 32 ++++++++++++------- shvatka/tgbot/dialogs/time_hint/getters.py | 1 - shvatka/tgbot/dialogs/time_hint/handlers.py | 13 ++++++++ shvatka/tgbot/views/jinja_filters/__init__.py | 7 +++- 9 files changed, 49 insertions(+), 31 deletions(-) diff --git a/shvatka/tgbot/dialogs/game_manage/getters.py b/shvatka/tgbot/dialogs/game_manage/getters.py index 26e497a3..e3b79f22 100644 --- a/shvatka/tgbot/dialogs/game_manage/getters.py +++ b/shvatka/tgbot/dialogs/game_manage/getters.py @@ -5,6 +5,7 @@ from aiogram_dialog import DialogManager from aiogram_dialog.api.entities import MediaAttachment, MediaId from dishka import AsyncContainer +from dishka.integrations.aiogram import CONTAINER_NAME from telegraph import Telegraph from shvatka.common.url_factory import UrlFactory @@ -30,7 +31,7 @@ async def get_completed_game(dao: HolderDao, dialog_manager: DialogManager, **_) game_id = ( dialog_manager.dialog_data.get("game_id", None) or dialog_manager.start_data["game_id"] ) - dishka: AsyncContainer = dialog_manager.middleware_data["dishka_container"] + dishka: AsyncContainer = dialog_manager.middleware_data[CONTAINER_NAME] url_factory = await dishka.get(UrlFactory) return { "game": await game.get_game( diff --git a/shvatka/tgbot/dialogs/level_manage/getters.py b/shvatka/tgbot/dialogs/level_manage/getters.py index 07990bd8..a61a70bd 100644 --- a/shvatka/tgbot/dialogs/level_manage/getters.py +++ b/shvatka/tgbot/dialogs/level_manage/getters.py @@ -6,7 +6,6 @@ from shvatka.core.services.level import get_by_id, get_level_by_id_for_org, get_all_my_free_levels from shvatka.core.services.organizers import get_org_by_id, get_by_player from shvatka.infrastructure.db.dao.holder import HolderDao -from shvatka.tgbot.views.utils import render_time_hints async def get_level_id(dao: HolderDao, dialog_manager: DialogManager, **_): diff --git a/shvatka/tgbot/dialogs/level_scn/dialogs.py b/shvatka/tgbot/dialogs/level_scn/dialogs.py index af84acba..0ebb9d83 100644 --- a/shvatka/tgbot/dialogs/level_scn/dialogs.py +++ b/shvatka/tgbot/dialogs/level_scn/dialogs.py @@ -2,7 +2,7 @@ from aiogram_dialog import Dialog, Window from aiogram_dialog.widgets.input import TextInput from aiogram_dialog.widgets.kbd import Button, Cancel, ScrollingGroup, Select -from aiogram_dialog.widgets.text import Const, Jinja, Format +from aiogram_dialog.widgets.text import Const, Jinja from shvatka.tgbot import states from .getters import get_time_hints, get_level_id, get_level_data, get_keys, get_bonus_keys @@ -26,7 +26,8 @@ convert_bonus_keys, on_correct_bonus_keys, not_correct_bonus_keys, - start_bonus_keys, start_edit_time_hint, + start_bonus_keys, + start_edit_time_hint, ) level = Dialog( diff --git a/shvatka/tgbot/dialogs/level_scn/getters.py b/shvatka/tgbot/dialogs/level_scn/getters.py index 0b1398ec..917a2699 100644 --- a/shvatka/tgbot/dialogs/level_scn/getters.py +++ b/shvatka/tgbot/dialogs/level_scn/getters.py @@ -3,7 +3,6 @@ from shvatka.core.models.dto import scn from shvatka.core.models.dto.scn import TimeHint -from shvatka.tgbot.views.utils import render_time_hints async def get_level_id(dialog_manager: DialogManager, **_): diff --git a/shvatka/tgbot/dialogs/level_scn/handlers.py b/shvatka/tgbot/dialogs/level_scn/handlers.py index e4eb504b..1dfbb89d 100644 --- a/shvatka/tgbot/dialogs/level_scn/handlers.py +++ b/shvatka/tgbot/dialogs/level_scn/handlers.py @@ -4,7 +4,6 @@ from aiogram_dialog import Data, DialogManager from aiogram_dialog.widgets.kbd import Button from dataclass_factory import Factory -from dishka import AsyncContainer from shvatka.core.models import dto from shvatka.core.models.dto import scn @@ -17,7 +16,6 @@ ) from shvatka.infrastructure.db.dao.holder import HolderDao from shvatka.tgbot import states -from shvatka.tgbot.views.hint_sender import HintSender def check_level_id(name_id: str) -> str: @@ -135,24 +133,17 @@ async def on_start_hints_edit(start_data: dict[str, Any], manager: DialogManager manager.dialog_data["time_hints"] = start_data["time_hints"] -async def start_edit_time_hint(c: CallbackQuery, widget: Any, manager: DialogManager, hint_time: str): +async def start_edit_time_hint( + c: CallbackQuery, widget: Any, manager: DialogManager, hint_time: str +): dcf: Factory = manager.middleware_data["dcf"] hints = dcf.load(manager.dialog_data.get("time_hints", []), list[scn.TimeHint]) await manager.start( state=states.TimeHintEditSG.details, - data={"time_hint": dcf.dump(next(filter(lambda x: x.time == int(hint_time), hints)))} + data={"time_hint": dcf.dump(next(filter(lambda x: x.time == int(hint_time), hints)))}, ) -async def edit_single_hint(c: CallbackQuery, widget: Any, manager: DialogManager, hint_index: str): - dcf: Factory = manager.middleware_data["dcf"] - dishka: AsyncContainer = manager.middleware_data["dishka_container"] - hint = dcf.load(manager.start_data.get("time_hint"), scn.TimeHint) - hint_sender = await dishka.get(HintSender) - await hint_sender.send_hint(hint.hint[int(hint_index)], c.message.chat.id) - - - async def start_add_time_hint(c: CallbackQuery, button: Button, manager: DialogManager): dcf: Factory = manager.middleware_data["dcf"] hints = dcf.load(manager.dialog_data.get("time_hints", []), list[scn.TimeHint]) diff --git a/shvatka/tgbot/dialogs/time_hint/dialogs.py b/shvatka/tgbot/dialogs/time_hint/dialogs.py index 2d0663ea..8b52afe7 100644 --- a/shvatka/tgbot/dialogs/time_hint/dialogs.py +++ b/shvatka/tgbot/dialogs/time_hint/dialogs.py @@ -1,14 +1,29 @@ from aiogram_dialog import Dialog, Window from aiogram_dialog.widgets.input import MessageInput -from aiogram_dialog.widgets.kbd import Select, Button, Group, Back, Cancel, SwitchTo, ScrollingGroup +from aiogram_dialog.widgets.kbd import ( + Select, + Button, + Group, + Back, + Cancel, + SwitchTo, + ScrollingGroup, +) from aiogram_dialog.widgets.text import Const, Format, Case, Jinja from shvatka.tgbot import states from .getters import get_available_times, get_hints -from .handlers import process_time_message, select_time, process_hint, on_finish, hint_on_start, hint_edit_on_start, \ - process_edit_time_message +from .handlers import ( + process_time_message, + select_time, + process_hint, + on_finish, + hint_on_start, + hint_edit_on_start, + process_edit_time_message, + edit_single_hint, +) from shvatka.tgbot.dialogs.preview_data import TIMES_PRESET -from ..level_scn.handlers import start_edit_time_hint, edit_single_hint time_hint = Dialog( Window( @@ -60,10 +75,7 @@ time_hint_edit = Dialog( Window( - Jinja( - "Подсказка выходящая в {{time}}:" - "{{hints | hints}}" - ), + Jinja("Подсказка выходящая в {{time}}:" "{{hints | hints}}"), SwitchTo( Const("Изменить время"), id="change_time", @@ -86,9 +98,7 @@ state=states.TimeHintEditSG.details, ), Window( - Jinja( - "Введи новое время выхода подсказки" - ), + Jinja("Введи новое время выхода подсказки"), MessageInput(func=process_edit_time_message), getter=get_hints, state=states.TimeHintEditSG.time, diff --git a/shvatka/tgbot/dialogs/time_hint/getters.py b/shvatka/tgbot/dialogs/time_hint/getters.py index 35f97cee..1d173396 100644 --- a/shvatka/tgbot/dialogs/time_hint/getters.py +++ b/shvatka/tgbot/dialogs/time_hint/getters.py @@ -2,7 +2,6 @@ from dataclass_factory import Factory from shvatka.core.models.dto.scn.hint_part import AnyHint -from shvatka.tgbot.views.utils import render_hints async def get_available_times(dialog_manager: DialogManager, **_): diff --git a/shvatka/tgbot/dialogs/time_hint/handlers.py b/shvatka/tgbot/dialogs/time_hint/handlers.py index 7f8e85f7..54ce1115 100644 --- a/shvatka/tgbot/dialogs/time_hint/handlers.py +++ b/shvatka/tgbot/dialogs/time_hint/handlers.py @@ -1,15 +1,19 @@ from typing import Any +from aiogram import types from aiogram.types import CallbackQuery, Message from aiogram_dialog import DialogManager from aiogram_dialog.widgets.kbd import Button from dataclass_factory import Factory +from dishka import AsyncContainer +from dishka.integrations.aiogram import CONTAINER_NAME from shvatka.core.models.dto import scn from shvatka.core.models.dto.scn import TimeHint from shvatka.core.models.dto.scn.hint_part import AnyHint from shvatka.tgbot import states from shvatka.tgbot.views.hint_factory.hint_parser import HintParser +from shvatka.tgbot.views.hint_sender import HintSender async def select_time(c: CallbackQuery, widget: Any, manager: DialogManager, item_id: str): @@ -38,6 +42,15 @@ async def process_time_message(m: Message, dialog_: Any, manager: DialogManager) await m.answer("Время выхода данной подсказки должно быть больше, чем предыдущей") +async def edit_single_hint(c: CallbackQuery, widget: Any, manager: DialogManager, hint_index: str): + dishka: AsyncContainer = manager.middleware_data[CONTAINER_NAME] + dcf = await dishka.get(Factory) + hint = dcf.load(manager.start_data.get("time_hint"), scn.TimeHint) + hint_sender = await dishka.get(HintSender) + chat: types.Chat = manager.middleware_data["event_from_chat"] + await hint_sender.send_hint(hint.hint[int(hint_index)], chat.id) + + async def set_time(time_minutes: int, manager: DialogManager): if time_minutes <= int(manager.start_data["previous_time"]): raise ValueError("Время меньше предыдущего") diff --git a/shvatka/tgbot/views/jinja_filters/__init__.py b/shvatka/tgbot/views/jinja_filters/__init__.py index f6e70502..d8dd418c 100644 --- a/shvatka/tgbot/views/jinja_filters/__init__.py +++ b/shvatka/tgbot/views/jinja_filters/__init__.py @@ -6,7 +6,12 @@ from .boolean_emoji import bool_render from .game_status import to_readable_name from .timezone import datetime_filter, timedelta_filter -from ..utils import render_single_hint, render_hints, render_time_hint, render_time_hints +from shvatka.tgbot.views.utils import ( + render_single_hint, + render_hints, + render_time_hint, + render_time_hints, +) def setup_jinja(bot: Bot): From 075be45762e44e6ad83faa9b11e51ffce7479e9e Mon Sep 17 00:00:00 2001 From: bomzheg Date: Fri, 23 Aug 2024 23:30:51 +0300 Subject: [PATCH 05/34] added todo --- shvatka/tgbot/dialogs/time_hint/handlers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/shvatka/tgbot/dialogs/time_hint/handlers.py b/shvatka/tgbot/dialogs/time_hint/handlers.py index 54ce1115..7ca4ac7d 100644 --- a/shvatka/tgbot/dialogs/time_hint/handlers.py +++ b/shvatka/tgbot/dialogs/time_hint/handlers.py @@ -47,6 +47,7 @@ async def edit_single_hint(c: CallbackQuery, widget: Any, manager: DialogManager dcf = await dishka.get(Factory) hint = dcf.load(manager.start_data.get("time_hint"), scn.TimeHint) hint_sender = await dishka.get(HintSender) + # TODO now it only show. but we want to show, to edit and to delete chat: types.Chat = manager.middleware_data["event_from_chat"] await hint_sender.send_hint(hint.hint[int(hint_index)], chat.id) From b33ef553c6b0abb3b5c4a4fe6c618b8df8aeefb9 Mon Sep 17 00:00:00 2001 From: bomzheg Date: Sat, 31 Aug 2024 23:18:41 +0300 Subject: [PATCH 06/34] added time_hint domain logic --- shvatka/core/models/dto/scn/time_hint.py | 25 +++++++++++ shvatka/core/utils/exceptions.py | 14 +++--- tests/unit/test_time_hint.py | 55 ++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 tests/unit/test_time_hint.py diff --git a/shvatka/core/models/dto/scn/time_hint.py b/shvatka/core/models/dto/scn/time_hint.py index 50ce427e..b6bf80d2 100644 --- a/shvatka/core/models/dto/scn/time_hint.py +++ b/shvatka/core/models/dto/scn/time_hint.py @@ -1,5 +1,6 @@ from dataclasses import dataclass +from shvatka.core.utils import exceptions from .hint_part import AnyHint @@ -8,6 +9,9 @@ class TimeHint: time: int hint: list[AnyHint] + def __post_init__(self): + _check_hint_not_empty(self.hint) + def get_guids(self) -> list[str]: guids = [] for hint in self.hint: @@ -18,7 +22,28 @@ def get_guids(self) -> list[str]: def hints_count(self) -> int: return len(self.hint) + def update_time(self, new_time: int) -> None: + if not self.can_update_time(): + raise exceptions.LevelError(text="Невозможно отредактировать загадку уровня") + if new_time == 0: + raise exceptions.LevelError(text="Нельзя заменить таким способом загадку уровня") + self.time = new_time + + def update_hint(self, new_hint: list[AnyHint]) -> None: + _check_hint_not_empty(new_hint) + self.hint = new_hint + + def can_update_time(self) -> bool: + return self.time != 0 + @dataclass class EnumeratedTimeHint(TimeHint): number: int + + +def _check_hint_not_empty(new_hint: list[AnyHint]) -> None: + if not new_hint: + raise exceptions.LevelError( + text="Нельзя установить пустой список подсказок. Вероятно нужно было удалить?" + ) diff --git a/shvatka/core/utils/exceptions.py b/shvatka/core/utils/exceptions.py index 48bc27e6..02b26486 100644 --- a/shvatka/core/utils/exceptions.py +++ b/shvatka/core/utils/exceptions.py @@ -1,6 +1,8 @@ +import typing from typing import Any -from shvatka.core.models import dto +if typing.TYPE_CHECKING: + from shvatka.core.models import dto class SHError(Exception): @@ -14,10 +16,10 @@ def __init__( chat_id: int | None = None, team_id: int | None = None, game_id: int | None = None, - user: dto.User | None = None, - player: dto.Player | None = None, - chat: dto.Chat | None = None, - team: dto.Team | None = None, + user: "dto.User | None" = None, + player: "dto.Player | None" = None, + chat: "dto.Chat | None" = None, + team: "dto.Team | None" = None, game: Any | None = None, alarm: bool | None = False, notify_user: str | None = None, @@ -163,7 +165,7 @@ class GameNotFound(GameError, AttributeError): class LevelError(SHError): notify_user = "Ошибка связанная с уровнем" - def __init__(self, level_id: int, *args, **kwargs) -> None: + def __init__(self, level_id: int | None = None, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.level_id = level_id diff --git a/tests/unit/test_time_hint.py b/tests/unit/test_time_hint.py new file mode 100644 index 00000000..9968159f --- /dev/null +++ b/tests/unit/test_time_hint.py @@ -0,0 +1,55 @@ +import pytest + +from shvatka.core.models.dto import scn +from shvatka.core.utils import exceptions + + +def test_create_ok_time_hint(): + time_hint = scn.TimeHint( + time=0, + hint=[scn.TextHint(text="some text")], + ) + assert time_hint.time == 0 + assert time_hint.hint == [scn.TextHint(text="some text")] + + +def test_create_empty_time_hint(): + with pytest.raises(exceptions.LevelError): + scn.TimeHint(time=5, hint=[]) + + +def test_edit_empty_time_hint(): + time_hint = scn.TimeHint( + time=0, + hint=[scn.TextHint(text="some text")], + ) + with pytest.raises(exceptions.LevelError): + time_hint.update_hint([]) + + +def test_ok_change_time(): + time_hint = scn.TimeHint( + time=5, + hint=[scn.TextHint(text="some text")], + ) + time_hint.update_time(10) + assert time_hint.time == 10 + assert time_hint.hint == [scn.TextHint(text="some text")] + + +def test_change_time_for_0(): + time_hint = scn.TimeHint( + time=0, + hint=[scn.TextHint(text="some text")], + ) + with pytest.raises(exceptions.LevelError): + time_hint.update_time(5) + + +def test_change_time_to_0(): + time_hint = scn.TimeHint( + time=5, + hint=[scn.TextHint(text="some text")], + ) + with pytest.raises(exceptions.LevelError): + time_hint.update_time(0) From b8fdf4a7225379db2b44dd6a59c8bc5095e9721f Mon Sep 17 00:00:00 2001 From: bomzheg Date: Sat, 31 Aug 2024 23:33:44 +0300 Subject: [PATCH 07/34] save time --- shvatka/tgbot/dialogs/level_scn/handlers.py | 6 +++++- shvatka/tgbot/dialogs/time_hint/dialogs.py | 6 ++++++ shvatka/tgbot/dialogs/time_hint/handlers.py | 24 +++++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/shvatka/tgbot/dialogs/level_scn/handlers.py b/shvatka/tgbot/dialogs/level_scn/handlers.py index 1dfbb89d..d59e3bd9 100644 --- a/shvatka/tgbot/dialogs/level_scn/handlers.py +++ b/shvatka/tgbot/dialogs/level_scn/handlers.py @@ -103,8 +103,12 @@ async def on_correct_bonus_keys( async def process_time_hint_result(start_data: Data, result: Any, manager: DialogManager): if not result: return - if new_hint := result["time_hint"]: + if new_hint := result.get("time_hint", None): manager.dialog_data.setdefault("time_hints", []).append(new_hint) + elif (edited_hint := result.get("edited_time_hint")) and isinstance(start_data, dict): + old_hint = start_data["time_hint"] + assert edited_hint != old_hint + # TODO save me async def process_level_result(start_data: Data, result: Any, manager: DialogManager): diff --git a/shvatka/tgbot/dialogs/time_hint/dialogs.py b/shvatka/tgbot/dialogs/time_hint/dialogs.py index 8b52afe7..a1360faf 100644 --- a/shvatka/tgbot/dialogs/time_hint/dialogs.py +++ b/shvatka/tgbot/dialogs/time_hint/dialogs.py @@ -22,6 +22,7 @@ hint_edit_on_start, process_edit_time_message, edit_single_hint, + save_edited_time_hint, ) from shvatka.tgbot.dialogs.preview_data import TIMES_PRESET @@ -93,6 +94,11 @@ width=1, height=10, ), + Button( + text=Const("Сохранить изменения"), + id="save_time_hint", + on_click=save_edited_time_hint, + ), Cancel(text=Const("Вернуться, ничего не менять")), getter=get_hints, state=states.TimeHintEditSG.details, diff --git a/shvatka/tgbot/dialogs/time_hint/handlers.py b/shvatka/tgbot/dialogs/time_hint/handlers.py index 7ca4ac7d..4161cdbf 100644 --- a/shvatka/tgbot/dialogs/time_hint/handlers.py +++ b/shvatka/tgbot/dialogs/time_hint/handlers.py @@ -11,6 +11,7 @@ from shvatka.core.models.dto import scn from shvatka.core.models.dto.scn import TimeHint from shvatka.core.models.dto.scn.hint_part import AnyHint +from shvatka.core.utils import exceptions from shvatka.tgbot import states from shvatka.tgbot.views.hint_factory.hint_parser import HintParser from shvatka.tgbot.views.hint_sender import HintSender @@ -26,6 +27,15 @@ async def process_edit_time_message(m: Message, dialog_: Any, manager: DialogMan except ValueError: await m.answer("Некорректный формат времени. Пожалуйста введите время в формате ЧЧ:ММ") return + dcf: Factory = manager.middleware_data["dcf"] + hint = dcf.load(manager.start_data["time_hint"], scn.TimeHint) + if not hint.can_update_time(): + await m.reply( + "Увы, отредактировать время данной подсказки не получится. " + "Скорее всего это загадка уровня (Подсказка 0 мин.). " + "Придётся переделать прямо тут текст (или медиа, или что там)" + ) + return manager.dialog_data["time"] = time_ await manager.switch_to(states.TimeHintEditSG.details) @@ -52,6 +62,20 @@ async def edit_single_hint(c: CallbackQuery, widget: Any, manager: DialogManager await hint_sender.send_hint(hint.hint[int(hint_index)], chat.id) +async def save_edited_time_hint(c: CallbackQuery, widget: Any, manager: DialogManager): + dishka: AsyncContainer = manager.middleware_data[CONTAINER_NAME] + dcf = await dishka.get(Factory) + time_hint = dcf.load(manager.start_data["time_hint"], scn.TimeHint) + try: + time_hint.update_time(manager.dialog_data["time"]) + time_hint.update_hint(dcf.load(manager.dialog_data["hints"], list[AnyHint])) + except exceptions.LevelError as e: + assert isinstance(c.message, Message) + await c.message.reply(e.text) + return + await manager.done({"edited_time_hint": dcf.dump(time_hint)}) + + async def set_time(time_minutes: int, manager: DialogManager): if time_minutes <= int(manager.start_data["previous_time"]): raise ValueError("Время меньше предыдущего") From 737a4966d3eb540eda7cca8c862ceacfb86b9a60 Mon Sep 17 00:00:00 2001 From: bomzheg Date: Thu, 5 Sep 2024 09:41:11 +0300 Subject: [PATCH 08/34] added HintsList --- shvatka/common/data_examples.py | 408 ++++++++++++------------ shvatka/core/models/dto/scn/__init__.py | 2 +- shvatka/core/models/dto/scn/level.py | 99 +++++- 3 files changed, 295 insertions(+), 214 deletions(-) diff --git a/shvatka/common/data_examples.py b/shvatka/common/data_examples.py index 17122fdb..c0463f17 100644 --- a/shvatka/common/data_examples.py +++ b/shvatka/common/data_examples.py @@ -42,56 +42,58 @@ scenario=scn.LevelScenario( id="level_100", keys={"SH1"}, - time_hints=[ - scn.TimeHint( - time=0, - hint=[ - scn.TextHint( - text="level_100_0", - ), - ], - ), - scn.TimeHint( - time=10, - hint=[ - scn.TextHint( - text="level_100_10", - ), - ], - ), - scn.TimeHint( - time=20, - hint=[ - scn.TextHint( - text="level_100_20", - ), - ], - ), - scn.TimeHint( - time=30, - hint=[ - scn.TextHint( - text="level_100_20", - ), - ], - ), - scn.TimeHint( - time=40, - hint=[ - scn.TextHint( - text="level_100_20", - ), - ], - ), - scn.TimeHint( - time=60, - hint=[ - scn.TextHint( - text="level_100_20", - ), - ], - ), - ], + time_hints=scn.HintsList( + [ + scn.TimeHint( + time=0, + hint=[ + scn.TextHint( + text="level_100_0", + ), + ], + ), + scn.TimeHint( + time=10, + hint=[ + scn.TextHint( + text="level_100_10", + ), + ], + ), + scn.TimeHint( + time=20, + hint=[ + scn.TextHint( + text="level_100_20", + ), + ], + ), + scn.TimeHint( + time=30, + hint=[ + scn.TextHint( + text="level_100_20", + ), + ], + ), + scn.TimeHint( + time=40, + hint=[ + scn.TextHint( + text="level_100_20", + ), + ], + ), + scn.TimeHint( + time=60, + hint=[ + scn.TextHint( + text="level_100_20", + ), + ], + ), + ] + ), ), ), dto.Level( @@ -103,56 +105,58 @@ scenario=scn.LevelScenario( id="level_101", keys={"SH2"}, - time_hints=[ - scn.TimeHint( - time=0, - hint=[ - scn.TextHint( - text="level_101_0", - ), - ], - ), - scn.TimeHint( - time=10, - hint=[ - scn.TextHint( - text="level_101_10", - ), - ], - ), - scn.TimeHint( - time=20, - hint=[ - scn.TextHint( - text="level_101_20", - ), - ], - ), - scn.TimeHint( - time=30, - hint=[ - scn.TextHint( - text="level_101_20", - ), - ], - ), - scn.TimeHint( - time=40, - hint=[ - scn.TextHint( - text="level_101_20", - ), - ], - ), - scn.TimeHint( - time=60, - hint=[ - scn.TextHint( - text="level_101_20", - ), - ], - ), - ], + time_hints=scn.HintsList( + [ + scn.TimeHint( + time=0, + hint=[ + scn.TextHint( + text="level_101_0", + ), + ], + ), + scn.TimeHint( + time=10, + hint=[ + scn.TextHint( + text="level_101_10", + ), + ], + ), + scn.TimeHint( + time=20, + hint=[ + scn.TextHint( + text="level_101_20", + ), + ], + ), + scn.TimeHint( + time=30, + hint=[ + scn.TextHint( + text="level_101_20", + ), + ], + ), + scn.TimeHint( + time=40, + hint=[ + scn.TextHint( + text="level_101_20", + ), + ], + ), + scn.TimeHint( + time=60, + hint=[ + scn.TextHint( + text="level_101_20", + ), + ], + ), + ] + ), ), ), dto.Level( @@ -164,56 +168,58 @@ scenario=scn.LevelScenario( id="level_102", keys={"SH3"}, - time_hints=[ - scn.TimeHint( - time=0, - hint=[ - scn.TextHint( - text="level_102_0", - ), - ], - ), - scn.TimeHint( - time=10, - hint=[ - scn.TextHint( - text="level_102_10", - ), - ], - ), - scn.TimeHint( - time=20, - hint=[ - scn.TextHint( - text="level_102_20", - ), - ], - ), - scn.TimeHint( - time=30, - hint=[ - scn.TextHint( - text="level_102_20", - ), - ], - ), - scn.TimeHint( - time=40, - hint=[ - scn.TextHint( - text="level_102_20", - ), - ], - ), - scn.TimeHint( - time=60, - hint=[ - scn.TextHint( - text="level_102_20", - ), - ], - ), - ], + time_hints=scn.HintsList( + [ + scn.TimeHint( + time=0, + hint=[ + scn.TextHint( + text="level_102_0", + ), + ], + ), + scn.TimeHint( + time=10, + hint=[ + scn.TextHint( + text="level_102_10", + ), + ], + ), + scn.TimeHint( + time=20, + hint=[ + scn.TextHint( + text="level_102_20", + ), + ], + ), + scn.TimeHint( + time=30, + hint=[ + scn.TextHint( + text="level_102_20", + ), + ], + ), + scn.TimeHint( + time=40, + hint=[ + scn.TextHint( + text="level_102_20", + ), + ], + ), + scn.TimeHint( + time=60, + hint=[ + scn.TextHint( + text="level_102_20", + ), + ], + ), + ] + ), ), ), dto.Level( @@ -225,56 +231,58 @@ scenario=scn.LevelScenario( id="level_103", keys={"SH4"}, - time_hints=[ - scn.TimeHint( - time=0, - hint=[ - scn.TextHint( - text="level_103_0", - ), - ], - ), - scn.TimeHint( - time=10, - hint=[ - scn.TextHint( - text="level_103_10", - ), - ], - ), - scn.TimeHint( - time=20, - hint=[ - scn.TextHint( - text="level_103_20", - ), - ], - ), - scn.TimeHint( - time=30, - hint=[ - scn.TextHint( - text="level_103_20", - ), - ], - ), - scn.TimeHint( - time=40, - hint=[ - scn.TextHint( - text="level_103_20", - ), - ], - ), - scn.TimeHint( - time=60, - hint=[ - scn.TextHint( - text="level_103_20", - ), - ], - ), - ], + time_hints=scn.HintsList( + [ + scn.TimeHint( + time=0, + hint=[ + scn.TextHint( + text="level_103_0", + ), + ], + ), + scn.TimeHint( + time=10, + hint=[ + scn.TextHint( + text="level_103_10", + ), + ], + ), + scn.TimeHint( + time=20, + hint=[ + scn.TextHint( + text="level_103_20", + ), + ], + ), + scn.TimeHint( + time=30, + hint=[ + scn.TextHint( + text="level_103_20", + ), + ], + ), + scn.TimeHint( + time=40, + hint=[ + scn.TextHint( + text="level_103_20", + ), + ], + ), + scn.TimeHint( + time=60, + hint=[ + scn.TextHint( + text="level_103_20", + ), + ], + ), + ] + ), ), ), ], diff --git a/shvatka/core/models/dto/scn/__init__.py b/shvatka/core/models/dto/scn/__init__.py index bbd629bd..bc3bd059 100644 --- a/shvatka/core/models/dto/scn/__init__.py +++ b/shvatka/core/models/dto/scn/__init__.py @@ -25,6 +25,6 @@ PhotoHint, ContactHint, ) -from .level import LevelScenario, SHKey, BonusKey +from .level import LevelScenario, SHKey, BonusKey, HintsList from .parsed_zip import ParsedZip from .time_hint import TimeHint diff --git a/shvatka/core/models/dto/scn/level.py b/shvatka/core/models/dto/scn/level.py index 7133b75b..a7838a99 100644 --- a/shvatka/core/models/dto/scn/level.py +++ b/shvatka/core/models/dto/scn/level.py @@ -1,7 +1,11 @@ import typing +from collections.abc import Sequence from dataclasses import dataclass, field from datetime import timedelta +from typing import overload +from shvatka.core.utils import exceptions +from .hint_part import AnyHint from .time_hint import TimeHint, EnumeratedTimeHint SHKey: typing.TypeAlias = str @@ -21,10 +25,88 @@ def __hash__(self) -> int: return hash(self.text) +class HintsList(Sequence[TimeHint]): + def __init__(self, hints: list[TimeHint]): + self.verify(hints) + self.hints = hints + + @classmethod + def parse(cls, hints: list[TimeHint]): + return cls(cls.normalize(hints)) + + @staticmethod + def normalize(hints: list[TimeHint]) -> list[TimeHint]: + hint_map: dict[int, list[AnyHint]] = {} + for hint in hints: + if not hint.hint: + continue + hint_map.setdefault(hint.time, []).extend(hint.hint) + return [TimeHint(k, v) for k, v in sorted(hint_map.items(), key=lambda x: x[0])] + + @staticmethod + def verify(hints: Sequence[TimeHint]) -> None: + times: set[int] = set() + for hint in hints: + if hint.time in times: + raise exceptions.LevelError( + text=f"Contains multiple times hints for time {hint.time}" + ) + times.add(hint.time) + if not hint.hint: + raise exceptions.LevelError(text=f"There is no hint for time {hint.time}") + + def get_hint_by_time(self, time: timedelta) -> EnumeratedTimeHint: + hint = self.hints[0] + number = 0 + for i, h in enumerate(self.hints): + if timedelta(minutes=h.time) < time: + hint = h + number = i + else: + break + return EnumeratedTimeHint(time=hint.time, hint=hint.hint, number=number) + + def get_hints_for_timedelta(self, delta: timedelta) -> list[TimeHint]: + minutes = delta.total_seconds() // 60 + return [th for th in self.hints if th.time <= minutes] + + def replace(self, old: TimeHint, new: TimeHint) -> "HintsList": + for i, hint in enumerate(self.hints): + if hint.time == old.time: + old_index = i + break + else: + old_index = None + if old_index is None: + raise exceptions.LevelError( + text=f"can't replace, there is no hints for time {old.time}" + ) + result = self.hints[0:old_index] + self.hints[old_index + 1 :] + [new] + return self.__class__(self.normalize(result)) + + @property + def hints_count(self) -> int: + return sum(time_hint.hints_count for time_hint in self.hints) + + @overload + def __getitem__(self, index: int) -> TimeHint: + return self.hints[index] + + @overload + def __getitem__(self, index: slice) -> Sequence[TimeHint]: + return self.hints[index] + + def __getitem__(self, index): + return self.hints[index] + + def __len__(self): + return len(self.hints) + + @dataclass class LevelScenario: id: str - time_hints: list[TimeHint] + time_hints: HintsList keys: set[SHKey] = field(default_factory=set) bonus_keys: set[BonusKey] = field(default_factory=set) @@ -32,15 +114,7 @@ def get_hint(self, hint_number: int) -> TimeHint: return self.time_hints[hint_number] def get_hint_by_time(self, time: timedelta) -> EnumeratedTimeHint: - hint = self.time_hints[0] - number = 0 - for i, h in enumerate(self.time_hints): - if timedelta(minutes=h.time) < time: - hint = h - number = i - else: - break - return EnumeratedTimeHint(time=hint.time, hint=hint.hint, number=number) + return self.time_hints.get_hint_by_time(time) def is_last_hint(self, hint_number: int) -> bool: return len(self.time_hints) == hint_number + 1 @@ -59,8 +133,7 @@ def get_guids(self) -> list[str]: @property def hints_count(self) -> int: - return sum(time_hint.hints_count for time_hint in self.time_hints) + return self.time_hints.hints_count def get_hints_for_timedelta(self, delta: timedelta) -> list[TimeHint]: - minutes = delta.total_seconds() // 60 - return [th for th in self.time_hints if th.time <= minutes] + return self.time_hints.get_hints_for_timedelta(delta) From b5b0337b2bd888819919114008fa84c013f01003 Mon Sep 17 00:00:00 2001 From: bomzheg Date: Fri, 4 Oct 2024 22:46:40 +0300 Subject: [PATCH 09/34] migrate to adaptix for scn-models dump-restore --- shvatka/common/factory.py | 37 ++++++++++++++++++++ shvatka/core/services/scenario/game_ops.py | 14 ++++---- shvatka/core/services/scenario/level_ops.py | 10 +++--- tests/conftest.py | 7 ++++ tests/unit/serialization/test_deserialize.py | 26 +++++++------- 5 files changed, 69 insertions(+), 25 deletions(-) diff --git a/shvatka/common/factory.py b/shvatka/common/factory.py index 73caea12..dc6d9ca1 100644 --- a/shvatka/common/factory.py +++ b/shvatka/common/factory.py @@ -1,11 +1,19 @@ +import adaptix import dataclass_factory +from adaptix import Retort, validator, P, name_mapping, loader, Chain, as_is_loader, constructor, bound from dataclass_factory import Schema, NameStyle from dishka import Provider, Scope, provide +from pyrogram.errors.exceptions.all import exceptions from telegraph.aio import Telegraph from shvatka.common.url_factory import UrlFactory +from shvatka.core.models.dto import scn +from shvatka.core.models.dto.scn import HintsList, TimeHint from shvatka.core.models.schems import schemas +from shvatka.core.utils.input_validation import validate_level_id, is_multiple_keys_normal +from shvatka.core.views.texts import INVALID_KEY_ERROR from shvatka.tgbot.config.models.bot import BotConfig +from shvatka.tgbot.views.hint_factory.hint_parser import HintParser class TelegraphProvider(Provider): @@ -28,6 +36,35 @@ def create_dataclass_factory(self) -> dataclass_factory.Factory: ) return dcf + @provide + def create_retort(self) -> Retort: + internal_retort = Retort( + recipe=[ + name_mapping(name_style=adaptix.NameStyle.LOWER_KEBAB), + ] + ) + retort = Retort( + recipe=[ + name_mapping( + name_style=adaptix.NameStyle.LOWER_KEBAB, + ), + loader( + HintsList, lambda x: HintsList(internal_retort.load(x, list[TimeHint])), + ), + validator( + pred=P[scn.LevelScenario].id, + func=lambda x: validate_level_id(x) is not None, + error=lambda x: exceptions.ScenarioNotCorrect(name_id=x, text=f"name_id ({x}) not correct") + ), + validator( + pred=P[scn.LevelScenario].keys, + func=is_multiple_keys_normal, + error=lambda x: exceptions.ScenarioNotCorrect(notify_user=INVALID_KEY_ERROR, text="invalid keys") + ), + ] + ) + return retort + class UrlProvider(Provider): scope = Scope.APP diff --git a/shvatka/core/services/scenario/game_ops.py b/shvatka/core/services/scenario/game_ops.py index e045d18d..b77d4778 100644 --- a/shvatka/core/services/scenario/game_ops.py +++ b/shvatka/core/services/scenario/game_ops.py @@ -1,4 +1,4 @@ -from dataclass_factory import Factory +from adaptix import Retort from shvatka.core.models.dto import scn from shvatka.core.services.scenario.level_ops import ( @@ -6,16 +6,16 @@ ) -def parse_game(game: scn.RawGameScenario, dcf: Factory) -> scn.GameScenario: - return dcf.load(game.scn, scn.GameScenario) +def parse_game(game: scn.RawGameScenario, retort: Retort) -> scn.GameScenario: + return retort.load(game.scn, scn.GameScenario) -def parse_uploaded_game(game: scn.RawGameScenario, dcf: Factory) -> scn.UploadedGameScenario: - return dcf.load(game.scn, scn.UploadedGameScenario) +def parse_uploaded_game(game: scn.RawGameScenario, retort: Retort) -> scn.UploadedGameScenario: + return retort.load(game.scn, scn.UploadedGameScenario) -def serialize(game: scn.FullGameScenario, dcf: Factory) -> dict: - return dcf.dump(game) +def serialize(game: scn.FullGameScenario, retort: Retort) -> dict: + return retort.dump(game) def check_all_files_saved(game: scn.GameScenario, guids: set[str]): diff --git a/shvatka/core/services/scenario/level_ops.py b/shvatka/core/services/scenario/level_ops.py index 57651d9d..b4363586 100644 --- a/shvatka/core/services/scenario/level_ops.py +++ b/shvatka/core/services/scenario/level_ops.py @@ -1,14 +1,14 @@ -import dataclass_factory -from dataclass_factory import Factory +from adaptix import Retort +from adaptix.load_error import LoadError from shvatka.core.models.dto import scn from shvatka.core.utils import exceptions -def load_level(dct: dict, dcf: Factory) -> scn.LevelScenario: +def load_level(dct: dict, retort: Retort) -> scn.LevelScenario: try: - return dcf.load(dct, scn.LevelScenario) - except dataclass_factory.PARSER_EXCEPTIONS as e: + return retort.load(dct, scn.LevelScenario) + except LoadError as e: raise exceptions.ScenarioNotCorrect(notify_user="невалидный уровень") from e diff --git a/tests/conftest.py b/tests/conftest.py index 118f20a2..b69340ef 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,6 +4,7 @@ import pytest import pytest_asyncio +from adaptix import Retort from dataclass_factory import Factory from dishka import make_async_container @@ -41,3 +42,9 @@ def event_loop(): async def dcf(): dishka = make_async_container(DCFProvider()) return await dishka.get(Factory) + + +@pytest_asyncio.fixture(scope="session") +async def retort() -> Retort: + dishka = make_async_container(DCFProvider()) + return await dishka.get(Retort) diff --git a/tests/unit/serialization/test_deserialize.py b/tests/unit/serialization/test_deserialize.py index 0af61daa..bf8c53e5 100644 --- a/tests/unit/serialization/test_deserialize.py +++ b/tests/unit/serialization/test_deserialize.py @@ -1,7 +1,7 @@ from copy import deepcopy import pytest -from dataclass_factory import Factory +from adaptix import Retort from shvatka.core.models.dto.scn import TextHint, GPSHint, PhotoHint, ContactHint from shvatka.core.models.dto.scn.game import RawGameScenario @@ -22,41 +22,41 @@ from shvatka.tgbot.views.utils import render_hints -def test_deserialize_game(simple_scn: RawGameScenario, dcf: Factory): - game = parse_game(simple_scn, dcf) +def test_deserialize_game(simple_scn: RawGameScenario, retort: Retort): + game = parse_game(simple_scn, retort) assert "My new game" == game.name assert HintType.text.name == game.levels[0].time_hints[0].hint[0].type assert "загадка" == game.levels[0].time_hints[0].hint[0].text assert HintType.gps.name == game.levels[0].time_hints[2].hint[0].type -def test_deserialize_level(simple_scn: RawGameScenario, dcf: Factory): - level = load_level(simple_scn.scn["levels"][0], dcf) +def test_deserialize_level(simple_scn: RawGameScenario, retort: Retort): + level = load_level(simple_scn.scn["levels"][0], retort) assert "first" == level.id assert HintType.text.name == level.time_hints[0].hint[0].type assert "загадка" == level.time_hints[0].hint[0].text assert HintType.gps.name == level.time_hints[2].hint[0].type -def test_deserialize_invalid_level(simple_scn: RawGameScenario, dcf: Factory): +def test_deserialize_invalid_level(simple_scn: RawGameScenario, retort: Retort): level_source = simple_scn.scn["levels"][0] level = deepcopy(level_source) level["id"] = "привет" with pytest.raises(ScenarioNotCorrect): - load_level(level, dcf) + load_level(level, retort) level["keys"] = {"SHCamelCase"} with pytest.raises(ScenarioNotCorrect): - load_level(level, dcf) + load_level(level, retort) level = deepcopy(level_source) level["time_hints"] = level.pop("time-hints") with pytest.raises(ScenarioNotCorrect): - load_level(level, dcf) + load_level(level, retort) -def test_deserialize_all_types(all_types_scn: RawGameScenario, dcf: Factory): - game_scn = parse_game(all_types_scn, dcf) +def test_deserialize_all_types(all_types_scn: RawGameScenario, retort: Retort): + game_scn = parse_game(all_types_scn, retort) hints = game_scn.levels[0].time_hints assert 12 == len(hints) for i, type_ in enumerate( @@ -79,8 +79,8 @@ def test_deserialize_all_types(all_types_scn: RawGameScenario, dcf: Factory): assert hints[i].hint[0].type == type_.type # type: ignore[attr-defined] -def test_render_all_types(all_types_scn: RawGameScenario, dcf: Factory): - game_scn = parse_game(all_types_scn, dcf) +def test_render_all_types(all_types_scn: RawGameScenario, retort: Retort): + game_scn = parse_game(all_types_scn, retort) hints = [time_hint.hint[0] for time_hint in game_scn.levels[0].time_hints] assert 12 == len(hints) assert "📃📡🧭📷🎼🎬📎🌀🎤🤳🪪🏷" == render_hints(hints) From 1236768a9e64055ccea1aca094214d2743f167e8 Mon Sep 17 00:00:00 2001 From: bomzheg Date: Fri, 4 Oct 2024 23:38:37 +0300 Subject: [PATCH 10/34] tries to use correct way --- shvatka/common/factory.py | 4 +--- shvatka/core/models/dto/scn/level.py | 8 +++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/shvatka/common/factory.py b/shvatka/common/factory.py index dc6d9ca1..d0639240 100644 --- a/shvatka/common/factory.py +++ b/shvatka/common/factory.py @@ -48,9 +48,7 @@ def create_retort(self) -> Retort: name_mapping( name_style=adaptix.NameStyle.LOWER_KEBAB, ), - loader( - HintsList, lambda x: HintsList(internal_retort.load(x, list[TimeHint])), - ), + validator( pred=P[scn.LevelScenario].id, func=lambda x: validate_level_id(x) is not None, diff --git a/shvatka/core/models/dto/scn/level.py b/shvatka/core/models/dto/scn/level.py index a7838a99..72936b82 100644 --- a/shvatka/core/models/dto/scn/level.py +++ b/shvatka/core/models/dto/scn/level.py @@ -4,6 +4,8 @@ from datetime import timedelta from typing import overload +from mypy.server.objgraph import Iterable + from shvatka.core.utils import exceptions from .hint_part import AnyHint from .time_hint import TimeHint, EnumeratedTimeHint @@ -26,9 +28,9 @@ def __hash__(self) -> int: class HintsList(Sequence[TimeHint]): - def __init__(self, hints: list[TimeHint]): + def __init__(self, hints: Iterable[TimeHint]): self.verify(hints) - self.hints = hints + self.hints: list[TimeHint] = list(hints) @classmethod def parse(cls, hints: list[TimeHint]): @@ -44,7 +46,7 @@ def normalize(hints: list[TimeHint]) -> list[TimeHint]: return [TimeHint(k, v) for k, v in sorted(hint_map.items(), key=lambda x: x[0])] @staticmethod - def verify(hints: Sequence[TimeHint]) -> None: + def verify(hints: Iterable[TimeHint]) -> None: times: set[int] = set() for hint in hints: if hint.time in times: From 0531c408af2c4c92d7b9f6b90a4197a983117a61 Mon Sep 17 00:00:00 2001 From: bomzheg Date: Fri, 4 Oct 2024 23:41:54 +0300 Subject: [PATCH 11/34] use abcproxy --- shvatka/common/factory.py | 9 +++------ shvatka/core/models/dto/scn/level.py | 4 ++-- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/shvatka/common/factory.py b/shvatka/common/factory.py index d0639240..9587b09f 100644 --- a/shvatka/common/factory.py +++ b/shvatka/common/factory.py @@ -1,6 +1,7 @@ import adaptix import dataclass_factory from adaptix import Retort, validator, P, name_mapping, loader, Chain, as_is_loader, constructor, bound +from adaptix._internal.morphing.provider_template import ABCProxy from dataclass_factory import Schema, NameStyle from dishka import Provider, Scope, provide from pyrogram.errors.exceptions.all import exceptions @@ -38,17 +39,13 @@ def create_dataclass_factory(self) -> dataclass_factory.Factory: @provide def create_retort(self) -> Retort: - internal_retort = Retort( - recipe=[ - name_mapping(name_style=adaptix.NameStyle.LOWER_KEBAB), - ] - ) retort = Retort( recipe=[ name_mapping( name_style=adaptix.NameStyle.LOWER_KEBAB, ), - + loader(HintsList, lambda x: HintsList(x), Chain.LAST), + ABCProxy(HintsList, list[TimeHint]), # internal class, can be broken in next version adaptix validator( pred=P[scn.LevelScenario].id, func=lambda x: validate_level_id(x) is not None, diff --git a/shvatka/core/models/dto/scn/level.py b/shvatka/core/models/dto/scn/level.py index 72936b82..e6ce2da3 100644 --- a/shvatka/core/models/dto/scn/level.py +++ b/shvatka/core/models/dto/scn/level.py @@ -28,9 +28,9 @@ def __hash__(self) -> int: class HintsList(Sequence[TimeHint]): - def __init__(self, hints: Iterable[TimeHint]): + def __init__(self, hints: list[TimeHint]): self.verify(hints) - self.hints: list[TimeHint] = list(hints) + self.hints = hints @classmethod def parse(cls, hints: list[TimeHint]): From be72d37927301a9868af9ff41bcb7b9bf6ff6f37 Mon Sep 17 00:00:00 2001 From: bomzheg Date: Sat, 5 Oct 2024 00:41:07 +0300 Subject: [PATCH 12/34] fixed tests --- shvatka/api/models/responses.py | 7 +++- shvatka/common/factory.py | 38 +++++++++++++++---- shvatka/core/models/dto/scn/level.py | 10 +++++ shvatka/core/services/game.py | 6 +-- shvatka/core/services/level.py | 6 +-- .../crawler/game_scn/loader/load_scns.py | 10 ++--- shvatka/infrastructure/db/models/level.py | 21 +++++----- shvatka/tgbot/dialogs/game_manage/handlers.py | 6 +-- shvatka/tgbot/dialogs/game_scn/handlers.py | 6 +-- shvatka/tgbot/dialogs/level_scn/getters.py | 13 ++++--- shvatka/tgbot/dialogs/level_scn/handlers.py | 30 +++++++-------- shvatka/tgbot/dialogs/time_hint/getters.py | 5 ++- shvatka/tgbot/dialogs/time_hint/handlers.py | 34 ++++++++--------- shvatka/tgbot/middlewares/init_middleware.py | 2 + shvatka/tgbot/utils/data.py | 2 + tests/fixtures/game_fixtures.py | 6 +-- tests/integration/api_full/test_game.py | 13 +++++-- tests/integration/test_game.py | 18 ++++----- tests/integration/test_level.py | 6 +-- 19 files changed, 146 insertions(+), 93 deletions(-) diff --git a/shvatka/api/models/responses.py b/shvatka/api/models/responses.py index 56af0342..6fe97975 100644 --- a/shvatka/api/models/responses.py +++ b/shvatka/api/models/responses.py @@ -3,12 +3,15 @@ from datetime import datetime from typing import Sequence, Generic +from adaptix import Retort, dumper + from shvatka.core.games.dto import CurrentHints from shvatka.core.models import dto, enums from shvatka.core.models.dto import scn from shvatka.core.models.enums import GameStatus T = typing.TypeVar("T") +retort = Retort(recipe=[dumper(scn.HintsList, lambda x: x.hints)]) @dataclass @@ -76,7 +79,7 @@ class Level: db_id: int name_id: str author: Player - scenario: scn.LevelScenario + scenario: dict[str, typing.Any] game_id: int | None = None number_in_game: int | None = None @@ -88,7 +91,7 @@ def from_core(cls, core: dto.Level | None = None): db_id=core.db_id, name_id=core.name_id, author=Player.from_core(core.author), - scenario=core.scenario, + scenario=retort.dump(core.scenario), game_id=core.game_id, number_in_game=core.number_in_game, ) diff --git a/shvatka/common/factory.py b/shvatka/common/factory.py index 9587b09f..106eb741 100644 --- a/shvatka/common/factory.py +++ b/shvatka/common/factory.py @@ -1,20 +1,29 @@ +import typing + import adaptix import dataclass_factory -from adaptix import Retort, validator, P, name_mapping, loader, Chain, as_is_loader, constructor, bound +from adaptix import ( + Retort, + validator, + P, + name_mapping, + loader, + Chain, +) +from adaptix.load_error import LoadError from adaptix._internal.morphing.provider_template import ABCProxy from dataclass_factory import Schema, NameStyle from dishka import Provider, Scope, provide -from pyrogram.errors.exceptions.all import exceptions from telegraph.aio import Telegraph from shvatka.common.url_factory import UrlFactory from shvatka.core.models.dto import scn from shvatka.core.models.dto.scn import HintsList, TimeHint from shvatka.core.models.schems import schemas +from shvatka.core.utils import exceptions from shvatka.core.utils.input_validation import validate_level_id, is_multiple_keys_normal from shvatka.core.views.texts import INVALID_KEY_ERROR from shvatka.tgbot.config.models.bot import BotConfig -from shvatka.tgbot.views.hint_factory.hint_parser import HintParser class TelegraphProvider(Provider): @@ -26,6 +35,12 @@ def create_telegraph(self, bot_config: BotConfig) -> Telegraph: return telegraph +REQUIRED_GAME_RECIPES = [ + loader(HintsList, lambda x: HintsList(x), Chain.LAST), + ABCProxy(HintsList, list[TimeHint]), # internal class, can be broken in next version adaptix +] + + class DCFProvider(Provider): scope = Scope.APP @@ -44,17 +59,26 @@ def create_retort(self) -> Retort: name_mapping( name_style=adaptix.NameStyle.LOWER_KEBAB, ), - loader(HintsList, lambda x: HintsList(x), Chain.LAST), - ABCProxy(HintsList, list[TimeHint]), # internal class, can be broken in next version adaptix + *REQUIRED_GAME_RECIPES, validator( pred=P[scn.LevelScenario].id, func=lambda x: validate_level_id(x) is not None, - error=lambda x: exceptions.ScenarioNotCorrect(name_id=x, text=f"name_id ({x}) not correct") + error=lambda x: typing.cast( + LoadError, + exceptions.ScenarioNotCorrect( + name_id=x, text=f"name_id ({x}) not correct" + ), + ), ), validator( pred=P[scn.LevelScenario].keys, func=is_multiple_keys_normal, - error=lambda x: exceptions.ScenarioNotCorrect(notify_user=INVALID_KEY_ERROR, text="invalid keys") + error=lambda x: typing.cast( + LoadError, + exceptions.ScenarioNotCorrect( + notify_user=INVALID_KEY_ERROR, text="invalid keys" + ), + ), ), ] ) diff --git a/shvatka/core/models/dto/scn/level.py b/shvatka/core/models/dto/scn/level.py index e6ce2da3..30e9f4a4 100644 --- a/shvatka/core/models/dto/scn/level.py +++ b/shvatka/core/models/dto/scn/level.py @@ -104,6 +104,16 @@ def __getitem__(self, index): def __len__(self): return len(self.hints) + def __eq__(self, other): + if isinstance(other, HintsList): + return self.hints == other.hints + if isinstance(other, list): + return self.hints == other + return NotImplemented + + def __repr__(self): + return repr(self.hints) + @dataclass class LevelScenario: diff --git a/shvatka/core/services/game.py b/shvatka/core/services/game.py index 03542209..65d24bef 100644 --- a/shvatka/core/services/game.py +++ b/shvatka/core/services/game.py @@ -1,6 +1,6 @@ from datetime import datetime -from dataclass_factory import Factory +from adaptix import Retort from shvatka.core.interfaces.clients.file_storage import FileGateway from shvatka.core.interfaces.dal.complex import GameCompleter, GamePackager @@ -37,7 +37,7 @@ async def upsert_game( raw_scn: scn.RawGameScenario, author: dto.Player, dao: GameUpserter, - dcf: Factory, + dcf: Retort, file_gateway: FileGateway, ) -> dto.FullGame: check_allow_be_author(author) @@ -121,7 +121,7 @@ async def get_game_package( id_: int, author: dto.Player, dao: GamePackager, - dcf: Factory, + dcf: Retort, file_gateway: FileGateway, ) -> scn.RawGameScenario: game = await dao.get_full(id_=id_) diff --git a/shvatka/core/services/level.py b/shvatka/core/services/level.py index 71b75cd7..edaa11ff 100644 --- a/shvatka/core/services/level.py +++ b/shvatka/core/services/level.py @@ -1,4 +1,4 @@ -from dataclass_factory import Factory +from adaptix import Retort from shvatka.core.interfaces.dal.level import ( LevelUpserter, @@ -15,10 +15,10 @@ async def upsert_raw_level( - level_data: dict, author: dto.Player, dcf: Factory, dao: LevelUpserter + level_data: dict, author: dto.Player, retort: Retort, dao: LevelUpserter ) -> dto.Level: check_allow_be_author(author) - scenario = load_level(level_data, dcf) + scenario = load_level(level_data, retort) return await upsert_level(author, scenario, dao) diff --git a/shvatka/infrastructure/crawler/game_scn/loader/load_scns.py b/shvatka/infrastructure/crawler/game_scn/loader/load_scns.py index 2b5b7cc1..8fe95700 100644 --- a/shvatka/infrastructure/crawler/game_scn/loader/load_scns.py +++ b/shvatka/infrastructure/crawler/game_scn/loader/load_scns.py @@ -6,7 +6,7 @@ from typing import BinaryIO, Any, Callable, Coroutine from zipfile import Path as ZipPath -from dataclass_factory import Factory +from adaptix import Retort from dishka import make_async_container from shvatka.common.config.parser.logging_config import setup_logging @@ -50,7 +50,7 @@ async def main(): bot_player=bot_player, dao=dao, file_gateway=file_gateway, - dcf=await dishka.get(Factory), + dcf=await dishka.get(Retort), path=config.file_storage_config.path.parent / "scn", ) finally: @@ -61,7 +61,7 @@ async def load_scns( bot_player: dto.Player, dao: HolderDao, file_gateway: FileGateway, - dcf: Factory, + dcf: Retort, path: Path, ): files = sorted(path.glob("*.zip"), key=lambda p: int(p.stem)) @@ -192,7 +192,7 @@ async def transfer_ownership(game: dto.FullGame, bot_player: dto.Player, dao: Ho await dao.game.transfer(game, bot_player) -def load_results(game_zip_scn: BinaryIO, dcf: Factory) -> GameStat: +def load_results(game_zip_scn: BinaryIO, dcf: Retort) -> GameStat: zip_path = ZipPath(game_zip_scn) for unpacked_file in zip_path.iterdir(): if not unpacked_file.is_file(): @@ -209,7 +209,7 @@ async def load_scn( player: dto.Player, dao: HolderDao, file_gateway: FileGateway, - dcf: Factory, + dcf: Retort, zip_scn: BinaryIO, ) -> dto.FullGame | None: try: diff --git a/shvatka/infrastructure/db/models/level.py b/shvatka/infrastructure/db/models/level.py index ff781794..7d479d3c 100644 --- a/shvatka/infrastructure/db/models/level.py +++ b/shvatka/infrastructure/db/models/level.py @@ -1,13 +1,14 @@ import typing from typing import Any -from dataclass_factory import Factory +from adaptix import Retort, dumper from sqlalchemy import Integer, Text, ForeignKey, JSON, TypeDecorator, UniqueConstraint from sqlalchemy.engine import Dialect from sqlalchemy.orm import relationship, mapped_column, Mapped +from shvatka.common.factory import REQUIRED_GAME_RECIPES from shvatka.core.models import dto -from shvatka.core.models.dto.scn.level import LevelScenario +from shvatka.core.models.dto import scn from shvatka.infrastructure.db.models import Base if typing.TYPE_CHECKING: @@ -18,20 +19,22 @@ class ScenarioField(TypeDecorator): impl = JSON cache_ok = True - dcf = Factory() + retort = Retort( + recipe=[*REQUIRED_GAME_RECIPES, dumper(set, lambda x: list(x))], + ) def coerce_compared_value(self, op: Any, value: Any): - if isinstance(value, LevelScenario): + if isinstance(value, scn.LevelScenario): return self return self.impl().coerce_compared_value(op=op, value=value) - def process_bind_param(self, value: LevelScenario | None, dialect: Dialect): - return self.dcf.dump(value, LevelScenario) + def process_bind_param(self, value: scn.LevelScenario | None, dialect: Dialect): + return self.retort.dump(value, scn.LevelScenario) - def process_result_value(self, value: Any, dialect: Dialect) -> LevelScenario | None: + def process_result_value(self, value: Any, dialect: Dialect) -> scn.LevelScenario | None: if value is None: return None - return self.dcf.load(value, LevelScenario) + return self.retort.load(value, scn.LevelScenario) class Level(Base): @@ -52,7 +55,7 @@ class Level(Base): back_populates="my_levels", ) number_in_game = mapped_column(Integer, nullable=True) - scenario: Mapped[LevelScenario] = mapped_column(ScenarioField) + scenario: Mapped[scn.LevelScenario] = mapped_column(ScenarioField) __table_args__ = (UniqueConstraint("author_id", "name_id"),) diff --git a/shvatka/tgbot/dialogs/game_manage/handlers.py b/shvatka/tgbot/dialogs/game_manage/handlers.py index a724002b..a97a38f3 100644 --- a/shvatka/tgbot/dialogs/game_manage/handlers.py +++ b/shvatka/tgbot/dialogs/game_manage/handlers.py @@ -3,10 +3,10 @@ from io import BytesIO from typing import Any +from adaptix import Retort from aiogram.types import CallbackQuery, Message, BufferedInputFile from aiogram_dialog import DialogManager from aiogram_dialog.widgets.kbd import Button -from dataclass_factory import Factory from shvatka.core.interfaces.clients.file_storage import FileGateway from shvatka.core.interfaces.scheduler import Scheduler @@ -80,9 +80,9 @@ async def show_my_zip_scn(c: CallbackQuery, widget: Button, manager: DialogManag async def common_show_zip(c: CallbackQuery, game_id: int, manager: DialogManager): player: dto.Player = manager.middleware_data["player"] dao: HolderDao = manager.middleware_data["dao"] - dcf: Factory = manager.middleware_data["dcf"] + retort: Retort = manager.middleware_data["retort"] file_gateway: FileGateway = manager.middleware_data["file_gateway"] - game_ = await game.get_game_package(game_id, player, dao.game_packager, dcf, file_gateway) + game_ = await game.get_game_package(game_id, player, dao.game_packager, retort, file_gateway) zip_ = pack_scn(game_) assert isinstance(c.message, Message) await c.message.answer_document(BufferedInputFile(file=zip_.read(), filename="scenario.zip")) diff --git a/shvatka/tgbot/dialogs/game_scn/handlers.py b/shvatka/tgbot/dialogs/game_scn/handlers.py index 1a529e2b..4ffaf4a4 100644 --- a/shvatka/tgbot/dialogs/game_scn/handlers.py +++ b/shvatka/tgbot/dialogs/game_scn/handlers.py @@ -3,12 +3,12 @@ from typing import Any from zipfile import Path as ZipPath +from adaptix import Retort from aiogram import Bot from aiogram.types import Message, CallbackQuery from aiogram.utils.text_decorations import html_decoration as hd from aiogram_dialog import DialogManager from aiogram_dialog.widgets.kbd import Button, ManagedMultiselect -from dataclass_factory import Factory from shvatka.core.interfaces.clients.file_storage import FileGateway from shvatka.core.models import dto @@ -57,12 +57,12 @@ async def process_zip_scn(m: Message, dialog_: Any, manager: DialogManager): dao: HolderDao = manager.middleware_data["dao"] bot: Bot = manager.middleware_data["bot"] file_gateway: FileGateway = manager.middleware_data["file_gateway"] - dcf: Factory = manager.middleware_data["dcf"] + retort: Retort = manager.middleware_data["retort"] assert m.document document = await bot.download(m.document.file_id) try: with unpack_scn(ZipPath(document)).open() as scenario: # type: scn.RawGameScenario - game = await upsert_game(scenario, player, dao.game_upserter, dcf, file_gateway) + game = await upsert_game(scenario, player, dao.game_upserter, retort, file_gateway) except ScenarioNotCorrect as e: await m.reply(f"Ошибка {e}\n попробуйте исправить файл") logger.error("game scenario from player %s has problems", player.id, exc_info=e) diff --git a/shvatka/tgbot/dialogs/level_scn/getters.py b/shvatka/tgbot/dialogs/level_scn/getters.py index 917a2699..a0ab860a 100644 --- a/shvatka/tgbot/dialogs/level_scn/getters.py +++ b/shvatka/tgbot/dialogs/level_scn/getters.py @@ -1,3 +1,4 @@ +from adaptix import Retort from aiogram_dialog import DialogManager from dataclass_factory import Factory @@ -19,19 +20,19 @@ async def get_keys(dialog_manager: DialogManager, **_): async def get_bonus_keys(dialog_manager: DialogManager, **_): - dcf: Factory = dialog_manager.middleware_data["dcf"] + retort: Retort = dialog_manager.middleware_data["retort"] keys_raw = dialog_manager.dialog_data.get( "bonus_keys", dialog_manager.start_data.get("bonus_keys", []) ) return { - "bonus_keys": dcf.load(keys_raw, list[scn.BonusKey]), + "bonus_keys": retort.load(keys_raw, list[scn.BonusKey]), } async def get_level_data(dialog_manager: DialogManager, **_): dialog_data = dialog_manager.dialog_data - dcf: Factory = dialog_manager.middleware_data["dcf"] - hints = dcf.load(dialog_data.get("time_hints", []), list[TimeHint]) + retort: Retort = dialog_manager.middleware_data["retort"] + hints = retort.load(dialog_data.get("time_hints", []), list[TimeHint]) return { "level_id": dialog_data["level_id"], "keys": dialog_data.get("keys", []), @@ -42,8 +43,8 @@ async def get_level_data(dialog_manager: DialogManager, **_): async def get_time_hints(dialog_manager: DialogManager, **_): dialog_data = dialog_manager.dialog_data - dcf: Factory = dialog_manager.middleware_data["dcf"] - hints = dcf.load(dialog_data.get("time_hints", []), list[TimeHint]) + retort: Retort = dialog_manager.middleware_data["retort"] + hints = retort.load(dialog_data.get("time_hints", []), list[TimeHint]) return { "level_id": dialog_manager.start_data["level_id"], "time_hints": hints, diff --git a/shvatka/tgbot/dialogs/level_scn/handlers.py b/shvatka/tgbot/dialogs/level_scn/handlers.py index d59e3bd9..3cbe6b87 100644 --- a/shvatka/tgbot/dialogs/level_scn/handlers.py +++ b/shvatka/tgbot/dialogs/level_scn/handlers.py @@ -1,9 +1,9 @@ from typing import Any +from adaptix import Retort from aiogram.types import CallbackQuery, Message from aiogram_dialog import Data, DialogManager from aiogram_dialog.widgets.kbd import Button -from dataclass_factory import Factory from shvatka.core.models import dto from shvatka.core.models.dto import scn @@ -96,8 +96,8 @@ async def not_correct_bonus_keys( async def on_correct_bonus_keys( m: Message, dialog_: Any, manager: DialogManager, keys: list[scn.BonusKey] ): - dcf: Factory = manager.middleware_data["dcf"] - await manager.done({"bonus_keys": dcf.dump(keys)}) + retort: Retort = manager.middleware_data["retort"] + await manager.done({"bonus_keys": retort.dump(keys)}) async def process_time_hint_result(start_data: Data, result: Any, manager: DialogManager): @@ -124,13 +124,13 @@ async def process_level_result(start_data: Data, result: Any, manager: DialogMan async def on_start_level_edit(start_data: dict[str, Any], manager: DialogManager): dao: HolderDao = manager.middleware_data["dao"] - dcf: Factory = manager.middleware_data["dcf"] + retort: Retort = manager.middleware_data["retort"] author: dto.Player = manager.middleware_data["player"] level = await get_by_id(start_data["level_id"], author, dao.level) manager.dialog_data["level_id"] = level.name_id manager.dialog_data["keys"] = list(level.get_keys()) - manager.dialog_data["time_hints"] = dcf.dump(level.scenario.time_hints) - manager.dialog_data["bonus_keys"] = dcf.dump(level.get_bonus_keys()) + manager.dialog_data["time_hints"] = retort.dump(level.scenario.time_hints) + manager.dialog_data["bonus_keys"] = retort.dump(level.get_bonus_keys()) async def on_start_hints_edit(start_data: dict[str, Any], manager: DialogManager): @@ -140,17 +140,17 @@ async def on_start_hints_edit(start_data: dict[str, Any], manager: DialogManager async def start_edit_time_hint( c: CallbackQuery, widget: Any, manager: DialogManager, hint_time: str ): - dcf: Factory = manager.middleware_data["dcf"] - hints = dcf.load(manager.dialog_data.get("time_hints", []), list[scn.TimeHint]) + retort: Retort = manager.middleware_data["retort"] + hints = retort.load(manager.dialog_data.get("time_hints", []), list[scn.TimeHint]) await manager.start( state=states.TimeHintEditSG.details, - data={"time_hint": dcf.dump(next(filter(lambda x: x.time == int(hint_time), hints)))}, + data={"time_hint": retort.dump(next(filter(lambda x: x.time == int(hint_time), hints)))}, ) async def start_add_time_hint(c: CallbackQuery, button: Button, manager: DialogManager): - dcf: Factory = manager.middleware_data["dcf"] - hints = dcf.load(manager.dialog_data.get("time_hints", []), list[scn.TimeHint]) + retort: Retort = manager.middleware_data["retort"] + hints = retort.load(manager.dialog_data.get("time_hints", []), list[scn.TimeHint]) previous_time = hints[-1].time if hints else -1 await manager.start(state=states.TimeHintSG.time, data={"previous_time": previous_time}) @@ -194,16 +194,16 @@ async def clear_hints(c: CallbackQuery, button: Button, manager: DialogManager): async def save_level(c: CallbackQuery, button: Button, manager: DialogManager): - dcf: Factory = manager.middleware_data["dcf"] + retort: Retort = manager.middleware_data["retort"] author: dto.Player = manager.middleware_data["player"] dao: HolderDao = manager.middleware_data["dao"] data = manager.dialog_data id_ = data["level_id"] keys = set(map(normalize_key, data["keys"])) - time_hints = dcf.load(data["time_hints"], list[scn.TimeHint]) - bonus_keys = dcf.load(data.get("bonus_keys", []), set[scn.BonusKey]) + time_hints = retort.load(data["time_hints"], list[scn.TimeHint]) + bonus_keys = retort.load(data.get("bonus_keys", []), set[scn.BonusKey]) level_scn = scn.LevelScenario(id=id_, keys=keys, time_hints=time_hints, bonus_keys=bonus_keys) level = await upsert_level(author=author, scenario=level_scn, dao=dao.level) - await manager.done(result={"level": dcf.dump(level)}) + await manager.done(result={"level": retort.dump(level)}) await c.answer(text="Уровень успешно сохранён") diff --git a/shvatka/tgbot/dialogs/time_hint/getters.py b/shvatka/tgbot/dialogs/time_hint/getters.py index 1d173396..d8e688dc 100644 --- a/shvatka/tgbot/dialogs/time_hint/getters.py +++ b/shvatka/tgbot/dialogs/time_hint/getters.py @@ -1,3 +1,4 @@ +from adaptix import Retort from aiogram_dialog import DialogManager from dataclass_factory import Factory @@ -17,9 +18,9 @@ async def get_available_times(dialog_manager: DialogManager, **_): async def get_hints(dialog_manager: DialogManager, **_): dialog_data = dialog_manager.dialog_data - dcf: Factory = dialog_manager.middleware_data["dcf"] + retort: Retort = dialog_manager.middleware_data["retort"] - hints = dcf.load(dialog_data["hints"], list[AnyHint]) + hints = retort.load(dialog_data["hints"], list[AnyHint]) time_ = dialog_data["time"] return { "hints": hints, diff --git a/shvatka/tgbot/dialogs/time_hint/handlers.py b/shvatka/tgbot/dialogs/time_hint/handlers.py index 4161cdbf..a2c4d685 100644 --- a/shvatka/tgbot/dialogs/time_hint/handlers.py +++ b/shvatka/tgbot/dialogs/time_hint/handlers.py @@ -1,10 +1,10 @@ from typing import Any +from adaptix import Retort from aiogram import types from aiogram.types import CallbackQuery, Message from aiogram_dialog import DialogManager from aiogram_dialog.widgets.kbd import Button -from dataclass_factory import Factory from dishka import AsyncContainer from dishka.integrations.aiogram import CONTAINER_NAME @@ -27,8 +27,8 @@ async def process_edit_time_message(m: Message, dialog_: Any, manager: DialogMan except ValueError: await m.answer("Некорректный формат времени. Пожалуйста введите время в формате ЧЧ:ММ") return - dcf: Factory = manager.middleware_data["dcf"] - hint = dcf.load(manager.start_data["time_hint"], scn.TimeHint) + retort: Retort = manager.middleware_data["retort"] + hint = retort.load(manager.start_data["time_hint"], scn.TimeHint) if not hint.can_update_time(): await m.reply( "Увы, отредактировать время данной подсказки не получится. " @@ -54,8 +54,8 @@ async def process_time_message(m: Message, dialog_: Any, manager: DialogManager) async def edit_single_hint(c: CallbackQuery, widget: Any, manager: DialogManager, hint_index: str): dishka: AsyncContainer = manager.middleware_data[CONTAINER_NAME] - dcf = await dishka.get(Factory) - hint = dcf.load(manager.start_data.get("time_hint"), scn.TimeHint) + retort = await dishka.get(Retort) + hint = retort.load(manager.start_data.get("time_hint"), scn.TimeHint) hint_sender = await dishka.get(HintSender) # TODO now it only show. but we want to show, to edit and to delete chat: types.Chat = manager.middleware_data["event_from_chat"] @@ -64,16 +64,16 @@ async def edit_single_hint(c: CallbackQuery, widget: Any, manager: DialogManager async def save_edited_time_hint(c: CallbackQuery, widget: Any, manager: DialogManager): dishka: AsyncContainer = manager.middleware_data[CONTAINER_NAME] - dcf = await dishka.get(Factory) - time_hint = dcf.load(manager.start_data["time_hint"], scn.TimeHint) + retort = await dishka.get(Retort) + time_hint = retort.load(manager.start_data["time_hint"], scn.TimeHint) try: time_hint.update_time(manager.dialog_data["time"]) - time_hint.update_hint(dcf.load(manager.dialog_data["hints"], list[AnyHint])) + time_hint.update_hint(retort.load(manager.dialog_data["hints"], list[AnyHint])) except exceptions.LevelError as e: assert isinstance(c.message, Message) await c.message.reply(e.text) return - await manager.done({"edited_time_hint": dcf.dump(time_hint)}) + await manager.done({"edited_time_hint": retort.dump(time_hint)}) async def set_time(time_minutes: int, manager: DialogManager): @@ -87,18 +87,18 @@ async def set_time(time_minutes: int, manager: DialogManager): async def process_hint(m: Message, dialog_: Any, manager: DialogManager) -> None: - dcf: Factory = manager.middleware_data["dcf"] + retort: Retort = manager.middleware_data["retort"] parser: HintParser = manager.middleware_data["hint_parser"] hint = await parser.parse(m, manager.middleware_data["player"]) - manager.dialog_data["hints"].append(dcf.dump(hint)) + manager.dialog_data["hints"].append(retort.dump(hint)) async def on_finish(c: CallbackQuery, button: Button, manager: DialogManager): - dcf: Factory = manager.middleware_data["dcf"] - hints = dcf.load(manager.dialog_data["hints"], list[AnyHint]) + retort: Retort = manager.middleware_data["retort"] + hints = retort.load(manager.dialog_data["hints"], list[AnyHint]) time_ = manager.dialog_data["time"] time_hint = TimeHint(time=time_, hint=hints) - await manager.done({"time_hint": dcf.dump(time_hint)}) + await manager.done({"time_hint": retort.dump(time_hint)}) async def hint_on_start(start_data: dict, manager: DialogManager): @@ -110,7 +110,7 @@ async def hint_on_start(start_data: dict, manager: DialogManager): async def hint_edit_on_start(start_data: dict, manager: DialogManager): - dcf: Factory = manager.middleware_data["dcf"] - hint = dcf.load(manager.start_data["time_hint"], scn.TimeHint) - manager.dialog_data["hints"] = dcf.dump(hint.hint, list[AnyHint]) + retort: Retort = manager.middleware_data["retort"] + hint = retort.load(manager.start_data["time_hint"], scn.TimeHint) + manager.dialog_data["hints"] = retort.dump(hint.hint, list[AnyHint]) manager.dialog_data["time"] = hint.time diff --git a/shvatka/tgbot/middlewares/init_middleware.py b/shvatka/tgbot/middlewares/init_middleware.py index ad9fa95a..ffdf557a 100644 --- a/shvatka/tgbot/middlewares/init_middleware.py +++ b/shvatka/tgbot/middlewares/init_middleware.py @@ -1,5 +1,6 @@ from typing import Callable, Any, Awaitable +from adaptix import Retort from aiogram import BaseMiddleware from aiogram.types import TelegramObject from aiogram_dialog.api.protocols import BgManagerFactory @@ -40,6 +41,7 @@ async def __call__( # type: ignore[override] data["main_config"] = await dishka.get(TgBotConfig) data["user_getter"] = await dishka.get(UserGetter) data["dcf"] = await dishka.get(Factory) + data["retort"] = await dishka.get(Retort) data["scheduler"] = await dishka.get(Scheduler) # type: ignore[type-abstract] data["locker"] = await dishka.get(KeyCheckerFactory) # type: ignore[type-abstract] data["file_storage"] = file_storage diff --git a/shvatka/tgbot/utils/data.py b/shvatka/tgbot/utils/data.py index bc9965ae..31e2d11d 100644 --- a/shvatka/tgbot/utils/data.py +++ b/shvatka/tgbot/utils/data.py @@ -1,5 +1,6 @@ from typing import TypedDict, Any +from adaptix import Retort from aiogram import types, Bot, Router from aiogram.dispatcher.event.handler import HandlerObject from aiogram.fsm.context import FSMContext @@ -52,6 +53,7 @@ class MiddlewareData(DialogMiddlewareData, total=False): dishka_container: AsyncContainer user_getter: UserGetter dcf: Factory + retort: Retort dao: HolderDao scheduler: Scheduler locker: KeyCheckerFactory diff --git a/tests/fixtures/game_fixtures.py b/tests/fixtures/game_fixtures.py index f9df0e7d..ca82e697 100644 --- a/tests/fixtures/game_fixtures.py +++ b/tests/fixtures/game_fixtures.py @@ -2,7 +2,7 @@ from datetime import datetime import pytest_asyncio -from dataclass_factory import Factory +from adaptix import Retort from shvatka.core.interfaces.clients.file_storage import FileGateway from shvatka.core.models import dto, enums @@ -20,14 +20,14 @@ async def game( complex_scn: RawGameScenario, author: dto.Player, dao: HolderDao, - dcf: Factory, + retort: Retort, file_gateway: FileGateway, ) -> dto.FullGame: return await upsert_game( complex_scn, author, dao.game_upserter, - dcf, + retort, file_gateway, ) diff --git a/tests/integration/api_full/test_game.py b/tests/integration/api_full/test_game.py index 54f8ee76..18958aa3 100644 --- a/tests/integration/api_full/test_game.py +++ b/tests/integration/api_full/test_game.py @@ -1,9 +1,12 @@ import pytest +from adaptix import Retort from dataclass_factory import Factory from httpx import AsyncClient from shvatka.api.models import responses +from shvatka.common.factory import REQUIRED_GAME_RECIPES from shvatka.core.models import dto +from shvatka.core.models.dto import scn from shvatka.core.models.enums import GameStatus from shvatka.core.services.player import upsert_player from shvatka.infrastructure.db.dao.holder import HolderDao @@ -61,12 +64,16 @@ async def test_game_card( assert resp.is_success resp.read() - dcf = Factory() - actual = dcf.load(resp.json(), responses.FullGame) + retort = Retort( + recipe=[ + *REQUIRED_GAME_RECIPES, + ] + ) + actual = retort.load(resp.json(), responses.FullGame) assert actual.id == finished_game.id assert actual.status == GameStatus.complete assert len(actual.levels) == len(finished_game.levels) - assert [lvl.scenario for lvl in actual.levels] == [ + assert [retort.load(lvl.scenario, scn.LevelScenario) for lvl in actual.levels] == [ lvl.scenario for lvl in finished_game.levels ] diff --git a/tests/integration/test_game.py b/tests/integration/test_game.py index 66d2831b..9a349d11 100644 --- a/tests/integration/test_game.py +++ b/tests/integration/test_game.py @@ -1,7 +1,7 @@ from copy import deepcopy import pytest -from dataclass_factory import Factory +from adaptix import Retort from shvatka.core.interfaces.clients.file_storage import FileGateway from shvatka.core.models import dto @@ -25,10 +25,10 @@ async def test_game_simple( author: dto.Player, three_lvl_scn: RawGameScenario, dao: HolderDao, - dcf: Factory, + retort: Retort, file_gateway: FileGateway, ): - game = await upsert_game(three_lvl_scn, author, dao.game_upserter, dcf, file_gateway) + game = await upsert_game(three_lvl_scn, author, dao.game_upserter, retort, file_gateway) assert await dao.game.count() == 1 assert await dao.level.count() == 3 @@ -48,7 +48,7 @@ async def test_game_simple( another_scn["levels"].append(another_scn["levels"].pop(0)) game = await upsert_game( - RawGameScenario(scn=another_scn, files={}), author, dao.game_upserter, dcf, file_gateway + RawGameScenario(scn=another_scn, files={}), author, dao.game_upserter, retort, file_gateway ) assert await dao.game.count() == 1 @@ -68,7 +68,7 @@ async def test_game_simple( another_scn["levels"].pop() game = await upsert_game( - RawGameScenario(scn=another_scn, files={}), author, dao.game_upserter, dcf, file_gateway + RawGameScenario(scn=another_scn, files={}), author, dao.game_upserter, retort, file_gateway ) assert await dao.game.count() == 1 @@ -98,10 +98,10 @@ async def test_game_get_full( author: dto.Player, simple_scn: RawGameScenario, dao: HolderDao, - dcf: Factory, + retort: Retort, file_gateway: FileGateway, ): - game_expected = await upsert_game(simple_scn, author, dao.game_upserter, dcf, file_gateway) + game_expected = await upsert_game(simple_scn, author, dao.game_upserter, retort, file_gateway) game_actual = await dao.game.get_full(game_expected.id) assert game_expected == game_actual @@ -111,10 +111,10 @@ async def test_game_get_preview( author: dto.Player, simple_scn: RawGameScenario, dao: HolderDao, - dcf: Factory, + retort: Retort, file_gateway: FileGateway, ): - game_expected = await upsert_game(simple_scn, author, dao.game_upserter, dcf, file_gateway) + game_expected = await upsert_game(simple_scn, author, dao.game_upserter, retort, file_gateway) game_actual = await dao.game.get_preview(game_expected.id) assert len(game_expected.levels) == game_actual.levels_count assert game_expected.id == game_actual.id diff --git a/tests/integration/test_level.py b/tests/integration/test_level.py index de41a9fc..be4fc54f 100644 --- a/tests/integration/test_level.py +++ b/tests/integration/test_level.py @@ -1,5 +1,5 @@ import pytest -from dataclass_factory import Factory +from adaptix import Retort from shvatka.core.models.dto.scn.game import RawGameScenario from shvatka.core.models.dto.scn.level import LevelScenario @@ -11,12 +11,12 @@ @pytest.mark.asyncio -async def test_simple_level(simple_scn: RawGameScenario, dao: HolderDao, dcf: Factory): +async def test_simple_level(simple_scn: RawGameScenario, dao: HolderDao, retort: Retort): author = await upsert_player(await upsert_user(create_dto_harry(), dao.user), dao.player) await dao.player.promote(author, author) await dao.commit() author.can_be_author = True - lvl = await upsert_raw_level(simple_scn.scn["levels"][0], author, dcf, dao.level) + lvl = await upsert_raw_level(simple_scn.scn["levels"][0], author, retort, dao.level) assert lvl.db_id is not None assert await dao.level.count() == 1 From 8d8705ccb175ea079ae1982e66959377655b8e5a Mon Sep 17 00:00:00 2001 From: bomzheg Date: Sat, 5 Oct 2024 18:51:40 +0300 Subject: [PATCH 13/34] fixed tests --- shvatka/common/factory.py | 3 ++- shvatka/tgbot/dialogs/level_scn/handlers.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/shvatka/common/factory.py b/shvatka/common/factory.py index 106eb741..27f85ea3 100644 --- a/shvatka/common/factory.py +++ b/shvatka/common/factory.py @@ -8,7 +8,7 @@ P, name_mapping, loader, - Chain, + Chain, dumper, ) from adaptix.load_error import LoadError from adaptix._internal.morphing.provider_template import ABCProxy @@ -38,6 +38,7 @@ def create_telegraph(self, bot_config: BotConfig) -> Telegraph: REQUIRED_GAME_RECIPES = [ loader(HintsList, lambda x: HintsList(x), Chain.LAST), ABCProxy(HintsList, list[TimeHint]), # internal class, can be broken in next version adaptix + dumper(set, lambda x: tuple(x)) ] diff --git a/shvatka/tgbot/dialogs/level_scn/handlers.py b/shvatka/tgbot/dialogs/level_scn/handlers.py index 3cbe6b87..39d211f0 100644 --- a/shvatka/tgbot/dialogs/level_scn/handlers.py +++ b/shvatka/tgbot/dialogs/level_scn/handlers.py @@ -130,7 +130,7 @@ async def on_start_level_edit(start_data: dict[str, Any], manager: DialogManager manager.dialog_data["level_id"] = level.name_id manager.dialog_data["keys"] = list(level.get_keys()) manager.dialog_data["time_hints"] = retort.dump(level.scenario.time_hints) - manager.dialog_data["bonus_keys"] = retort.dump(level.get_bonus_keys()) + manager.dialog_data["bonus_keys"] = retort.dump(level.get_bonus_keys(), set[scn.BonusKey]) async def on_start_hints_edit(start_data: dict[str, Any], manager: DialogManager): From dcd0df91fd96a484ef817832796da47e232bee0f Mon Sep 17 00:00:00 2001 From: bomzheg Date: Sat, 5 Oct 2024 18:58:00 +0300 Subject: [PATCH 14/34] fixed for linter --- shvatka/common/factory.py | 5 +++-- shvatka/tgbot/dialogs/level_scn/getters.py | 1 - shvatka/tgbot/dialogs/time_hint/getters.py | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/shvatka/common/factory.py b/shvatka/common/factory.py index 27f85ea3..4ecc19d1 100644 --- a/shvatka/common/factory.py +++ b/shvatka/common/factory.py @@ -8,7 +8,8 @@ P, name_mapping, loader, - Chain, dumper, + Chain, + dumper, ) from adaptix.load_error import LoadError from adaptix._internal.morphing.provider_template import ABCProxy @@ -38,7 +39,7 @@ def create_telegraph(self, bot_config: BotConfig) -> Telegraph: REQUIRED_GAME_RECIPES = [ loader(HintsList, lambda x: HintsList(x), Chain.LAST), ABCProxy(HintsList, list[TimeHint]), # internal class, can be broken in next version adaptix - dumper(set, lambda x: tuple(x)) + dumper(set, lambda x: tuple(x)), ] diff --git a/shvatka/tgbot/dialogs/level_scn/getters.py b/shvatka/tgbot/dialogs/level_scn/getters.py index a0ab860a..8057fd62 100644 --- a/shvatka/tgbot/dialogs/level_scn/getters.py +++ b/shvatka/tgbot/dialogs/level_scn/getters.py @@ -1,6 +1,5 @@ from adaptix import Retort from aiogram_dialog import DialogManager -from dataclass_factory import Factory from shvatka.core.models.dto import scn from shvatka.core.models.dto.scn import TimeHint diff --git a/shvatka/tgbot/dialogs/time_hint/getters.py b/shvatka/tgbot/dialogs/time_hint/getters.py index d8e688dc..6decdcaa 100644 --- a/shvatka/tgbot/dialogs/time_hint/getters.py +++ b/shvatka/tgbot/dialogs/time_hint/getters.py @@ -1,6 +1,5 @@ from adaptix import Retort from aiogram_dialog import DialogManager -from dataclass_factory import Factory from shvatka.core.models.dto.scn.hint_part import AnyHint From d2b0f90edaf28e61543c3a0bf9e185ad4653e746 Mon Sep 17 00:00:00 2001 From: bomzheg Date: Sun, 6 Oct 2024 13:08:44 +0300 Subject: [PATCH 15/34] fixed bot --- tests/mocks/bot.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/mocks/bot.py b/tests/mocks/bot.py index c154981e..52d775c9 100644 --- a/tests/mocks/bot.py +++ b/tests/mocks/bot.py @@ -7,6 +7,7 @@ from shvatka.tgbot.config.models.bot import BotConfig from shvatka.tgbot.config.models.main import TgBotConfig from shvatka.tgbot.views.bot_alert import BotAlert +from shvatka.tgbot.views.jinja_filters import setup_jinja class MockMessageManagerProvider(Provider): @@ -22,7 +23,9 @@ class MockBotProvider(Provider): @provide async def get_bot(self, config: TgBotConfig) -> Bot: - return MockedBot(token=config.bot.token) + bot = MockedBot(token=config.bot.token) + setup_jinja(bot) + return bot @provide async def bot_alert(self, bot: Bot, config: BotConfig) -> BotAlert: From 1526ce8596dd128bda629fe0ad0ea50477be0995 Mon Sep 17 00:00:00 2001 From: bomzheg Date: Sun, 6 Oct 2024 13:15:24 +0300 Subject: [PATCH 16/34] few renames --- shvatka/core/services/game.py | 10 ++++++---- .../crawler/game_scn/loader/load_scns.py | 16 ++++++++-------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/shvatka/core/services/game.py b/shvatka/core/services/game.py index 65d24bef..3c7eb7a9 100644 --- a/shvatka/core/services/game.py +++ b/shvatka/core/services/game.py @@ -37,11 +37,11 @@ async def upsert_game( raw_scn: scn.RawGameScenario, author: dto.Player, dao: GameUpserter, - dcf: Retort, + retort: Retort, file_gateway: FileGateway, ) -> dto.FullGame: check_allow_be_author(author) - game_scn = parse_uploaded_game(raw_scn, dcf) + game_scn = parse_uploaded_game(raw_scn, retort) if not await dao.is_name_available(name=game_scn.name): if not await dao.is_author_game_by_name(name=game_scn.name, author=author): raise CantEditGame( @@ -121,7 +121,7 @@ async def get_game_package( id_: int, author: dto.Player, dao: GamePackager, - dcf: Retort, + retort: Retort, file_gateway: FileGateway, ) -> scn.RawGameScenario: game = await dao.get_full(id_=id_) @@ -151,7 +151,9 @@ async def get_game_package( ) else: game_stat = None - return scn.RawGameScenario(scn=dcf.dump(scenario), files=contents, stat=dcf.dump(game_stat)) + return scn.RawGameScenario( + scn=retort.dump(scenario), files=contents, stat=retort.dump(game_stat) + ) async def get_active(dao: ActiveGameFinder) -> dto.Game | None: diff --git a/shvatka/infrastructure/crawler/game_scn/loader/load_scns.py b/shvatka/infrastructure/crawler/game_scn/loader/load_scns.py index 8fe95700..e0206bc1 100644 --- a/shvatka/infrastructure/crawler/game_scn/loader/load_scns.py +++ b/shvatka/infrastructure/crawler/game_scn/loader/load_scns.py @@ -50,7 +50,7 @@ async def main(): bot_player=bot_player, dao=dao, file_gateway=file_gateway, - dcf=await dishka.get(Retort), + retort=await dishka.get(Retort), path=config.file_storage_config.path.parent / "scn", ) finally: @@ -61,7 +61,7 @@ async def load_scns( bot_player: dto.Player, dao: HolderDao, file_gateway: FileGateway, - dcf: Retort, + retort: Retort, path: Path, ): files = sorted(path.glob("*.zip"), key=lambda p: int(p.stem)) @@ -73,12 +73,12 @@ async def load_scns( player=bot_player, dao=dao, file_gateway=file_gateway, - dcf=dcf, + retort=retort, zip_scn=game_zip_scn, ) if not game: continue - results = load_results(game_zip_scn, dcf) + results = load_results(game_zip_scn, retort) await dao.game.set_completed(game) await set_results(game, results, dao) await dao.commit() @@ -192,7 +192,7 @@ async def transfer_ownership(game: dto.FullGame, bot_player: dto.Player, dao: Ho await dao.game.transfer(game, bot_player) -def load_results(game_zip_scn: BinaryIO, dcf: Retort) -> GameStat: +def load_results(game_zip_scn: BinaryIO, retort: Retort) -> GameStat: zip_path = ZipPath(game_zip_scn) for unpacked_file in zip_path.iterdir(): if not unpacked_file.is_file(): @@ -200,7 +200,7 @@ def load_results(game_zip_scn: BinaryIO, dcf: Retort) -> GameStat: if unpacked_file.name != "results.json": continue with unpacked_file.open("r", encoding="utf8") as results_file: - results = dcf.load(json.load(results_file), GameStat) + results = retort.load(json.load(results_file), GameStat) return results raise ValueError("no results found") @@ -209,12 +209,12 @@ async def load_scn( player: dto.Player, dao: HolderDao, file_gateway: FileGateway, - dcf: Retort, + retort: Retort, zip_scn: BinaryIO, ) -> dto.FullGame | None: try: with unpack_scn(ZipPath(zip_scn)).open() as scenario: # type: scn.RawGameScenario - game = await upsert_game(scenario, player, dao.game_upserter, dcf, file_gateway) + game = await upsert_game(scenario, player, dao.game_upserter, retort, file_gateway) except exceptions.ScenarioNotCorrect as e: logger.error("game scenario from player %s has problems", player.id, exc_info=e) return None From 3bccc07a050b853f8994e1a954b147eeb9923e6c Mon Sep 17 00:00:00 2001 From: bomzheg Date: Sun, 6 Oct 2024 13:35:01 +0300 Subject: [PATCH 17/34] added render transitions --- shvatka/tgbot/dialogs/__init__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/shvatka/tgbot/dialogs/__init__.py b/shvatka/tgbot/dialogs/__init__.py index 9e517dbe..3f37c607 100644 --- a/shvatka/tgbot/dialogs/__init__.py +++ b/shvatka/tgbot/dialogs/__init__.py @@ -2,6 +2,8 @@ from aiogram.enums import ChatType from aiogram_dialog import setup_dialogs from aiogram_dialog.api.protocols import MessageManagerProtocol, BgManagerFactory +from aiogram_dialog.manager.message_manager import MessageManager +from aiogram_dialog.tools import render_transitions from shvatka.tgbot.dialogs import ( game_orgs, @@ -60,3 +62,13 @@ def setup_active_game_dialogs() -> Router: router = Router(name=__name__ + ".game.running") game_spy.setup(router) return router + + +def render_all(): + router = Router(name="main") + setup(router, MessageManager()) + render_transitions(router, title="Shvatka", filename="shvatka-dialogs") + + +if __name__ == "__main__": + render_all() From 2572e9e833eef820be13576ed57c16fdd7ba1688 Mon Sep 17 00:00:00 2001 From: bomzheg Date: Sun, 6 Oct 2024 14:08:52 +0300 Subject: [PATCH 18/34] added levels transitions --- shvatka/tgbot/dialogs/level_scn/dialogs.py | 19 ++++++++++++++++++- shvatka/tgbot/dialogs/preview_data.py | 14 ++++++++++++++ shvatka/tgbot/dialogs/time_hint/dialogs.py | 3 ++- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/shvatka/tgbot/dialogs/level_scn/dialogs.py b/shvatka/tgbot/dialogs/level_scn/dialogs.py index 0ebb9d83..2c2b059c 100644 --- a/shvatka/tgbot/dialogs/level_scn/dialogs.py +++ b/shvatka/tgbot/dialogs/level_scn/dialogs.py @@ -1,7 +1,7 @@ from aiogram import F from aiogram_dialog import Dialog, Window from aiogram_dialog.widgets.input import TextInput -from aiogram_dialog.widgets.kbd import Button, Cancel, ScrollingGroup, Select +from aiogram_dialog.widgets.kbd import Button, Cancel, ScrollingGroup, Select, Start, Next from aiogram_dialog.widgets.text import Const, Jinja from shvatka.tgbot import states @@ -29,6 +29,7 @@ start_bonus_keys, start_edit_time_hint, ) +from ..preview_data import PreviewStart level = Dialog( Window( @@ -51,6 +52,7 @@ id="level_id", ), state=states.LevelSG.level_id, + preview_add_transitions=[Next()], ), Window( Jinja( @@ -84,6 +86,12 @@ preview_data={ "level_id": "Pinky Pie", }, + preview_add_transitions=[ + PreviewStart(state=states.LevelKeysSG.keys), + PreviewStart(state=states.LevelBonusKeysSG.bonus_keys), + PreviewStart(state=states.LevelHintsSG.time_hints), + Cancel(), + ], ), on_process_result=process_level_result, ) @@ -122,6 +130,11 @@ preview_data={ "level_id": "Pinky Pie", }, + preview_add_transitions=[ + PreviewStart(state=states.LevelKeysSG.keys), + PreviewStart(state=states.LevelBonusKeysSG.bonus_keys), + PreviewStart(state=states.LevelHintsSG.time_hints), + ], ), on_process_result=process_level_result, on_start=on_start_level_edit, @@ -205,6 +218,10 @@ "time_hints": [], "level_id": "Pinky Pie", }, + preview_add_transitions=[ + PreviewStart(state=states.TimeHintSG.time), + PreviewStart(state=states.TimeHintEditSG.details), + ], ), on_process_result=process_time_hint_result, on_start=on_start_hints_edit, diff --git a/shvatka/tgbot/dialogs/preview_data.py b/shvatka/tgbot/dialogs/preview_data.py index 0c30341c..6be247ca 100644 --- a/shvatka/tgbot/dialogs/preview_data.py +++ b/shvatka/tgbot/dialogs/preview_data.py @@ -1,5 +1,9 @@ from datetime import datetime +from aiogram.fsm.state import State +from aiogram_dialog.widgets.kbd import Start, Cancel, SwitchTo +from aiogram_dialog.widgets.text import Const + from shvatka.core.models import dto from shvatka.core.models.enums import GameStatus from shvatka.core.utils.datetime_utils import tz_utc @@ -34,3 +38,13 @@ levels_count=13, ) TIMES_PRESET = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60] + + +class PreviewStart(Start): + def __init__(self, state: State): + super().__init__(Const(""), "", state) + + +class PreviewSwitchTo(SwitchTo): + def __init__(self, state: State): + super().__init__(Const(""), "", state) diff --git a/shvatka/tgbot/dialogs/time_hint/dialogs.py b/shvatka/tgbot/dialogs/time_hint/dialogs.py index a1360faf..655d9435 100644 --- a/shvatka/tgbot/dialogs/time_hint/dialogs.py +++ b/shvatka/tgbot/dialogs/time_hint/dialogs.py @@ -24,7 +24,7 @@ edit_single_hint, save_edited_time_hint, ) -from shvatka.tgbot.dialogs.preview_data import TIMES_PRESET +from shvatka.tgbot.dialogs.preview_data import TIMES_PRESET, PreviewSwitchTo time_hint = Dialog( Window( @@ -45,6 +45,7 @@ state=states.TimeHintSG.time, getter=get_available_times, preview_data={"times": TIMES_PRESET}, + preview_add_transitions=[PreviewSwitchTo(state=states.TimeHintSG.hint)], ), Window( Jinja("Подсказка выходящая в {{time}} мин."), From b361e84de8f22abd8c1a6286ebe2e9d49714f504 Mon Sep 17 00:00:00 2001 From: bomzheg Date: Sun, 6 Oct 2024 14:10:07 +0300 Subject: [PATCH 19/34] output dir --- .gitignore | 1 + shvatka/tgbot/dialogs/__init__.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 259335a3..6b1453d4 100644 --- a/.gitignore +++ b/.gitignore @@ -150,6 +150,7 @@ dmypy.json /config/ /files/ /local-storage/ +/out/ #pyro stuff *.session diff --git a/shvatka/tgbot/dialogs/__init__.py b/shvatka/tgbot/dialogs/__init__.py index 3f37c607..f866df98 100644 --- a/shvatka/tgbot/dialogs/__init__.py +++ b/shvatka/tgbot/dialogs/__init__.py @@ -67,7 +67,7 @@ def setup_active_game_dialogs() -> Router: def render_all(): router = Router(name="main") setup(router, MessageManager()) - render_transitions(router, title="Shvatka", filename="shvatka-dialogs") + render_transitions(router, title="Shvatka", filename="out/shvatka-dialogs") if __name__ == "__main__": From f4efede55b56f72f61fe9397bd8d07fb0e855f6b Mon Sep 17 00:00:00 2001 From: bomzheg Date: Sun, 6 Oct 2024 14:35:04 +0300 Subject: [PATCH 20/34] added transitions --- shvatka/tgbot/dialogs/game_manage/dialogs.py | 27 ++++++++++++++----- shvatka/tgbot/dialogs/game_scn/dialogs.py | 6 +++++ shvatka/tgbot/dialogs/level_manage/dialogs.py | 7 +++++ shvatka/tgbot/dialogs/level_scn/dialogs.py | 4 +-- shvatka/tgbot/dialogs/preview_data.py | 2 +- 5 files changed, 36 insertions(+), 10 deletions(-) diff --git a/shvatka/tgbot/dialogs/game_manage/dialogs.py b/shvatka/tgbot/dialogs/game_manage/dialogs.py index b82cbb8f..fc3b0f62 100644 --- a/shvatka/tgbot/dialogs/game_manage/dialogs.py +++ b/shvatka/tgbot/dialogs/game_manage/dialogs.py @@ -47,8 +47,7 @@ to_publish_game_forum, complete_game_handler, ) -from shvatka.tgbot.dialogs.preview_data import PREVIEW_GAME - +from shvatka.tgbot.dialogs.preview_data import PREVIEW_GAME, PreviewSwitchTo, PreviewStart games = Dialog( Window( @@ -67,8 +66,9 @@ ), Cancel(Const("🔙Назад")), state=states.CompletedGamesPanelSG.list, - preview_data={"games": [PREVIEW_GAME]}, getter=get_games, + preview_data={"games": [PREVIEW_GAME]}, + preview_add_transitions=[PreviewSwitchTo(states.CompletedGamesPanelSG.game)], ), Window( Jinja( @@ -117,8 +117,11 @@ state=states.CompletedGamesPanelSG.list, ), state=states.CompletedGamesPanelSG.game, - preview_data={"game": PREVIEW_GAME}, getter=get_completed_game, + preview_data={"game": PREVIEW_GAME}, + preview_add_transitions=[ + PreviewStart(states.GameOrgsSG.orgs_list), + ], ), Window( Jinja( @@ -237,8 +240,9 @@ ), Cancel(Const("🔙Назад")), state=states.MyGamesPanelSG.choose_game, - preview_data={"games": [PREVIEW_GAME]}, getter=get_my_games, + preview_data={"games": [PREVIEW_GAME]}, + preview_add_transitions=[PreviewSwitchTo(states.MyGamesPanelSG.game_menu)], ), Window( Jinja( @@ -320,8 +324,16 @@ state=states.MyGamesPanelSG.choose_game, ), state=states.MyGamesPanelSG.game_menu, - preview_data={"game": PREVIEW_GAME}, getter=get_game, + preview_data={"game": PREVIEW_GAME}, + preview_add_transitions=[ + PreviewStart(states.GameEditSG.current_levels), + PreviewStart(states.GameOrgsSG.orgs_list), + PreviewStart(states.GamePublishSG.prepare), + PreviewStart(states.GamePublishSG.forum), + Cancel(), + PreviewStart(states.GameScheduleSG.date), + ], ), Window( Jinja("Чтобы переименовать игру {{game.name}} пришли новое имя"), @@ -339,8 +351,9 @@ Calendar(id="select_game_play_date", on_click=select_date), Cancel(Const("🔙Назад")), state=states.GameScheduleSG.date, - preview_data={"game": PREVIEW_GAME}, getter=get_game, + preview_data={"game": PREVIEW_GAME}, + preview_add_transitions=[PreviewSwitchTo(states.GameScheduleSG.time)], ), Window( Case( diff --git a/shvatka/tgbot/dialogs/game_scn/dialogs.py b/shvatka/tgbot/dialogs/game_scn/dialogs.py index 6fdff055..5bccf7af 100644 --- a/shvatka/tgbot/dialogs/game_scn/dialogs.py +++ b/shvatka/tgbot/dialogs/game_scn/dialogs.py @@ -8,12 +8,14 @@ Select, Cancel, SwitchTo, + Next, ) from aiogram_dialog.widgets.text import Const, Format, Jinja from shvatka.tgbot import states from .getters import get_game_name, select_my_levels, select_full_game from .handlers import process_name, save_game, edit_level, add_level_handler, process_zip_scn +from shvatka.tgbot.dialogs.preview_data import PreviewStart game_writer = Dialog( Window( @@ -33,6 +35,7 @@ SwitchTo(Const("Загрузить из zip"), id="game_from_zip", state=states.GameWriteSG.from_zip), Cancel(Const("🔙Отменить")), state=states.GameWriteSG.game_name, + preview_add_transitions=[Next()], ), Window( Jinja("Игра {{game_name}}\n\n"), @@ -91,6 +94,9 @@ Cancel(Const("🔙Назад")), state=states.GameEditSG.current_levels, getter=select_full_game, + preview_add_transitions=[ + PreviewStart(states.LevelTestSG.wait_key), + ], ), Window( Jinja("Игра {{game.name}}\n\n"), diff --git a/shvatka/tgbot/dialogs/level_manage/dialogs.py b/shvatka/tgbot/dialogs/level_manage/dialogs.py index 35c646bf..9cc1f2bc 100644 --- a/shvatka/tgbot/dialogs/level_manage/dialogs.py +++ b/shvatka/tgbot/dialogs/level_manage/dialogs.py @@ -19,6 +19,7 @@ unlink_level_handler, delete_level_handler, ) +from shvatka.tgbot.dialogs.preview_data import PreviewStart levels_list = Dialog( Window( @@ -38,6 +39,7 @@ Cancel(Const("🔙Назад")), state=states.LevelListSG.levels, getter=get_levels, + preview_add_transitions=[PreviewStart(states.LevelManageSG.menu)], ), ) @@ -89,6 +91,10 @@ Cancel(Const("🔙Назад")), state=states.LevelManageSG.menu, getter=get_level_id, + preview_add_transitions=[ + PreviewStart(states.LevelEditSg.menu), + PreviewStart(states.LevelTestSG.wait_key), + ], ), Window( Jinja( @@ -126,5 +132,6 @@ ), getter=get_level_id, state=states.LevelTestSG.wait_key, + preview_add_transitions=[Cancel()], ), ) diff --git a/shvatka/tgbot/dialogs/level_scn/dialogs.py b/shvatka/tgbot/dialogs/level_scn/dialogs.py index 2c2b059c..954ae96f 100644 --- a/shvatka/tgbot/dialogs/level_scn/dialogs.py +++ b/shvatka/tgbot/dialogs/level_scn/dialogs.py @@ -1,7 +1,7 @@ from aiogram import F from aiogram_dialog import Dialog, Window from aiogram_dialog.widgets.input import TextInput -from aiogram_dialog.widgets.kbd import Button, Cancel, ScrollingGroup, Select, Start, Next +from aiogram_dialog.widgets.kbd import Button, Cancel, ScrollingGroup, Select, Next from aiogram_dialog.widgets.text import Const, Jinja from shvatka.tgbot import states @@ -29,7 +29,7 @@ start_bonus_keys, start_edit_time_hint, ) -from ..preview_data import PreviewStart +from shvatka.tgbot.dialogs.preview_data import PreviewStart level = Dialog( Window( diff --git a/shvatka/tgbot/dialogs/preview_data.py b/shvatka/tgbot/dialogs/preview_data.py index 6be247ca..a40a27f3 100644 --- a/shvatka/tgbot/dialogs/preview_data.py +++ b/shvatka/tgbot/dialogs/preview_data.py @@ -1,7 +1,7 @@ from datetime import datetime from aiogram.fsm.state import State -from aiogram_dialog.widgets.kbd import Start, Cancel, SwitchTo +from aiogram_dialog.widgets.kbd import Start, SwitchTo from aiogram_dialog.widgets.text import Const from shvatka.core.models import dto From 87cfd1ea8cd9a7f5bee53f17a6c9e1e16d1f2b7a Mon Sep 17 00:00:00 2001 From: bomzheg Date: Sun, 6 Oct 2024 14:47:53 +0300 Subject: [PATCH 21/34] added publish ci --- .github/workflows/test.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2c1f402d..e09cd5db 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -61,3 +61,27 @@ jobs: run: uv venv && uv pip install .[test] - name: Test with pytest run: source .venv/bin/activate && pytest + docs: + needs: [build] + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v4 + - name: Install uv + run: pipx install uv + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: '3.11' + cache: "pip" + cache-dependency-path: pyproject.toml + - name: Install dependencies + run: uv venv && uv pip install .[test] + - name: Render Aiogram-dialogs transitions + run: source .venv/bin/activate && python -m shvatka.tgbot.dialogs.__init__ + - name: Upload artifacts + uses: actions/upload-artifact@4 + with: + name: transitions + path: out/shvatka-dialogs.png + From 9d60444ee2c0ce5c8879a07c5072abfbd047cfa0 Mon Sep 17 00:00:00 2001 From: bomzheg Date: Sun, 6 Oct 2024 14:49:21 +0300 Subject: [PATCH 22/34] fixed publish ci --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e09cd5db..e85b98a9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -80,7 +80,7 @@ jobs: - name: Render Aiogram-dialogs transitions run: source .venv/bin/activate && python -m shvatka.tgbot.dialogs.__init__ - name: Upload artifacts - uses: actions/upload-artifact@4 + uses: actions/upload-artifact@v4 with: name: transitions path: out/shvatka-dialogs.png From 0462d0f9d01245f3c9cfb2d2253fb059c7ddff0d Mon Sep 17 00:00:00 2001 From: bomzheg Date: Sun, 6 Oct 2024 14:50:44 +0300 Subject: [PATCH 23/34] added tools to test deps --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index a360ebc1..9ccc2c27 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,6 +62,7 @@ test = [ "mypy>=1.1.1,<2.0", "aiogram-tests @ git+https://github.com/bomzheg/aiogram_tests.git@fix/aiogram3rc", "asgi-lifespan>=2.1.0,<3.0", + "aiogram_dialog[tools]>=2.1,<2.2", ] [project.scripts] From 3079124a6530f89f6719f73a7ea1204ce5bbf90d Mon Sep 17 00:00:00 2001 From: bomzheg Date: Sun, 6 Oct 2024 14:55:04 +0300 Subject: [PATCH 24/34] added tools to test deps --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e85b98a9..97811d8e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -69,6 +69,8 @@ jobs: - uses: actions/checkout@v4 - name: Install uv run: pipx install uv + - name: Install graphviz + run: apt install graphviz - name: Set up Python 3.11 uses: actions/setup-python@v5 with: From 4f659bb9b5293025c736e700e938796ae37a25cd Mon Sep 17 00:00:00 2001 From: bomzheg Date: Sun, 6 Oct 2024 14:57:35 +0300 Subject: [PATCH 25/34] added tools to test deps --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 97811d8e..b47f5be7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -70,7 +70,7 @@ jobs: - name: Install uv run: pipx install uv - name: Install graphviz - run: apt install graphviz + run: sudo apt install -y graphviz - name: Set up Python 3.11 uses: actions/setup-python@v5 with: From 06940e978cab87a80488a2c716911e0b312d8995 Mon Sep 17 00:00:00 2001 From: bomzheg Date: Sun, 6 Oct 2024 15:19:10 +0300 Subject: [PATCH 26/34] added /levels command --- shvatka/tgbot/dialogs/starters/editor.py | 7 ++++++- shvatka/tgbot/views/commands.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/shvatka/tgbot/dialogs/starters/editor.py b/shvatka/tgbot/dialogs/starters/editor.py index 55130932..f9b085d8 100644 --- a/shvatka/tgbot/dialogs/starters/editor.py +++ b/shvatka/tgbot/dialogs/starters/editor.py @@ -6,7 +6,7 @@ from shvatka.tgbot import states from shvatka.tgbot.filters.can_be_author import can_be_author from shvatka.tgbot.utils.router import register_start_handler -from shvatka.tgbot.views.commands import MY_GAMES_COMMAND, NEW_LEVEL_COMMAND, NEW_GAME_COMMAND +from shvatka.tgbot.views.commands import MY_GAMES_COMMAND, NEW_LEVEL_COMMAND, NEW_GAME_COMMAND, LEVELS_COMMAND logger = logging.getLogger(__name__) @@ -31,4 +31,9 @@ def setup() -> Router: state=states.GameWriteSG.game_name, router=router, ) + register_start_handler( + Command(commands=LEVELS_COMMAND), + state=states.LevelListSG.levels, + router=router, + ) return router diff --git a/shvatka/tgbot/views/commands.py b/shvatka/tgbot/views/commands.py index 57eec1c6..313968bc 100644 --- a/shvatka/tgbot/views/commands.py +++ b/shvatka/tgbot/views/commands.py @@ -105,7 +105,7 @@ def __str__(self) -> str: NEW_GAME_COMMAND = BotCommand( command="new_game", description="начать сборку новой игры из ранее написанных уровней" ) -LEVELS_COMMAND = BotCommand(command="levels", description="показать список уровней") # TODO +LEVELS_COMMAND = BotCommand(command="levels", description="показать список уровней") HELP_ORG = CommandsGroup( "Команды для организаторов:", [ From d9be11b387fe900ff76db87e800910965b781683 Mon Sep 17 00:00:00 2001 From: bomzheg Date: Sun, 6 Oct 2024 15:46:43 +0300 Subject: [PATCH 27/34] added save edited hint --- shvatka/tgbot/dialogs/level_scn/handlers.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/shvatka/tgbot/dialogs/level_scn/handlers.py b/shvatka/tgbot/dialogs/level_scn/handlers.py index 39d211f0..020a3b88 100644 --- a/shvatka/tgbot/dialogs/level_scn/handlers.py +++ b/shvatka/tgbot/dialogs/level_scn/handlers.py @@ -107,8 +107,12 @@ async def process_time_hint_result(start_data: Data, result: Any, manager: Dialo manager.dialog_data.setdefault("time_hints", []).append(new_hint) elif (edited_hint := result.get("edited_time_hint")) and isinstance(start_data, dict): old_hint = start_data["time_hint"] - assert edited_hint != old_hint - # TODO save me + if edited_hint == old_hint: + return + retort: Retort = manager.middleware_data["retort"] + hints_list = retort.load(manager.dialog_data.get("time_hints", []), scn.HintsList) + edited_list = hints_list.replace(retort.load(old_hint, scn.TimeHint), retort.load(edited_hint, scn.TimeHint)) + manager.dialog_data["time_hints"] = retort.dump(edited_list) async def process_level_result(start_data: Data, result: Any, manager: DialogManager): From cf6f23b2e6afd5eddea6e996fc19a4e126f2bb7e Mon Sep 17 00:00:00 2001 From: bomzheg Date: Sun, 6 Oct 2024 20:19:00 +0300 Subject: [PATCH 28/34] its work? --- shvatka/tgbot/dialogs/level_manage/getters.py | 10 ++++------ shvatka/tgbot/dialogs/level_scn/handlers.py | 4 +++- shvatka/tgbot/dialogs/starters/editor.py | 7 ++++++- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/shvatka/tgbot/dialogs/level_manage/getters.py b/shvatka/tgbot/dialogs/level_manage/getters.py index a61a70bd..d4d7ae45 100644 --- a/shvatka/tgbot/dialogs/level_manage/getters.py +++ b/shvatka/tgbot/dialogs/level_manage/getters.py @@ -37,14 +37,12 @@ async def get_level_and_org( dao: HolderDao, manager: DialogManager, ) -> tuple[dto.Level, dto.Organizer | None]: - try: - org_id = manager.start_data["org_id"] - except KeyError: + if "org_id" in manager.start_data: + org = await get_org_by_id(manager.start_data["org_id"], dao.organizer) + level = await get_level_by_id_for_org(manager.start_data["level_id"], org, dao.level) + else: level = await get_by_id(manager.start_data["level_id"], author, dao.level) org = await get_org(author, level, dao) - else: - org = await get_org_by_id(org_id, dao.organizer) - level = await get_level_by_id_for_org(manager.start_data["level_id"], org, dao.level) return level, org diff --git a/shvatka/tgbot/dialogs/level_scn/handlers.py b/shvatka/tgbot/dialogs/level_scn/handlers.py index 020a3b88..f82affad 100644 --- a/shvatka/tgbot/dialogs/level_scn/handlers.py +++ b/shvatka/tgbot/dialogs/level_scn/handlers.py @@ -111,7 +111,9 @@ async def process_time_hint_result(start_data: Data, result: Any, manager: Dialo return retort: Retort = manager.middleware_data["retort"] hints_list = retort.load(manager.dialog_data.get("time_hints", []), scn.HintsList) - edited_list = hints_list.replace(retort.load(old_hint, scn.TimeHint), retort.load(edited_hint, scn.TimeHint)) + edited_list = hints_list.replace( + retort.load(old_hint, scn.TimeHint), retort.load(edited_hint, scn.TimeHint) + ) manager.dialog_data["time_hints"] = retort.dump(edited_list) diff --git a/shvatka/tgbot/dialogs/starters/editor.py b/shvatka/tgbot/dialogs/starters/editor.py index f9b085d8..1f883170 100644 --- a/shvatka/tgbot/dialogs/starters/editor.py +++ b/shvatka/tgbot/dialogs/starters/editor.py @@ -6,7 +6,12 @@ from shvatka.tgbot import states from shvatka.tgbot.filters.can_be_author import can_be_author from shvatka.tgbot.utils.router import register_start_handler -from shvatka.tgbot.views.commands import MY_GAMES_COMMAND, NEW_LEVEL_COMMAND, NEW_GAME_COMMAND, LEVELS_COMMAND +from shvatka.tgbot.views.commands import ( + MY_GAMES_COMMAND, + NEW_LEVEL_COMMAND, + NEW_GAME_COMMAND, + LEVELS_COMMAND, +) logger = logging.getLogger(__name__) From f1cd5c4df66d6629c2c4aaa0cbb4bba842506a12 Mon Sep 17 00:00:00 2001 From: bomzheg Date: Sun, 6 Oct 2024 20:24:01 +0300 Subject: [PATCH 29/34] fixed show hint for edit method --- shvatka/tgbot/dialogs/time_hint/handlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shvatka/tgbot/dialogs/time_hint/handlers.py b/shvatka/tgbot/dialogs/time_hint/handlers.py index a2c4d685..4e710c0d 100644 --- a/shvatka/tgbot/dialogs/time_hint/handlers.py +++ b/shvatka/tgbot/dialogs/time_hint/handlers.py @@ -58,7 +58,7 @@ async def edit_single_hint(c: CallbackQuery, widget: Any, manager: DialogManager hint = retort.load(manager.start_data.get("time_hint"), scn.TimeHint) hint_sender = await dishka.get(HintSender) # TODO now it only show. but we want to show, to edit and to delete - chat: types.Chat = manager.middleware_data["event_from_chat"] + chat: types.Chat = manager.middleware_data["event_chat"] await hint_sender.send_hint(hint.hint[int(hint_index)], chat.id) From 2747ee0631b6cecb12774be15dfe5bb762e5f33c Mon Sep 17 00:00:00 2001 From: bomzheg Date: Sun, 6 Oct 2024 22:36:48 +0300 Subject: [PATCH 30/34] migrate select to ListGroup --- shvatka/tgbot/dialogs/level_manage/getters.py | 5 +++-- shvatka/tgbot/dialogs/time_hint/dialogs.py | 10 +++++++--- shvatka/tgbot/dialogs/time_hint/handlers.py | 6 ++++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/shvatka/tgbot/dialogs/level_manage/getters.py b/shvatka/tgbot/dialogs/level_manage/getters.py index d4d7ae45..af225d6b 100644 --- a/shvatka/tgbot/dialogs/level_manage/getters.py +++ b/shvatka/tgbot/dialogs/level_manage/getters.py @@ -40,10 +40,11 @@ async def get_level_and_org( if "org_id" in manager.start_data: org = await get_org_by_id(manager.start_data["org_id"], dao.organizer) level = await get_level_by_id_for_org(manager.start_data["level_id"], org, dao.level) + return level, org else: level = await get_by_id(manager.start_data["level_id"], author, dao.level) - org = await get_org(author, level, dao) - return level, org + org_ = await get_org(author, level, dao) + return level, org_ async def get_levels( diff --git a/shvatka/tgbot/dialogs/time_hint/dialogs.py b/shvatka/tgbot/dialogs/time_hint/dialogs.py index 655d9435..bf7b563c 100644 --- a/shvatka/tgbot/dialogs/time_hint/dialogs.py +++ b/shvatka/tgbot/dialogs/time_hint/dialogs.py @@ -8,6 +8,7 @@ Cancel, SwitchTo, ScrollingGroup, + ListGroup, ) from aiogram_dialog.widgets.text import Const, Format, Case, Jinja @@ -84,12 +85,15 @@ state=states.TimeHintEditSG.time, ), ScrollingGroup( - Select( - Jinja("{{item[1] | single_hint}}"), + ListGroup( + Button( + Jinja("{{item[1] | single_hint}}"), + on_click=edit_single_hint, + id="show", + ), id="hints", item_id_getter=lambda x: x[0], items="numerated_hints", - on_click=edit_single_hint, ), id="hints_sg", width=1, diff --git a/shvatka/tgbot/dialogs/time_hint/handlers.py b/shvatka/tgbot/dialogs/time_hint/handlers.py index 4e710c0d..f93e10e5 100644 --- a/shvatka/tgbot/dialogs/time_hint/handlers.py +++ b/shvatka/tgbot/dialogs/time_hint/handlers.py @@ -3,7 +3,7 @@ from adaptix import Retort from aiogram import types from aiogram.types import CallbackQuery, Message -from aiogram_dialog import DialogManager +from aiogram_dialog import DialogManager, SubManager from aiogram_dialog.widgets.kbd import Button from dishka import AsyncContainer from dishka.integrations.aiogram import CONTAINER_NAME @@ -52,13 +52,15 @@ async def process_time_message(m: Message, dialog_: Any, manager: DialogManager) await m.answer("Время выхода данной подсказки должно быть больше, чем предыдущей") -async def edit_single_hint(c: CallbackQuery, widget: Any, manager: DialogManager, hint_index: str): +async def edit_single_hint(c: CallbackQuery, widget: Any, manager: DialogManager): + assert isinstance(manager, SubManager) dishka: AsyncContainer = manager.middleware_data[CONTAINER_NAME] retort = await dishka.get(Retort) hint = retort.load(manager.start_data.get("time_hint"), scn.TimeHint) hint_sender = await dishka.get(HintSender) # TODO now it only show. but we want to show, to edit and to delete chat: types.Chat = manager.middleware_data["event_chat"] + hint_index = manager.item_id await hint_sender.send_hint(hint.hint[int(hint_index)], chat.id) From 230233a6efe8eb2d3c2959a4986a7d492e94e89a Mon Sep 17 00:00:00 2001 From: bomzheg Date: Sun, 6 Oct 2024 22:50:55 +0300 Subject: [PATCH 31/34] added delete hint part button --- shvatka/tgbot/dialogs/time_hint/dialogs.py | 9 +++++++-- shvatka/tgbot/dialogs/time_hint/handlers.py | 15 ++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/shvatka/tgbot/dialogs/time_hint/dialogs.py b/shvatka/tgbot/dialogs/time_hint/dialogs.py index bf7b563c..969ba8c3 100644 --- a/shvatka/tgbot/dialogs/time_hint/dialogs.py +++ b/shvatka/tgbot/dialogs/time_hint/dialogs.py @@ -23,7 +23,7 @@ hint_edit_on_start, process_edit_time_message, edit_single_hint, - save_edited_time_hint, + save_edited_time_hint, delete_single_hint, ) from shvatka.tgbot.dialogs.preview_data import TIMES_PRESET, PreviewSwitchTo @@ -91,12 +91,17 @@ on_click=edit_single_hint, id="show", ), + Button( + Const("🗑"), + on_click=delete_single_hint, + id="delete", + ), id="hints", item_id_getter=lambda x: x[0], items="numerated_hints", ), id="hints_sg", - width=1, + width=2, height=10, ), Button( diff --git a/shvatka/tgbot/dialogs/time_hint/handlers.py b/shvatka/tgbot/dialogs/time_hint/handlers.py index f93e10e5..26461897 100644 --- a/shvatka/tgbot/dialogs/time_hint/handlers.py +++ b/shvatka/tgbot/dialogs/time_hint/handlers.py @@ -56,12 +56,21 @@ async def edit_single_hint(c: CallbackQuery, widget: Any, manager: DialogManager assert isinstance(manager, SubManager) dishka: AsyncContainer = manager.middleware_data[CONTAINER_NAME] retort = await dishka.get(Retort) - hint = retort.load(manager.start_data.get("time_hint"), scn.TimeHint) + hint = retort.load(manager.dialog_data["hints"], list[AnyHint]) hint_sender = await dishka.get(HintSender) - # TODO now it only show. but we want to show, to edit and to delete chat: types.Chat = manager.middleware_data["event_chat"] hint_index = manager.item_id - await hint_sender.send_hint(hint.hint[int(hint_index)], chat.id) + await hint_sender.send_hint(hint[int(hint_index)], chat.id) + + +async def delete_single_hint(c: CallbackQuery, widget: Any, manager: DialogManager): + assert isinstance(manager, SubManager) + dishka: AsyncContainer = manager.middleware_data[CONTAINER_NAME] + retort = await dishka.get(Retort) + hints = retort.load(manager.dialog_data.get("hints"), list[AnyHint]) + hint_index = manager.item_id + hints.pop(int(hint_index)) + manager.dialog_data["hints"] = retort.dump(hints, list[AnyHint]) async def save_edited_time_hint(c: CallbackQuery, widget: Any, manager: DialogManager): From d7a3d22fa32caf6d875825261d94087225c809ce Mon Sep 17 00:00:00 2001 From: bomzheg Date: Sun, 6 Oct 2024 22:51:17 +0300 Subject: [PATCH 32/34] added delete hint part button --- shvatka/tgbot/dialogs/time_hint/dialogs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shvatka/tgbot/dialogs/time_hint/dialogs.py b/shvatka/tgbot/dialogs/time_hint/dialogs.py index 969ba8c3..b8f58e5a 100644 --- a/shvatka/tgbot/dialogs/time_hint/dialogs.py +++ b/shvatka/tgbot/dialogs/time_hint/dialogs.py @@ -23,7 +23,8 @@ hint_edit_on_start, process_edit_time_message, edit_single_hint, - save_edited_time_hint, delete_single_hint, + save_edited_time_hint, + delete_single_hint, ) from shvatka.tgbot.dialogs.preview_data import TIMES_PRESET, PreviewSwitchTo From 5fb39d699c8c2ebcf48858fcc4744c754c531df7 Mon Sep 17 00:00:00 2001 From: bomzheg Date: Sun, 6 Oct 2024 23:08:53 +0300 Subject: [PATCH 33/34] added add hint parts --- shvatka/tgbot/dialogs/time_hint/dialogs.py | 26 ++++++++++++++++++++++ shvatka/tgbot/states.py | 1 + 2 files changed, 27 insertions(+) diff --git a/shvatka/tgbot/dialogs/time_hint/dialogs.py b/shvatka/tgbot/dialogs/time_hint/dialogs.py index b8f58e5a..ccb9b2a0 100644 --- a/shvatka/tgbot/dialogs/time_hint/dialogs.py +++ b/shvatka/tgbot/dialogs/time_hint/dialogs.py @@ -105,6 +105,11 @@ width=2, height=10, ), + SwitchTo( + Const("Добавить"), + state=states.TimeHintEditSG.add_part, + id="to_add_part" + ), Button( text=Const("Сохранить изменения"), id="save_time_hint", @@ -120,5 +125,26 @@ getter=get_hints, state=states.TimeHintEditSG.time, ), + Window( + Jinja("Подсказка выходящая в {{time}} мин."), + Case( + { + False: Const("Присылай сообщения с подсказками (текст, фото, видео итд)"), + True: Jinja( + "{{hints | hints}}\n" + "Можно прислать ещё сообщения или вернуться" + ), + }, + selector="has_hints", + ), + MessageInput(func=process_hint), + SwitchTo( + text=Const("Вернуться"), + state=states.TimeHintEditSG.details, + id="to_details", + ), + getter=get_hints, + state=states.TimeHintEditSG.add_part, + ), on_start=hint_edit_on_start, ) diff --git a/shvatka/tgbot/states.py b/shvatka/tgbot/states.py index eb7d6f38..5fc9d64a 100644 --- a/shvatka/tgbot/states.py +++ b/shvatka/tgbot/states.py @@ -21,6 +21,7 @@ class TimeHintSG(StatesGroup): class TimeHintEditSG(StatesGroup): details = State() time = State() + add_part = State() class LevelListSG(StatesGroup): From 831282eb45a695365f26eb92cef7a5e187bda92c Mon Sep 17 00:00:00 2001 From: bomzheg Date: Sun, 6 Oct 2024 23:10:02 +0300 Subject: [PATCH 34/34] added add hint parts --- shvatka/tgbot/dialogs/time_hint/dialogs.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/shvatka/tgbot/dialogs/time_hint/dialogs.py b/shvatka/tgbot/dialogs/time_hint/dialogs.py index ccb9b2a0..9afd32b6 100644 --- a/shvatka/tgbot/dialogs/time_hint/dialogs.py +++ b/shvatka/tgbot/dialogs/time_hint/dialogs.py @@ -105,11 +105,7 @@ width=2, height=10, ), - SwitchTo( - Const("Добавить"), - state=states.TimeHintEditSG.add_part, - id="to_add_part" - ), + SwitchTo(Const("Добавить"), state=states.TimeHintEditSG.add_part, id="to_add_part"), Button( text=Const("Сохранить изменения"), id="save_time_hint", @@ -130,10 +126,7 @@ Case( { False: Const("Присылай сообщения с подсказками (текст, фото, видео итд)"), - True: Jinja( - "{{hints | hints}}\n" - "Можно прислать ещё сообщения или вернуться" - ), + True: Jinja("{{hints | hints}}\n" "Можно прислать ещё сообщения или вернуться"), }, selector="has_hints", ),