From 8c801eb66e3b2351dfb215db1659941452a78173 Mon Sep 17 00:00:00 2001 From: DaShMore Date: Thu, 25 Sep 2025 13:22:14 +0300 Subject: [PATCH] new bot structure --- .env.example | 3 +++ .gitignore | 4 +++- app/bot/__init__.py | 3 +++ app/{ => bot}/bot.py | 14 +++++------- app/bot/dialogs/flows/__init__.py | 8 +++++++ app/bot/dialogs/flows/admin/__init__.py | 3 +++ app/bot/dialogs/flows/user/__init__.py | 6 +++++ app/bot/dialogs/flows/user/start/dialogs.py | 14 ++++++++++++ app/bot/dialogs/flows/user/start/states.py | 5 +++++ app/{utils => bot/dialogs/widgets}/getters.py | 2 +- .../handlers/commands.py} | 6 ++--- app/dialogs/__init__.py | 6 ----- app/dialogs/admin_dialog.py | 0 app/dialogs/user_dialog.py | 22 ------------------- app/handlers/__init__.py | 6 ----- .../database}/database.py | 0 app/{db => infrastructure/database}/models.py | 0 config/config.py | 12 ++++++++++ config/settings.toml | 1 + main.py | 5 +++++ pyproject.toml | 1 + uv.lock | 11 ++++++++++ 22 files changed, 85 insertions(+), 47 deletions(-) create mode 100644 .env.example create mode 100644 app/bot/__init__.py rename app/{ => bot}/bot.py (56%) create mode 100644 app/bot/dialogs/flows/__init__.py create mode 100644 app/bot/dialogs/flows/admin/__init__.py create mode 100644 app/bot/dialogs/flows/user/__init__.py create mode 100644 app/bot/dialogs/flows/user/start/dialogs.py create mode 100644 app/bot/dialogs/flows/user/start/states.py rename app/{utils => bot/dialogs/widgets}/getters.py (91%) rename app/{handlers/user_handlers.py => bot/handlers/commands.py} (65%) delete mode 100644 app/dialogs/__init__.py delete mode 100644 app/dialogs/admin_dialog.py delete mode 100644 app/dialogs/user_dialog.py delete mode 100644 app/handlers/__init__.py rename app/{db => infrastructure/database}/database.py (100%) rename app/{db => infrastructure/database}/models.py (100%) create mode 100644 config/config.py create mode 100644 config/settings.toml create mode 100644 main.py diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ede2441 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +ENV_FOR_DYNACONF=production + +BOT_TOKEN= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7b284dd..da78a4b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ __pycache__ .venv .env -*.db \ No newline at end of file +*.db +# Ignore dynaconf secret files +.secrets.* diff --git a/app/bot/__init__.py b/app/bot/__init__.py new file mode 100644 index 0000000..d8ef71b --- /dev/null +++ b/app/bot/__init__.py @@ -0,0 +1,3 @@ +from .bot import main + +__all__ = ["main"] \ No newline at end of file diff --git a/app/bot.py b/app/bot/bot.py similarity index 56% rename from app/bot.py rename to app/bot/bot.py index 6928403..bafd6ba 100644 --- a/app/bot.py +++ b/app/bot/bot.py @@ -2,21 +2,19 @@ import asyncio from aiogram import Bot, Dispatcher from aiogram_dialog import setup_dialogs -from bestconfig import Config -from app.dialogs import dialogs_router -from app.handlers import handlers_router +from app.bot.dialogs.flows import dialogs_router +from app.bot.handlers.commands import commands_router +from config.config import settings -cfg = Config() - -bot = Bot(token=cfg.get("bot_token")) +bot = Bot(token=settings.bot_token) dp = Dispatcher() async def main(): - dp.include_router(handlers_router) - dp.include_router(dialogs_router) setup_dialogs(dp) + dp.include_router(commands_router) + dp.include_router(dialogs_router) await dp.start_polling(bot) diff --git a/app/bot/dialogs/flows/__init__.py b/app/bot/dialogs/flows/__init__.py new file mode 100644 index 0000000..f5d7585 --- /dev/null +++ b/app/bot/dialogs/flows/__init__.py @@ -0,0 +1,8 @@ +from aiogram import Router + +from .admin import admin_router +from .user import user_router + +dialogs_router = Router(name="dialogs") +dialogs_router.include_router(admin_router) +dialogs_router.include_router(user_router) diff --git a/app/bot/dialogs/flows/admin/__init__.py b/app/bot/dialogs/flows/admin/__init__.py new file mode 100644 index 0000000..297b1ad --- /dev/null +++ b/app/bot/dialogs/flows/admin/__init__.py @@ -0,0 +1,3 @@ +from aiogram import Router + +admin_router = Router(name="admin dialogs") \ No newline at end of file diff --git a/app/bot/dialogs/flows/user/__init__.py b/app/bot/dialogs/flows/user/__init__.py new file mode 100644 index 0000000..64aed02 --- /dev/null +++ b/app/bot/dialogs/flows/user/__init__.py @@ -0,0 +1,6 @@ +from aiogram import Router + +from .start.dialogs import start_dialog + +user_router = Router(name="user") +user_router.include_router(start_dialog) diff --git a/app/bot/dialogs/flows/user/start/dialogs.py b/app/bot/dialogs/flows/user/start/dialogs.py new file mode 100644 index 0000000..7a5f971 --- /dev/null +++ b/app/bot/dialogs/flows/user/start/dialogs.py @@ -0,0 +1,14 @@ +from aiogram_dialog import Dialog, Window +from aiogram_dialog.widgets.text import Format + +from app.bot.dialogs.widgets.getters import username_getter + +from .states import StartSG + +start_dialog = Dialog( + Window( + Format("Hello, {username}"), + getter=username_getter, + state=StartSG.start, + ) +) diff --git a/app/bot/dialogs/flows/user/start/states.py b/app/bot/dialogs/flows/user/start/states.py new file mode 100644 index 0000000..b93478a --- /dev/null +++ b/app/bot/dialogs/flows/user/start/states.py @@ -0,0 +1,5 @@ +from aiogram.fsm.state import State, StatesGroup + + +class StartSG(StatesGroup): + start = State() \ No newline at end of file diff --git a/app/utils/getters.py b/app/bot/dialogs/widgets/getters.py similarity index 91% rename from app/utils/getters.py rename to app/bot/dialogs/widgets/getters.py index 1431d49..6584094 100644 --- a/app/utils/getters.py +++ b/app/bot/dialogs/widgets/getters.py @@ -4,5 +4,5 @@ from aiogram_dialog import DialogManager async def username_getter( dialog_manager: DialogManager, event_from_user: User, **kwargs -): +) -> dict[str, str]: return {"username": event_from_user.username} diff --git a/app/handlers/user_handlers.py b/app/bot/handlers/commands.py similarity index 65% rename from app/handlers/user_handlers.py rename to app/bot/handlers/commands.py index 33bfed8..ec7700a 100644 --- a/app/handlers/user_handlers.py +++ b/app/bot/handlers/commands.py @@ -3,10 +3,10 @@ from aiogram.filters import CommandStart from aiogram.types import Message from aiogram_dialog import DialogManager -from app.dialogs.user_dialog import StartSG +from app.bot.dialogs.flows.user.start.states import StartSG -router = Router(name="user_handlers") +commands_router = Router(name="commands_router") -@router.message(CommandStart()) +@commands_router.message(CommandStart()) async def command_start_process(message: Message, dialog_manager: DialogManager): await dialog_manager.start(state=StartSG.start) diff --git a/app/dialogs/__init__.py b/app/dialogs/__init__.py deleted file mode 100644 index ca7b3f7..0000000 --- a/app/dialogs/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from aiogram import Router - -from .user_dialog import router as user_router - -dialogs_router = Router(name="dialogs") -dialogs_router.include_router(user_router) \ No newline at end of file diff --git a/app/dialogs/admin_dialog.py b/app/dialogs/admin_dialog.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/dialogs/user_dialog.py b/app/dialogs/user_dialog.py deleted file mode 100644 index 3c1a1eb..0000000 --- a/app/dialogs/user_dialog.py +++ /dev/null @@ -1,22 +0,0 @@ -from aiogram import Router -from aiogram.fsm.state import State, StatesGroup -from aiogram_dialog import Dialog, DialogManager, StartMode, Window -from aiogram_dialog.widgets.text import Format - -from app.utils.getters import username_getter - - -class StartSG(StatesGroup): - start = State() - - -start_dialog = Dialog( - Window( - Format("Hello, {username}"), - getter=username_getter, - state=StartSG.start - ) -) - -router = Router(name="user_dialogs") -router.include_router(start_dialog) diff --git a/app/handlers/__init__.py b/app/handlers/__init__.py deleted file mode 100644 index cf524db..0000000 --- a/app/handlers/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from aiogram import Router - -from .user_handlers import router as user_router - -handlers_router = Router(name="handlers") -handlers_router.include_router(user_router) diff --git a/app/db/database.py b/app/infrastructure/database/database.py similarity index 100% rename from app/db/database.py rename to app/infrastructure/database/database.py diff --git a/app/db/models.py b/app/infrastructure/database/models.py similarity index 100% rename from app/db/models.py rename to app/infrastructure/database/models.py diff --git a/config/config.py b/config/config.py new file mode 100644 index 0000000..1886b1a --- /dev/null +++ b/config/config.py @@ -0,0 +1,12 @@ +from dynaconf import Dynaconf + +settings = Dynaconf( + envvar_prefix=False, + environments=True, + env_switcher="ENV_FOR_DYNACONF", + settings_files=["settings.toml"], + load_dotenv=True +) + +# `envvar_prefix` = export envvars with `export DYNACONF_FOO=bar`. +# `settings_files` = Load these files in the order. diff --git a/config/settings.toml b/config/settings.toml new file mode 100644 index 0000000..1d1ad6d --- /dev/null +++ b/config/settings.toml @@ -0,0 +1 @@ +[DEVELOPMENT] \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..0105c1f --- /dev/null +++ b/main.py @@ -0,0 +1,5 @@ +import asyncio + +from app.bot import main + +asyncio.run(main()) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index a8aa5e6..c700a27 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,6 +6,7 @@ dependencies = [ "aiogram>=3.22.0", "aiogram-dialog>=2.4.0", "bestconfig>=1.3.6", + "dynaconf>=3.2.11", "psycopg>=3.2.10", "sqlalchemy>=2.0.43", ] diff --git a/uv.lock b/uv.lock index 921eee3..c7e9436 100644 --- a/uv.lock +++ b/uv.lock @@ -163,6 +163,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, ] +[[package]] +name = "dynaconf" +version = "3.2.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/62/eb/e9d1249ff56b11e63fd8c7d0fcc1f94704e21693c16862bf0ebfb07bd61a/dynaconf-3.2.11.tar.gz", hash = "sha256:4cfc6a730c533bf1a1d0bf266ae202133a22236bb3227d23eff4b8542d4034a5", size = 234694, upload-time = "2025-05-06T15:44:59.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/64/580c74003a356c5662e7b1da43ecd7cbda6e8f970c87b30c5a654c8ccb53/dynaconf-3.2.11-py2.py3-none-any.whl", hash = "sha256:660de90879d4da236f79195692a7d197957224d7acf922bcc6899187dc7b4a27", size = 236536, upload-time = "2025-05-06T15:44:56.18Z" }, +] + [[package]] name = "frozenlist" version = "1.7.0" @@ -607,6 +616,7 @@ dependencies = [ { name = "aiogram" }, { name = "aiogram-dialog" }, { name = "bestconfig" }, + { name = "dynaconf" }, { name = "psycopg" }, { name = "sqlalchemy" }, ] @@ -616,6 +626,7 @@ requires-dist = [ { name = "aiogram", specifier = ">=3.22.0" }, { name = "aiogram-dialog", specifier = ">=2.4.0" }, { name = "bestconfig", specifier = ">=1.3.6" }, + { name = "dynaconf", specifier = ">=3.2.11" }, { name = "psycopg", specifier = ">=3.2.10" }, { name = "sqlalchemy", specifier = ">=2.0.43" }, ]