def _build_request(self, get_updates: bool) -> BaseRequest: prefix = "_get_updates_" if get_updates else "_" if not isinstance(getattr(self, f"{prefix}request"), DefaultValue): return getattr(self, f"{prefix}request") proxy_url = DefaultValue.get_value(getattr(self, f"{prefix}proxy_url")) if get_updates: connection_pool_size = (DefaultValue.get_value( getattr(self, f"{prefix}connection_pool_size")) or 1) else: connection_pool_size = (DefaultValue.get_value( getattr(self, f"{prefix}connection_pool_size")) or 256) timeouts = dict( connect_timeout=getattr(self, f"{prefix}connect_timeout"), read_timeout=getattr(self, f"{prefix}read_timeout"), write_timeout=getattr(self, f"{prefix}write_timeout"), pool_timeout=getattr(self, f"{prefix}pool_timeout"), ) # Get timeouts that were actually set- effective_timeouts = { key: value for key, value in timeouts.items() if not isinstance(value, DefaultValue) } return HTTPXRequest( connection_pool_size=connection_pool_size, proxy_url=proxy_url, **effective_timeouts, )
def _insert_defaults(self, data: Dict[str, object]) -> None: """Inserts the defaults values for optional kwargs for which tg.ext.Defaults provides convenience functionality, i.e. the kwargs with a tg.utils.helpers.DefaultValue default data is edited in-place. As timeout is not passed via the kwargs, it needs to be passed separately and gets returned. This can only work, if all kwargs that may have defaults are passed in data! """ # if we have Defaults, we # 1) replace all DefaultValue instances with the relevant Defaults value. If there is none, # we fall back to the default value of the bot method # 2) convert all datetime.datetime objects to timestamps wrt the correct default timezone # 3) set the correct parse_mode for all InputMedia objects for key, val in data.items(): # 1) if isinstance(val, DefaultValue): data[key] = (self.defaults.api_defaults.get(key, val.value) if self.defaults else DefaultValue.get_value(val)) # 2) elif isinstance(val, datetime): data[key] = to_timestamp( val, tzinfo=self.defaults.tzinfo if self.defaults else None) # 3) elif isinstance(val, InputMedia) and val.parse_mode is DEFAULT_NONE: val.parse_mode = self.defaults.parse_mode if self.defaults else None elif key == "media" and isinstance(val, list): for media in val: if media.parse_mode is DEFAULT_NONE: media.parse_mode = self.defaults.parse_mode if self.defaults else None
def test_slot_behaviour(self, mro_slots): inst = DefaultValue(1) for attr in inst.__slots__: assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" assert len(mro_slots(inst)) == len(set( mro_slots(inst))), "duplicate slot"
class TestDefaultValue: def test_slot_behaviour(self, mro_slots): inst = DefaultValue(1) for attr in inst.__slots__: assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" assert len(mro_slots(inst)) == len(set( mro_slots(inst))), "duplicate slot" def test_identity(self): df_1 = DefaultValue(1) df_2 = DefaultValue(2) assert df_1 is not df_2 assert df_1 != df_2 @pytest.mark.parametrize( "value,expected", [ ({}, False), ({ 1: 2 }, True), (None, False), (True, True), (1, True), (0, False), (False, False), ([], False), ([1], True), ], ) def test_truthiness(self, value, expected): assert bool(DefaultValue(value)) == expected @pytest.mark.parametrize("value", [ "string", 1, True, [1, 2, 3], { 1: 3 }, DefaultValue(1), User(1, "first", False) ]) def test_string_representations(self, value): df = DefaultValue(value) assert str(df) == f"DefaultValue({value})" assert repr(df) == repr(value) def test_as_function_argument(self): default_one = DefaultValue(1) def foo(arg=default_one): if arg is default_one: return 1 else: return 2 assert foo() == 1 assert foo(None) == 2 assert foo(1) == 2
def test_as_function_argument(self): default_one = DefaultValue(1) def foo(arg=default_one): if arg is default_one: return 1 else: return 2 assert foo() == 1 assert foo(None) == 2 assert foo(1) == 2
def build( self: "ApplicationBuilder[BT, CCT, UD, CD, BD, JQ]", ) -> Application[BT, CCT, UD, CD, BD, JQ]: """Builds a :class:`telegram.ext.Application` with the provided arguments. Calls :meth:`telegram.ext.JobQueue.set_application` and :meth:`telegram.ext.BasePersistence.set_bot` if appropriate. Returns: :class:`telegram.ext.Application` """ job_queue = DefaultValue.get_value(self._job_queue) persistence = DefaultValue.get_value(self._persistence) # If user didn't set updater if isinstance(self._updater, DefaultValue) or self._updater is None: if isinstance(self._bot, DefaultValue): # and didn't set a bot bot: Bot = self._build_ext_bot() # build a bot else: bot = self._bot # now also build an updater/update_queue for them update_queue = DefaultValue.get_value(self._update_queue) if self._updater is None: updater = None else: updater = Updater(bot=bot, update_queue=update_queue) else: # if they set an updater, get all necessary attributes for Application from Updater: updater = self._updater bot = self._updater.bot update_queue = self._updater.update_queue application: Application[ BT, CCT, UD, CD, BD, JQ] = DefaultValue.get_value( # pylint: disable=not-callable self._application_class)( bot=bot, update_queue=update_queue, updater=updater, concurrent_updates=DefaultValue.get_value( self._concurrent_updates), job_queue=job_queue, persistence=persistence, context_types=DefaultValue.get_value(self._context_types), post_init=self._post_init, post_shutdown=self._post_shutdown, **self. _application_kwargs, # For custom Application subclasses ) if job_queue is not None: job_queue.set_application(application) if persistence is not None: # This raises an exception if persistence.store_data.callback_data is True # but self.bot is not an instance of ExtBot - so no need to check that later on persistence.set_bot(bot) return application
def _build_ext_bot(self) -> ExtBot: if isinstance(self._token, DefaultValue): raise RuntimeError("No bot token was set.") return ExtBot( token=self._token, base_url=DefaultValue.get_value(self._base_url), base_file_url=DefaultValue.get_value(self._base_file_url), private_key=DefaultValue.get_value(self._private_key), private_key_password=DefaultValue.get_value( self._private_key_password), defaults=DefaultValue.get_value(self._defaults), arbitrary_callback_data=DefaultValue.get_value( self._arbitrary_callback_data), request=self._build_request(get_updates=False), get_updates_request=self._build_request(get_updates=True), )
def __init__(self: "InitApplicationBuilder"): self._token: DVInput[str] = DefaultValue("") self._base_url: DVInput[str] = DefaultValue( "https://api.telegram.org/bot") self._base_file_url: DVInput[str] = DefaultValue( "https://api.telegram.org/file/bot") self._connection_pool_size: DVInput[int] = DEFAULT_NONE self._proxy_url: DVInput[str] = DEFAULT_NONE self._connect_timeout: ODVInput[float] = DEFAULT_NONE self._read_timeout: ODVInput[float] = DEFAULT_NONE self._write_timeout: ODVInput[float] = DEFAULT_NONE self._pool_timeout: ODVInput[float] = DEFAULT_NONE self._request: DVInput["BaseRequest"] = DEFAULT_NONE self._get_updates_connection_pool_size: DVInput[int] = DEFAULT_NONE self._get_updates_proxy_url: DVInput[str] = DEFAULT_NONE self._get_updates_connect_timeout: ODVInput[float] = DEFAULT_NONE self._get_updates_read_timeout: ODVInput[float] = DEFAULT_NONE self._get_updates_write_timeout: ODVInput[float] = DEFAULT_NONE self._get_updates_pool_timeout: ODVInput[float] = DEFAULT_NONE self._get_updates_request: DVInput["BaseRequest"] = DEFAULT_NONE self._private_key: ODVInput[bytes] = DEFAULT_NONE self._private_key_password: ODVInput[bytes] = DEFAULT_NONE self._defaults: ODVInput["Defaults"] = DEFAULT_NONE self._arbitrary_callback_data: DVInput[Union[bool, int]] = DEFAULT_FALSE self._bot: DVInput[Bot] = DEFAULT_NONE self._update_queue: DVInput[Queue] = DefaultValue(Queue()) self._job_queue: ODVInput["JobQueue"] = DefaultValue(JobQueue()) self._persistence: ODVInput["BasePersistence"] = DEFAULT_NONE self._context_types: DVInput[ContextTypes] = DefaultValue( ContextTypes()) self._application_class: DVInput[Type[Application]] = DefaultValue( Application) self._application_kwargs: Dict[str, object] = {} self._concurrent_updates: DVInput[Union[int, bool]] = DEFAULT_FALSE self._updater: ODVInput[Updater] = DEFAULT_NONE self._post_init: Optional[Callable[[Application], Coroutine[Any, Any, None]]] = None self._post_shutdown: Optional[Callable[[Application], Coroutine[Any, Any, None]]] = None
def check_defaults_type(ptb_param: inspect.Parameter) -> bool: return True if DefaultValue.get_value(ptb_param.default) is None else False
async def handle_update( # type: ignore[override] self, update: Update, application: "Application", check_result: _CheckUpdateType, context: CallbackContext, ) -> Optional[object]: """Send the update to the callback for the current state and BaseHandler Args: check_result: The result from :meth:`check_update`. For this handler it's a tuple of the conversation state, key, handler, and the handler's check result. update (:class:`telegram.Update`): Incoming telegram update. application (:class:`telegram.ext.Application`): Application that originated the update. context (:class:`telegram.ext.CallbackContext`): The context as provided by the application. """ current_state, conversation_key, handler, handler_check_result = check_result raise_dp_handler_stop = False async with self._timeout_jobs_lock: # Remove the old timeout job (if present) timeout_job = self.timeout_jobs.pop(conversation_key, None) if timeout_job is not None: timeout_job.schedule_removal() # Resolution order of "block": # 1. Setting of the selected handler # 2. Setting of the ConversationHandler # 3. Default values of the bot if handler.block is not DEFAULT_TRUE: block = handler.block else: if self._block is not DEFAULT_TRUE: block = self._block elif isinstance(application.bot, ExtBot) and application.bot.defaults is not None: block = application.bot.defaults.block else: block = DefaultValue.get_value(handler.block) try: # Now create task or await the callback if block: new_state: object = await handler.handle_update( update, application, handler_check_result, context) else: new_state = application.create_task( coroutine=handler.handle_update(update, application, handler_check_result, context), update=update, ) except ApplicationHandlerStop as exception: new_state = exception.state raise_dp_handler_stop = True async with self._timeout_jobs_lock: if self.conversation_timeout: if application.job_queue is None: warn( "Ignoring `conversation_timeout` because the Application has no JobQueue.", ) elif not application.job_queue.scheduler.running: warn( "Ignoring `conversation_timeout` because the Applications JobQueue is " "not running.", ) else: # Add the new timeout job # checking if the new state is self.END is done in _schedule_job if isinstance(new_state, asyncio.Task): application.create_task( self._schedule_job_delayed(new_state, application, update, context, conversation_key), update=update, ) else: self._schedule_job(new_state, application, update, context, conversation_key) if isinstance(self.map_to_parent, dict) and new_state in self.map_to_parent: self._update_state(self.END, conversation_key) if raise_dp_handler_stop: raise ApplicationHandlerStop(self.map_to_parent.get(new_state)) return self.map_to_parent.get(new_state) if current_state != self.WAITING: self._update_state(new_state, conversation_key) if raise_dp_handler_stop: # Don't pass the new state here. If we're in a nested conversation, the parent is # expecting None as return value. raise ApplicationHandlerStop() # Signals a possible parent conversation to stay in the current state return None
def test_string_representations(self, value): df = DefaultValue(value) assert str(df) == f"DefaultValue({value})" assert repr(df) == repr(value)
def test_truthiness(self, value, expected): assert bool(DefaultValue(value)) == expected
def test_identity(self): df_1 = DefaultValue(1) df_2 = DefaultValue(2) assert df_1 is not df_2 assert df_1 != df_2