chatbot1-1 Exercise 3: Swapping the Provider, and What Protocol Checks ======================================================================== ex3.py (save in the project root): import asyncio from bot.core import Bot class ReverseProvider: async def reply(self, messages): return messages[-1]["content"][::-1] class NotAProvider: pass async def main(): bot = Bot(ReverseProvider()) print(await bot.handle("s", "hello")) print(await bot.handle("s", "Ada Lovelace")) broken = Bot(NotAProvider()) print("constructed the Bot with a non-provider without any error") try: await broken.handle("s", "hello") except AttributeError as e: print("AttributeError on first message:", e) asyncio.run(main()) Run with: py ex3.py Output: olleh ecalevoL adA constructed the Bot with a non-provider without any error AttributeError on first message: 'NotAProvider' object has no attribute 'reply' WHY THIS WORKS AS AN ANSWER --------------------------- ReverseProvider has the right method and nothing else, and Bot and the channels needed no change to use it: they only call provider.reply(messages), so anything that offers it can be plugged in. This is the design rule of the course (the core does not know which model is behind it). The second half shows the limit of typing.Protocol: it is a promise checked by a static type checker (such as mypy or pyright, which were not run here) before the program runs, not by Python while it runs. Python happily built a Bot around an object with no reply method, and only complained at the first message with an AttributeError. The consequence for this course is that provider contract tests, added in chapter 2, matter: they are the run-time check that a provider really behaves like one.