Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
SHELL = /bin/bash


.PHONY: upgrade
upgrade:
uv lock --upgrade


.PHONY: run-test
run-test:
run-test: upgrade
Comment thread
Olegt0rr marked this conversation as resolved.
Outdated
@echo "Running linters and tests in parallel (uv run)..."
@status=0; \
uv run -- ruff check . & p1=$$!; \
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