Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
rev: v6.0.0
hooks:
- id: trailing-whitespace
exclude: ^(.*\.md|.*\.yml|.*\.yaml)
Expand Down
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
SHELL = /bin/bash


.PHONY: upgrade
upgrade:
uv lock --upgrade


.PHONY: run-test
run-test:
@echo "Running linters and tests in parallel (uv run)..."
Expand Down
27 changes: 18 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,15 @@ logging.basicConfig(level=logging.INFO)
bot = Bot()
dp = Dispatcher()


# Ответ бота при нажатии на кнопку "Начать"
@dp.bot_started()
async def bot_started(event: BotStarted):
await bot.send_message(
chat_id=event.chat_id,
text='Привет! Отправь мне /start'
chat_id=event.chat_id, text="Привет! Отправь мне /start"
)


# Ответ бота на команду /start
@dp.message_created(CommandStart())
async def hello(event: MessageCreated):
Expand All @@ -118,7 +119,7 @@ async def main():
await dp.start_polling(bot)


if __name__ == '__main__':
if __name__ == "__main__":
asyncio.run(main())
```

Expand Down Expand Up @@ -167,12 +168,12 @@ async def hello(event: MessageCreated):
async def main():
await dp.handle_webhook(
bot=bot,
host='0.0.0.0',
host="0.0.0.0",
port=8080,
)


if __name__ == '__main__':
if __name__ == "__main__":
asyncio.run(main())
```

Expand Down Expand Up @@ -201,11 +202,15 @@ import uvicorn
from fastapi import FastAPI
from maxapi.webhook.fastapi import FastAPIMaxWebhook


async def main():
webhook = FastAPIMaxWebhook(dp=dp, bot=bot)
app = FastAPI(lifespan=webhook.lifespan)
webhook.setup(app, path='/webhook')
await uvicorn.Server(uvicorn.Config(app, host='0.0.0.0', port=8080)).serve()
webhook.setup(app, path="/webhook")
await uvicorn.Server(
uvicorn.Config(app, host="0.0.0.0", port=8080)
).serve()


asyncio.run(main())
```
Expand All @@ -217,10 +222,14 @@ import asyncio
import uvicorn
from maxapi.webhook.litestar import LitestarMaxWebhook


async def main():
webhook = LitestarMaxWebhook(dp=dp, bot=bot)
app = webhook.create_app(path='/webhook')
await uvicorn.Server(uvicorn.Config(app, host='0.0.0.0', port=8080)).serve()
app = webhook.create_app(path="/webhook")
await uvicorn.Server(
uvicorn.Config(app, host="0.0.0.0", port=8080)
).serve()


asyncio.run(main())
```
98 changes: 68 additions & 30 deletions doc/router_inner_middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,22 @@ closes #132

```python
admin_router = Router(router_id="admin")
admin_router.middleware(BroadcastLockMiddleware()) # хочется: lock только когда broadcast реально начнётся
admin_router.middleware(
BroadcastLockMiddleware()
) # хочется: lock только когда broadcast реально начнётся


@admin_router.message_created(IsAdmin(), Command("broadcast"))
async def handle_broadcast(event): ...


fallback_router = Router(router_id="fallback")


@fallback_router.message_created()
async def handle_fallback(event): ...


dp.include_routers(admin_router, fallback_router)
```

Expand Down Expand Up @@ -183,8 +189,8 @@ handler.mw_chain = accumulated_inner_middlewares + handler.middlewares → call_
### 1. `Dispatcher.__init__` — переименование и новый атрибут

```python
self.outer_middlewares: list[BaseMiddleware] = [] # было: self.middlewares
self.inner_middlewares: list[BaseMiddleware] = [] # новый
self.outer_middlewares: list[BaseMiddleware] = [] # было: self.middlewares
self.inner_middlewares: list[BaseMiddleware] = [] # новый
```

Атрибут `middlewares` сохраняется как `@property`-алиас с
Expand Down Expand Up @@ -223,6 +229,7 @@ def register_outer_middleware(self, middleware: BaseMiddleware) -> None:
"""
self.outer_middlewares.append(middleware)


def register_inner_middleware(self, middleware: BaseMiddleware) -> None:
"""Inner middleware (после фильтров handler).

