Add support for managed_bot updates

This commit is contained in:
JRoot Junior 2026-04-03 22:18:24 +03:00
parent da14db0963
commit 81cc924ed8
No known key found for this signature in database
GPG key ID: 738964250D5FF6E2
6 changed files with 63 additions and 0 deletions

View file

@ -0,0 +1,43 @@
# Adding a New Update/Event Type to aiogram Dispatcher
When Telegram Bot API adds a new update type (e.g. `managed_bot`, `purchased_paid_media`), the following files must be touched. Types, enums, and butcher configs are generated — the dispatcher integration is manual.
## Checklist (in order)
### Generated / butcher layer (run `butcher parse && butcher refresh && butcher apply all`)
- `.butcher/types/<TypeName>/entity.json` — type definition
- `.butcher/types/Update/entity.json` — add field to Update entity
- `aiogram/types/<type_name>.py` — generated type class
- `aiogram/types/__init__.py` — export
- `aiogram/enums/update_type.py``NEW_TYPE = "new_type"` enum member
- `aiogram/types/update.py``new_type: NewType | None = None` field + TYPE_CHECKING import + `__init__` signature
### Manual dispatcher integration (NOT generated)
1. **`aiogram/types/update.py`** — `event_type` property (lines ~161-215): add `if self.new_type: return "new_type"` before the `raise UpdateTypeLookupError` line
2. **`aiogram/dispatcher/router.py`** — two places in `Router.__init__`:
- Add `self.new_type = TelegramEventObserver(router=self, event_name="new_type")` after the last observer attribute (before `self.errors`)
- Add `"new_type": self.new_type,` to the `self.observers` dict (before `"error"`)
3. **`aiogram/dispatcher/middlewares/user_context.py`** — `resolve_event_context()` method: add `if event.new_type: return EventContext(user=..., chat=...)` before `return EventContext()`. Use `user` field for user-scoped events, `chat` for chat-scoped. No `business_connection_id` unless the event has one.
### Tests (manual)
4. **`tests/test_dispatcher/test_dispatcher.py`** — add `pytest.param("new_type", Update(update_id=42, new_type=NewType(...)), has_chat, has_user)` to `test_listen_update` parametrize list. Import `NewType` in the imports block.
5. **`tests/test_dispatcher/test_router.py`** — add `assert router.observers["new_type"] == router.new_type` to `test_observers_config`
### Docs (generated or manual stub)
- `docs/api/types/<type_name>.rst` — RST stub
- `docs/api/types/index.rst` — add to index
## Key invariants
- The snake_case name must be identical across: `UpdateType` enum value, `Update` field name, `event_type` return string, Router attribute name, observers dict key, and `TelegramEventObserver(event_name=...)`.
- `Update.event_type` uses `@lru_cache()` — never mutate Update fields after construction.
- The routing machinery (`propagate_event`, middleware chains, sub-router propagation) requires **zero changes** — it operates on observer names looked up dynamically.
## Example: `managed_bot` (API 9.6)
- Type: `ManagedBotUpdated` with fields `user: User` (creator) and `bot_user: User` (the managed bot)
- user_context: `EventContext(user=event.managed_bot.user)` — user only, no chat
- `has_chat=False, has_user=True` in test parametrization

View file

@ -183,4 +183,6 @@ class UserContextMiddleware(BaseMiddleware):
return EventContext(
user=event.purchased_paid_media.from_user,
)
if event.managed_bot:
return EventContext(user=event.managed_bot.user)
return EventContext()

View file

@ -85,6 +85,7 @@ class Router:
router=self,
event_name="purchased_paid_media",
)
self.managed_bot = TelegramEventObserver(router=self, event_name="managed_bot")
self.errors = self.error = TelegramEventObserver(router=self, event_name="error")
@ -115,6 +116,7 @@ class Router:
"edited_business_message": self.edited_business_message,
"business_message": self.business_message,
"purchased_paid_media": self.purchased_paid_media,
"managed_bot": self.managed_bot,
"error": self.errors,
}

View file

@ -211,6 +211,8 @@ class Update(TelegramObject):
return "business_message"
if self.purchased_paid_media:
return "purchased_paid_media"
if self.managed_bot:
return "managed_bot"
raise UpdateTypeLookupError("Update does not contain any known event type.")

View file

@ -30,6 +30,7 @@ from aiogram.types import (
ChatMemberUpdated,
ChosenInlineResult,
InlineQuery,
ManagedBotUpdated,
Message,
MessageReactionCountUpdated,
MessageReactionUpdated,
@ -602,6 +603,18 @@ class TestDispatcher:
False,
True,
),
pytest.param(
"managed_bot",
Update(
update_id=42,
managed_bot=ManagedBotUpdated(
user=User(id=42, is_bot=False, first_name="Test"),
bot_user=User(id=100, is_bot=True, first_name="ManagedBot"),
),
),
False,
True,
),
],
)
async def test_listen_update(

View file

@ -71,6 +71,7 @@ class TestRouter:
assert router.observers["shipping_query"] == router.shipping_query
assert router.observers["pre_checkout_query"] == router.pre_checkout_query
assert router.observers["poll"] == router.poll
assert router.observers["managed_bot"] == router.managed_bot
async def test_emit_startup(self):
router1 = Router()