def test_data_init(self): ct = ContextTypes(SubClass, int, float, bool) assert ct.context is SubClass assert ct.bot_data is int assert ct.chat_data is float assert ct.user_data is bool with pytest.raises(ValueError, match="subclass of CallbackContext"): ContextTypes(context=bool)
def test_all_application_args_custom(self, builder, bot, monkeypatch): job_queue = JobQueue() persistence = PicklePersistence("file_path") update_queue = asyncio.Queue() context_types = ContextTypes() concurrent_updates = 123 async def post_init(app: Application) -> None: pass async def post_shutdown(app: Application) -> None: pass app = (builder.token(bot.token).job_queue(job_queue).persistence( persistence).update_queue(update_queue).context_types( context_types).concurrent_updates(concurrent_updates). post_init(post_init).post_shutdown(post_shutdown)).build() assert app.job_queue is job_queue assert app.job_queue.application is app assert app.persistence is persistence assert app.persistence.bot is app.bot assert app.update_queue is update_queue assert app.updater.update_queue is update_queue assert app.updater.bot is app.bot assert app.context_types is context_types assert app.concurrent_updates == concurrent_updates assert app.post_init is post_init assert app.post_shutdown is post_shutdown updater = Updater(bot=bot, update_queue=update_queue) app = ApplicationBuilder().updater(updater).build() assert app.updater is updater assert app.bot is updater.bot assert app.update_queue is updater.update_queue
def test_slot_behaviour(self, mro_slots): instance = ContextTypes() for attr in instance.__slots__: assert getattr(instance, attr, 'err') != 'err', f"got extra slot '{attr}'" assert len(mro_slots(instance)) == len(set(mro_slots(instance))), "duplicate slot" with pytest.raises(AttributeError): instance.custom
async def test_with_context_types(self, ud, cd, bd, singlefile): cc = ContextTypes(user_data=ud, chat_data=cd, bot_data=bd) persistence = PicklePersistence("pickletest", single_file=singlefile, context_types=cc) assert isinstance(await persistence.get_bot_data(), bd) assert await persistence.get_bot_data() == 0 persistence.user_data = None persistence.chat_data = None await persistence.drop_user_data(123) await persistence.drop_chat_data(123) assert isinstance(await persistence.get_user_data(), dict) assert isinstance(await persistence.get_chat_data(), dict) persistence.user_data = None persistence.chat_data = None await persistence.update_user_data(1, ud(1)) await persistence.update_chat_data(1, cd(1)) await persistence.update_bot_data(bd(1)) assert (await persistence.get_user_data())[1] == 1 assert (await persistence.get_chat_data())[1] == 1 assert await persistence.get_bot_data() == 1 await persistence.flush() persistence = PicklePersistence("pickletest", single_file=singlefile, context_types=cc) assert isinstance((await persistence.get_user_data())[1], ud) assert (await persistence.get_user_data())[1] == 1 assert isinstance((await persistence.get_chat_data())[1], cd) assert (await persistence.get_chat_data())[1] == 1 assert isinstance(await persistence.get_bot_data(), bd) assert await persistence.get_bot_data() == 1
def test_data_assignment(self): ct = ContextTypes() with pytest.raises(AttributeError): ct.bot_data = bool with pytest.raises(AttributeError): ct.user_data = bool with pytest.raises(AttributeError): ct.chat_data = bool
def main() -> None: """Run the bot.""" context_types = ContextTypes(context=CustomContext, chat_data=ChatData) application = Application.builder().token("TOKEN").context_types(context_types).build() # run track_users in its own group to not interfere with the user handlers application.add_handler(TypeHandler(Update, track_users), group=-1) application.add_handler(CommandHandler("start", start)) application.add_handler(CallbackQueryHandler(count_click)) application.add_handler(CommandHandler("print_users", print_users)) application.run_polling()
def test_custom_context_init(self, bot): cc = ContextTypes( context=CustomContext, user_data=int, chat_data=float, bot_data=complex, ) dispatcher = Dispatcher(bot, Queue(), context_types=cc) assert isinstance(dispatcher.user_data[1], int) assert isinstance(dispatcher.chat_data[1], float) assert isinstance(dispatcher.bot_data, complex)
def main() -> None: """Run the bot.""" context_types = ContextTypes(context=CustomContext, chat_data=ChatData) updater = Updater("TOKEN", context_types=context_types) dispatcher = updater.dispatcher # run track_users in its own group to not interfere with the user handlers dispatcher.add_handler(TypeHandler(Update, track_users), group=-1) dispatcher.add_handler(CommandHandler("start", start)) dispatcher.add_handler(CallbackQueryHandler(count_click)) dispatcher.add_handler(CommandHandler("print_users", print_users)) updater.start_polling() updater.idle()
async def test_custom_context(self, bot, job_queue): application = (ApplicationBuilder().token(bot.token).context_types( ContextTypes(context=CustomContext, bot_data=int, user_data=float, chat_data=complex)).build()) job_queue.set_application(application) def callback(context): self.result = ( type(context), context.user_data, context.chat_data, type(context.bot_data), ) job_queue.run_once(callback, 0.1) await asyncio.sleep(0.15) assert self.result == (CustomContext, None, None, int)
def test_custom_context(self, bot, job_queue): dispatcher = Dispatcher( bot, Queue(), context_types=ContextTypes(context=CustomContext, bot_data=int, user_data=float, chat_data=complex), ) job_queue.set_dispatcher(dispatcher) def callback(context): self.result = ( type(context), context.user_data, context.chat_data, type(context.bot_data), ) job_queue.run_once(callback, 0.1) sleep(0.15) assert self.result == (CustomContext, None, None, int)
def test_custom_context_handler_callback(self, bot): def callback(_, context): self.received = ( type(context), type(context.user_data), type(context.chat_data), type(context.bot_data), ) dispatcher = Dispatcher( bot, Queue(), context_types=ContextTypes(context=CustomContext, bot_data=int, user_data=float, chat_data=complex), ) dispatcher.add_handler(MessageHandler(Filters.all, callback)) dispatcher.process_update(self.message_update) sleep(0.1) assert self.received == (CustomContext, float, complex, int)
def main() -> None: """Setup and run ONGAbot""" context_types = ContextTypes(bot_data=BotData, user_data=UserData) persistence = PicklePersistence(filename=os.getenv("DB_PATH", "ongabot.db"), context_types=context_types) updater = Updater( os.getenv("API_TOKEN"), persistence=persistence, use_context=True, context_types=context_types, ) # Register handlers dispatcher = updater.dispatcher dispatcher.add_handler(StartCommandHandler()) dispatcher.add_handler(HelpCommandHandler()) dispatcher.add_handler(OngaCommandHandler()) dispatcher.add_handler(NewEventCommandHandler()) dispatcher.add_handler(CancelEventCommandHandler()) dispatcher.add_handler(EventPollHandler()) dispatcher.add_handler(EventPollAnswerHandler()) dispatcher.add_handler(ScheduleCommandHandler()) dispatcher.add_handler(DeScheduleCommandHandler()) dispatcher.add_error_handler(error) bot_data: BotData = persistence.bot_data bot_data.schedule_all_event_jobs(updater.job_queue, create_event_callback) # Start the bot updater.start_polling() # Block until you press Ctrl-C or the process receives SIGINT, SIGTERM or # SIGABRT. This should be used most of the time, since start_polling() is # non-blocking and will stop the bot gracefully. updater.idle()
async def main() -> None: """Set up the application and a custom webserver.""" url = "https://domain.tld" admin_chat_id = 123456 port = 8000 context_types = ContextTypes(context=CustomContext) # Here we set updater to None because we want our custom webhook server to handle the updates # and hence we don't need an Updater instance application = (Application.builder().token("TOKEN").updater( None).context_types(context_types).build()) # save the values in `bot_data` such that we may easily access them in the callbacks application.bot_data["url"] = url application.bot_data["admin_chat_id"] = admin_chat_id # register handlers application.add_handler(CommandHandler("start", start)) application.add_handler( TypeHandler(type=WebhookUpdate, callback=webhook_update)) # Pass webhook settings to telegram await application.bot.set_webhook(url=f"{url}/telegram") # Set up webserver async def telegram(request: Request) -> Response: """Handle incoming Telegram updates by putting them into the `update_queue`""" await application.update_queue.put( Update.de_json(data=await request.json(), bot=application.bot)) return Response() async def custom_updates(request: Request) -> PlainTextResponse: """ Handle incoming webhook updates by also putting them into the `update_queue` if the required parameters were passed correctly. """ try: user_id = int(request.query_params["user_id"]) payload = request.query_params["payload"] except KeyError: return PlainTextResponse( status_code=HTTPStatus.BAD_REQUEST, content= "Please pass both `user_id` and `payload` as query parameters.", ) except ValueError: return PlainTextResponse( status_code=HTTPStatus.BAD_REQUEST, content="The `user_id` must be a string!", ) await application.update_queue.put( WebhookUpdate(user_id=user_id, payload=payload)) return PlainTextResponse( "Thank you for the submission! It's being forwarded.") async def health(_: Request) -> PlainTextResponse: """For the health endpoint, reply with a simple plain text message.""" return PlainTextResponse(content="The bot is still running fine :)") starlette_app = Starlette(routes=[ Route("/telegram", telegram, methods=["POST"]), Route("/healthcheck", health, methods=["GET"]), Route("/submitpayload", custom_updates, methods=["POST", "GET"]), ]) webserver = uvicorn.Server(config=uvicorn.Config( app=starlette_app, port=port, use_colors=False, host="127.0.0.1", )) # Run application and webserver together async with application: await application.start() await webserver.serve() await application.stop()
def __init__( self, bot: 'Bot', update_queue: Queue, workers: int = 4, exception_event: Event = None, job_queue: 'JobQueue' = None, persistence: BasePersistence = None, use_context: bool = True, context_types: ContextTypes[CCT, UD, CD, BD] = None, ): self.bot = bot self.update_queue = update_queue self.job_queue = job_queue self.workers = workers self.use_context = use_context self.context_types = cast(ContextTypes[CCT, UD, CD, BD], context_types or ContextTypes()) if not use_context: warnings.warn( 'Old Handler API is deprecated - see https://git.io/fxJuV for details', TelegramDeprecationWarning, stacklevel=3, ) if self.workers < 1: warnings.warn( 'Asynchronous callbacks can not be processed without at least one worker thread.' ) self.user_data: DefaultDict[int, UD] = defaultdict( self.context_types.user_data) self.chat_data: DefaultDict[int, CD] = defaultdict( self.context_types.chat_data) self.bot_data = self.context_types.bot_data() self.persistence: Optional[BasePersistence] = None self._update_persistence_lock = Lock() if persistence: if not isinstance(persistence, BasePersistence): raise TypeError( "persistence must be based on telegram.ext.BasePersistence" ) self.persistence = persistence self.persistence.set_bot(self.bot) if self.persistence.store_user_data: self.user_data = self.persistence.get_user_data() if not isinstance(self.user_data, defaultdict): raise ValueError("user_data must be of type defaultdict") if self.persistence.store_chat_data: self.chat_data = self.persistence.get_chat_data() if not isinstance(self.chat_data, defaultdict): raise ValueError("chat_data must be of type defaultdict") if self.persistence.store_bot_data: self.bot_data = self.persistence.get_bot_data() if not isinstance(self.bot_data, self.context_types.bot_data): raise ValueError( f"bot_data must be of type {self.context_types.bot_data.__name__}" ) if self.persistence.store_callback_data: self.bot = cast(telegram.ext.extbot.ExtBot, self.bot) persistent_data = self.persistence.get_callback_data() if persistent_data is not None: if not isinstance(persistent_data, tuple) and len(persistent_data) != 2: raise ValueError('callback_data must be a 2-tuple') self.bot.callback_data_cache = CallbackDataCache( self.bot, self.bot.callback_data_cache.maxsize, persistent_data=persistent_data, ) else: self.persistence = None self.handlers: Dict[int, List[Handler]] = {} """Dict[:obj:`int`, List[:class:`telegram.ext.Handler`]]: Holds the handlers per group.""" self.groups: List[int] = [] """List[:obj:`int`]: A list with all groups.""" self.error_handlers: Dict[Callable, Union[bool, DefaultValue]] = {} """Dict[:obj:`callable`, :obj:`bool`]: A dict, where the keys are error handlers and the values indicate whether they are to be run asynchronously.""" self.running = False """:obj:`bool`: Indicates if this dispatcher is running.""" self.__stop_event = Event() self.__exception_event = exception_event or Event() self.__async_queue: Queue = Queue() self.__async_threads: Set[Thread] = set() # For backward compatibility, we allow a "singleton" mode for the dispatcher. When there's # only one instance of Dispatcher, it will be possible to use the `run_async` decorator. with self.__singleton_lock: if self.__singleton_semaphore.acquire(blocking=False): # pylint: disable=R1732 self._set_singleton(self) else: self._set_singleton(None)
def test_slot_behaviour(self, mro_slots): instance = ContextTypes() for attr in instance.__slots__: assert getattr(instance, attr, "err") != "err", f"got extra slot '{attr}'" assert len(mro_slots(instance)) == len(set(mro_slots(instance))), "duplicate slot"