Expand All @@ -233,6 +240,7 @@ def register_inner_middleware(self, middleware: BaseMiddleware) -> None:
"""
self.inner_middlewares.append(middleware)


def register_middleware(self, middleware: BaseMiddleware) -> None:
"""
.. deprecated::
Expand Down Expand Up @@ -266,7 +274,7 @@ def _iter_routers(
self,
routers,
parent_middlewares=None,
parent_inner_middlewares=None, # новый параметр
parent_inner_middlewares=None, # новый параметр
parent_filters=None,
parent_base_filters=None,
path=None,
Expand All @@ -280,12 +288,14 @@ def _iter_routers(
if router is self:
accumulated_inner_middlewares = inner_middlewares
else:
accumulated_inner_middlewares = inner_middlewares + router.inner_middlewares
accumulated_inner_middlewares = (
inner_middlewares + router.inner_middlewares
)

yield (
router,
accumulated_middlewares,
accumulated_inner_middlewares, # ← новый 5-й элемент
accumulated_inner_middlewares, # ← новый 5-й элемент
accumulated_filters,
accumulated_base_filters,
)
Expand Down Expand Up @@ -317,17 +327,15 @@ def _prepare_handlers(self, bot: Bot) -> None:
# Порядок: global inner → router inner (накопленный) → handler mw
# Внешний слой — global, внутренний — handler.
all_inner = (
global_inner_mw
+ accumulated_inner_mw
+ handler.middlewares
global_inner_mw + accumulated_inner_mw + handler.middlewares
)
handler.mw_chain = self.build_middleware_chain(
all_inner,
functools.partial(self.call_handler, handler),
)
router.handlers_by_type.setdefault(
handler.update_type, []
).append(handler)
router.handlers_by_type.setdefault(handler.update_type, []).append(
handler
)

self._cached_router_entries = list(self._iter_unique_routers(self.routers))
```
Expand Down Expand Up @@ -457,9 +465,12 @@ fallback_router:
```python
from maxapi import Router, Dispatcher, Bot
from myapp.middleware import (
RequestIdMiddleware, LoggingMiddleware,
DbTransactionMiddleware, HandlerLatencyMetricMiddleware,
AdminAccessLogMiddleware, BroadcastLockMiddleware,
RequestIdMiddleware,
LoggingMiddleware,
DbTransactionMiddleware,
HandlerLatencyMetricMiddleware,
AdminAccessLogMiddleware,
BroadcastLockMiddleware,
)
from myapp.filters import IsAdmin
from maxapi.filters import Command
Expand All @@ -470,14 +481,18 @@ dp = Dispatcher()
# Срабатывает для каждого события, даже для тех, что в итоге будут
# проигнорированы. Подходит всё, что должно «жить» в контексте
# любого входящего апдейта.
dp.register_outer_middleware(RequestIdMiddleware()) # request_id для трейсинга
dp.register_outer_middleware(LoggingMiddleware()) # лог всех updates
dp.register_outer_middleware(RequestIdMiddleware()) # request_id для трейсинга
dp.register_outer_middleware(LoggingMiddleware()) # лог всех updates

# ─── Глобальный inner ─────────────────────────────────────────────
# Срабатывает только если какой-то handler реально будет выполнен.
# Подходит то, что бессмысленно делать «вхолостую».
dp.register_inner_middleware(DbTransactionMiddleware()) # транзакция только под handler
dp.register_inner_middleware(HandlerLatencyMetricMiddleware()) # метрика времени handler
dp.register_inner_middleware(
DbTransactionMiddleware()
) # транзакция только под handler
dp.register_inner_middleware(
HandlerLatencyMetricMiddleware()
) # метрика времени handler

admin_router = Router(router_id="admin")

Expand All @@ -493,9 +508,11 @@ admin_router.register_outer_middleware(AdminAccessLogMiddleware())
# исключительно перед началом тяжёлой операции.
admin_router.register_inner_middleware(BroadcastLockMiddleware())


@admin_router.message_created(IsAdmin(), Command("broadcast"))
async def broadcast(event): ...


dp.include_routers(admin_router)
```

Expand Down Expand Up @@ -544,10 +561,14 @@ outer middleware и фильтры:

```python
parent = Router(router_id="parent")
parent.register_inner_middleware(DbTransactionMiddleware()) # на любой handler в parent + детях
parent.register_inner_middleware(
DbTransactionMiddleware() # на любой handler в parent + детях
)

child = Router(router_id="child")
child.register_inner_middleware(ChildAuditMiddleware()) # только на handler внутри child
child.register_inner_middleware(
ChildAuditMiddleware() # только на handler внутри child
)
parent.include_routers(child)
```

Expand Down Expand Up @@ -600,10 +621,14 @@ handler.mw_chain = [DbTransactionMiddleware] + handler.middlewares → call_hand
# aiogram
router = Router()

router.message.outer_middleware(LoggingMiddleware()) # outer для message-обзёрвера
router.message.middleware(AuthMiddleware()) # inner для message-обзёрвера
router.message.outer_middleware(
LoggingMiddleware()
) # outer для message-обзёрвера
router.message.middleware(AuthMiddleware()) # inner для message-обзёрвера

router.callback_query.middleware(RateLimitMiddleware()) # только для callback_query
router.callback_query.middleware(
RateLimitMiddleware()
) # только для callback_query

# Дополнительно: middleware на уровне Router.update — ловит ВСЕ типы
router.update.outer_middleware(TracingMiddleware())
Expand Down Expand Up @@ -679,14 +704,26 @@ warning):

```python
# Глобальный уровень
dp.register_outer_middleware(LoggingMW()) # ✅ канонично: outer (для каждого события)
dp.register_inner_middleware(MetricsMW()) # ✅ канонично: inner (только когда handler сработал)
dp.middleware(SomeMW()) # ⚠️ DeprecationWarning → используйте .register_outer_middleware()
dp.register_outer_middleware(
LoggingMW()
) # ✅ канонично: outer (для каждого события)
dp.register_inner_middleware(
MetricsMW()
) # ✅ канонично: inner (только когда handler сработал)
dp.middleware(
SomeMW()
) # ⚠️ DeprecationWarning → используйте .register_outer_middleware()

# Роутерный уровень
router.register_outer_middleware(TracingMW()) # ✅ канонично: outer (до filters handler)
router.register_inner_middleware(AuthMW()) # ✅ канонично: inner (после filters handler)
router.middleware(AuditMW()) # ⚠️ DeprecationWarning → используйте .register_outer_middleware()
router.register_outer_middleware(
TracingMW()
) # ✅ канонично: outer (до filters handler)
router.register_inner_middleware(
AuthMW()
) # ✅ канонично: inner (после filters handler)
router.middleware(
AuditMW()
) # ⚠️ DeprecationWarning → используйте .register_outer_middleware()
```

**Почему это лучше, чем в aiogram**: в aiogram `router.middleware()` без
Expand Down Expand Up @@ -755,6 +792,7 @@ execution order). Причины:
message_router = Router()
message_router.register_inner_middleware(MyMW())


@message_router.message_created()
async def handler(event): ...
```
Expand Down
Loading
Loading