chatbot1-9 Exercise 3: Flood Control, Redelivery and Giving Up ================================================================ ex3.py (save in the project root): import asyncio from bot.channels.telegram import TelegramAdapter, TelegramError from bot.core import Bot from bot.providers import FakeProvider class FlakyAPI: def __init__(self): self.sent, self.failures = [], [TelegramError("Too Many Requests: retry after 3", error_code=429, retry_after=3.0), TelegramError("Too Many Requests: retry after 6", error_code=429, retry_after=6.0)] async def send_message(self, chat_id, text, reply_to=None): if self.failures: raise self.failures.pop(0) self.sent.append(text) async def send_chat_action(self, chat_id, action="typing"): pass class Sleeps: def __init__(self): self.delays = [] async def __call__(self, s): self.delays.append(s) def update(uid, text): return {"update_id": uid, "message": {"message_id": uid, "from": {"id": 42, "is_bot": False}, "chat": {"id": 42, "type": "private"}, "date": 1, "text": text}} async def main(): api, sleeps = FlakyAPI(), Sleeps() bot = Bot(FakeProvider(["first reply", "second reply"])) adapter = TelegramAdapter(bot, api, sleep=sleeps) await adapter.handle_update(update(1, "hello")) # the send fails twice with flood control, then succeeds print("sent after flood control:", api.sent, "| waited:", sleeps.delays) await adapter.handle_update(update(1, "hello")) # Telegram redelivers the same update_id await adapter.handle_update(update(2, "hello again")) # a genuinely new one print("after a redelivery and a new message:", api.sent) print("model calls:", len(bot.provider.calls), "| events:", adapter.events) # three failures in a row: give up, but do not crash api.failures = [TelegramError("flood", error_code=429, retry_after=1.0)] * 3 await adapter.handle_update(update(3, "one more")) print("when all three send attempts fail: messages sent so far =", len(api.sent), "(nothing new) | events:", adapter.events, "| waited:", sleeps.delays[-2:]) asyncio.run(main()) Run with: py ex3.py Output: sent after flood control: ['first reply'] | waited: [3.0, 6.0] after a redelivery and a new message: ['first reply', 'second reply'] model calls: 2 | events: ['duplicate_update'] when all three send attempts fail: messages sent so far = 2 (nothing new) | events: ['duplicate_update', 'send_failed:429'] | waited: [1.0, 1.0] WHY THIS WORKS AS AN ANSWER --------------------------- The first send failed with flood control asking for 3 seconds, then again asking for 6; the adapter waited exactly 3.0 and 6.0 (the fake sleep recorded them) and the third attempt succeeded, so the user got "first reply" once. Sending the same update_id again produced no second model call and no second message (one duplicate_update event), which protects against Telegram redelivering an update after a slow or failed webhook response. A genuinely new update was answered normally: two model calls in total. When all three attempts hit flood control, the adapter recorded send_failed:429 and gave up without raising, so the polling loop or webhook keeps serving other people. The cost is that the user never gets that reply; a more careful bot might queue it for later, which this one does not.