How to correctly pass dependencies to handler? #4902
-
I recently tried to implement code with something like the following structure: from telegram.ext import ApplicationBuilder, CommandHandler, ContextTypes
from telegram import Update
class MyService: ...
async def hello(update: Update, context: ContextTypes.DEFAULT_TYPE):
# somehow get the service and do something useful
service: MyService = ...
...
def main():
# сreate instances of the required classes
service = MyService(...)
app = ApplicationBuilder().token("...").build()
app.add_handler(CommandHandler("hello", hello))
# somehow bind the service to the application
# so that it can be retrieved later in the handler.
...
app.run_polling() I'd be surprised there isn't a recommended way to do this easily, or I didn't read the documentation well. I understand that we can write something like this: # in main
app.service = service # just create attribute dynamically
# in handler
service = typing.cast(MyService, context.application.service) or like this # in main
app.bot_data["service"] = service
# in handler
service = typing.cast(MyService, context.bot_data["service"]) or even make a variable at the module level. But these solutions look dirty from the point of view of typing and testability. The closest result I was able to achieve was using custom Application and CallbackContext classes. Full code exampleimport typing as t
from telegram import Update
from telegram.ext import (
ApplicationBuilder,
Application,
CommandHandler,
ContextTypes,
CallbackContext,
ExtBot,
)
ADict = dict[t.Any, t.Any]
class MyService: ...
class MyApplication(Application):
def __init__(self, service: MyService, **kwargs):
super().__init__(**kwargs)
self.service = service
class MyContext(CallbackContext[ExtBot[None], ADict, ADict, ADict]):
@property
def service(self):
return t.cast(MyApplication, self.application).service
async def hello(update: Update, context: MyContext) -> None:
print(context.service) # <__main__.MyService object at 0x7f174d334680>
def main():
service = MyService()
context_types = ContextTypes(context=MyContext)
app = (
ApplicationBuilder()
.application_class(MyApplication, {"service": service})
.context_types(context_types)
.token("...")
.build()
)
app.add_handler(CommandHandler("hello", hello))
app.run_polling() But even here we need to use type cast because in the context the application property is annotated as In general, I would like to know if my last example can be made better, maybe there are other alternatives and I missed something? |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
I usually use a custom context, see e.g. https://github.com/python-telegram-bot/ptbcontrib/tree/main/ptbcontrib/username_to_chat_api or https://docs.python-telegram-bot.org/en/stable/examples.contexttypesbot.html Not sure that's fully accommodating of your fully type cast example here |
Beta Was this translation helpful? Give feedback.
I usually use a custom context, see e.g. https://github.com/python-telegram-bot/ptbcontrib/tree/main/ptbcontrib/username_to_chat_api or https://docs.python-telegram-bot.org/en/stable/examples.contexttypesbot.html
Not sure that's fully accommodating of your fully type cast example here