From db078a5f50f1adaa75063cccfef3f01969845592 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler Date: Sat, 7 Nov 2020 15:48:34 +0100 Subject: [PATCH] allow_sending_without_reply parameter & corresponding Default --- telegram/bot.py | 73 ++++++++++++++++++++ telegram/ext/defaults.py | 18 +++++ tests/test_animation.py | 36 ++++++++++ tests/test_bot.py | 142 +++++++++++++++++++++++++++++++++++++++ tests/test_contact.py | 37 ++++++++++ tests/test_defaults.py | 2 + tests/test_document.py | 36 ++++++++++ tests/test_inputmedia.py | 36 ++++++++++ tests/test_invoice.py | 58 ++++++++++++++++ tests/test_location.py | 35 ++++++++++ tests/test_photo.py | 36 ++++++++++ tests/test_sticker.py | 35 ++++++++++ tests/test_venue.py | 37 ++++++++++ tests/test_video.py | 36 ++++++++++ tests/test_videonote.py | 36 ++++++++++ tests/test_voice.py | 36 ++++++++++ 16 files changed, 689 insertions(+) diff --git a/telegram/bot.py b/telegram/bot.py index fa97e603cdb..9251e9ea4be 100644 --- a/telegram/bot.py +++ b/telegram/bot.py @@ -246,6 +246,7 @@ def _message( reply_to_message_id: Union[str, int] = None, disable_notification: bool = None, reply_markup: ReplyMarkup = None, + allow_sending_without_reply: bool = None, timeout: float = None, api_kwargs: JSONDict = None, ) -> Union[bool, Message, None]: @@ -255,6 +256,9 @@ def _message( if disable_notification is not None: data['disable_notification'] = disable_notification + if allow_sending_without_reply is not None: + data['allow_sending_without_reply'] = allow_sending_without_reply + if reply_markup is not None: if isinstance(reply_markup, ReplyMarkup): # We need to_json() instead of to_dict() here, because reply_markups may be @@ -398,6 +402,7 @@ def send_message( reply_markup: ReplyMarkup = None, timeout: float = None, api_kwargs: JSONDict = None, + allow_sending_without_reply: bool = None, ) -> Optional[Message]: """Use this method to send text messages. @@ -415,6 +420,8 @@ def send_message( receive a notification with no sound. reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. + allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message + should be sent even if the specified replied-to message is not found. reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -444,6 +451,7 @@ def send_message( disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, + allow_sending_without_reply=allow_sending_without_reply, timeout=timeout, api_kwargs=api_kwargs, ) @@ -555,6 +563,7 @@ def send_photo( timeout: float = 20, parse_mode: str = None, api_kwargs: JSONDict = None, + allow_sending_without_reply: bool = None, ) -> Optional[Message]: """Use this method to send photos. @@ -579,6 +588,8 @@ def send_photo( receive a notification with no sound. reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. + allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message + should be sent even if the specified replied-to message is not found. reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -613,6 +624,7 @@ def send_photo( disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, + allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, ) @@ -632,6 +644,7 @@ def send_audio( parse_mode: str = None, thumb: FileLike = None, api_kwargs: JSONDict = None, + allow_sending_without_reply: bool = None, ) -> Optional[Message]: """ Use this method to send audio files, if you want Telegram clients to display them in the @@ -666,6 +679,8 @@ def send_audio( receive a notification with no sound. reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. + allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message + should be sent even if the specified replied-to message is not found. reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -716,6 +731,7 @@ def send_audio( disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, + allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, ) @@ -734,6 +750,7 @@ def send_document( thumb: FileLike = None, api_kwargs: JSONDict = None, disable_content_type_detection: bool = None, + allow_sending_without_reply: bool = None, ) -> Optional[Message]: """ Use this method to send general files. @@ -766,6 +783,8 @@ def send_document( receive a notification with no sound. reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. + allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message + should be sent even if the specified replied-to message is not found. reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -812,6 +831,7 @@ def send_document( disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, + allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, ) @@ -825,6 +845,7 @@ def send_sticker( reply_markup: ReplyMarkup = None, timeout: float = 20, api_kwargs: JSONDict = None, + allow_sending_without_reply: bool = None, ) -> Optional[Message]: """ Use this method to send static .WEBP or animated .TGS stickers. @@ -845,6 +866,8 @@ def send_sticker( receive a notification with no sound. reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. + allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message + should be sent even if the specified replied-to message is not found. reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -874,6 +897,7 @@ def send_sticker( disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, + allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, ) @@ -894,6 +918,7 @@ def send_video( supports_streaming: bool = None, thumb: FileLike = None, api_kwargs: JSONDict = None, + allow_sending_without_reply: bool = None, ) -> Optional[Message]: """ Use this method to send video files, Telegram clients support mp4 videos @@ -931,6 +956,8 @@ def send_video( receive a notification with no sound. reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. + allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message + should be sent even if the specified replied-to message is not found. reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -983,6 +1010,7 @@ def send_video( disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, + allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, ) @@ -999,6 +1027,7 @@ def send_video_note( timeout: float = 20, thumb: FileLike = None, api_kwargs: JSONDict = None, + allow_sending_without_reply: bool = None, ) -> Optional[Message]: """ As of v.4.0, Telegram clients support rounded square mp4 videos of up to 1 minute long. @@ -1026,6 +1055,8 @@ def send_video_note( receive a notification with no sound. reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. + allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message + should be sent even if the specified replied-to message is not found. reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -1070,6 +1101,7 @@ def send_video_note( disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, + allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, ) @@ -1089,6 +1121,7 @@ def send_animation( reply_markup: ReplyMarkup = None, timeout: float = 20, api_kwargs: JSONDict = None, + allow_sending_without_reply: bool = None, ) -> Optional[Message]: """ Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without sound). @@ -1125,6 +1158,8 @@ def send_animation( receive a notification with no sound. reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. + allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message + should be sent even if the specified replied-to message is not found. reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -1170,6 +1205,7 @@ def send_animation( disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, + allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, ) @@ -1186,6 +1222,7 @@ def send_voice( timeout: float = 20, parse_mode: str = None, api_kwargs: JSONDict = None, + allow_sending_without_reply: bool = None, ) -> Optional[Message]: """ Use this method to send audio files, if you want Telegram clients to display the file @@ -1215,6 +1252,8 @@ def send_voice( receive a notification with no sound. reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. + allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message + should be sent even if the specified replied-to message is not found. reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -1251,6 +1290,7 @@ def send_voice( disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, + allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, ) @@ -1263,6 +1303,7 @@ def send_media_group( reply_to_message_id: Union[int, str] = None, timeout: float = 20, api_kwargs: JSONDict = None, + allow_sending_without_reply: bool = None, ) -> List[Optional[Message]]: """Use this method to send a group of photos or videos as an album. @@ -1276,6 +1317,8 @@ def send_media_group( receive a notification with no sound. reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. + allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message + should be sent even if the specified replied-to message is not found. timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds). api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the Telegram API. @@ -1299,6 +1342,8 @@ def send_media_group( data['reply_to_message_id'] = reply_to_message_id if disable_notification: data['disable_notification'] = disable_notification + if allow_sending_without_reply is not None: + data['allow_sending_without_reply'] = allow_sending_without_reply result = self._post('sendMediaGroup', data, timeout=timeout, api_kwargs=api_kwargs) @@ -1321,6 +1366,7 @@ def send_location( location: Location = None, live_period: int = None, api_kwargs: JSONDict = None, + allow_sending_without_reply: bool = None, ) -> Optional[Message]: """Use this method to send point on the map. @@ -1339,6 +1385,8 @@ def send_location( receive a notification with no sound. reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. + allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message + should be sent even if the specified replied-to message is not found. reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -1381,6 +1429,7 @@ def send_location( disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, + allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, ) @@ -1523,6 +1572,7 @@ def send_venue( venue: Venue = None, foursquare_type: str = None, api_kwargs: JSONDict = None, + allow_sending_without_reply: bool = None, ) -> Optional[Message]: """Use this method to send information about a venue. @@ -1547,6 +1597,8 @@ def send_venue( receive a notification with no sound. reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. + allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message + should be sent even if the specified replied-to message is not found. reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -1597,6 +1649,7 @@ def send_venue( disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, + allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, ) @@ -1614,6 +1667,7 @@ def send_contact( contact: Contact = None, vcard: str = None, api_kwargs: JSONDict = None, + allow_sending_without_reply: bool = None, ) -> Optional[Message]: """Use this method to send phone contacts. @@ -1634,6 +1688,8 @@ def send_contact( receive a notification with no sound. reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. + allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message + should be sent even if the specified replied-to message is not found. reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -1679,6 +1735,7 @@ def send_contact( disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, + allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, ) @@ -1692,6 +1749,7 @@ def send_game( reply_markup: ReplyMarkup = None, timeout: float = None, api_kwargs: JSONDict = None, + allow_sending_without_reply: bool = None, ) -> Optional[Message]: """Use this method to send a game. @@ -1704,6 +1762,8 @@ def send_game( receive a notification with no sound. reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. + allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message + should be sent even if the specified replied-to message is not found. reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): A JSON-serialized object for a new inline keyboard. If empty, one ‘Play game_title’ button will be shown. If not empty, the first button must launch the game. @@ -1729,6 +1789,7 @@ def send_game( disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, + allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, ) @@ -3005,6 +3066,7 @@ def send_invoice( send_email_to_provider: bool = None, timeout: float = None, api_kwargs: JSONDict = None, + allow_sending_without_reply: bool = None, ) -> Message: """Use this method to send invoices. @@ -3050,6 +3112,8 @@ def send_invoice( receive a notification with no sound. reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. + allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message + should be sent even if the specified replied-to message is not found. reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): A JSON-serialized object for an inline keyboard. If empty, one 'Pay total price' button will be shown. If not empty, the first button must be a Pay button. @@ -3111,6 +3175,7 @@ def send_invoice( disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, + allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, ) @@ -4107,6 +4172,7 @@ def send_poll( open_period: int = None, close_date: Union[int, datetime] = None, api_kwargs: JSONDict = None, + allow_sending_without_reply: bool = None, ) -> Message: """ Use this method to send a native poll. @@ -4144,6 +4210,8 @@ def send_poll( receive a notification with no sound. reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. + allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message + should be sent even if the specified replied-to message is not found. reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -4198,6 +4266,7 @@ def send_poll( disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, + allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, ) @@ -4257,6 +4326,7 @@ def send_dice( timeout: float = None, emoji: str = None, api_kwargs: JSONDict = None, + allow_sending_without_reply: bool = None, ) -> Message: """ Use this method to send an animated emoji, which will have a random value. On success, the @@ -4271,6 +4341,8 @@ def send_dice( receive a notification with no sound. reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the original message. + allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message + should be sent even if the specified replied-to message is not found. reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -4301,6 +4373,7 @@ def send_dice( disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, + allow_sending_without_reply=allow_sending_without_reply, api_kwargs=api_kwargs, ) diff --git a/telegram/ext/defaults.py b/telegram/ext/defaults.py index d8e3a15b5a8..ecc94e03a2c 100644 --- a/telegram/ext/defaults.py +++ b/telegram/ext/defaults.py @@ -35,6 +35,8 @@ class Defaults: receive a notification with no sound. disable_web_page_preview (:obj:`bool`): Optional. Disables link previews for links in this message. + allow_sending_without_reply (:obj:`bool`): Optional. Pass :obj:`True`, if the message + should be sent even if the specified replied-to message is not found. timeout (:obj:`int` | :obj:`float`): Optional. If this value is specified, use it as the read timeout from the server (instead of the one specified during creation of the connection pool). @@ -51,6 +53,8 @@ class Defaults: receive a notification with no sound. disable_web_page_preview (:obj:`bool`, optional): Disables link previews for links in this message. + allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message + should be sent even if the specified replied-to message is not found. timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as the read timeout from the server (instead of the one specified during creation of the connection pool). @@ -73,10 +77,12 @@ def __init__( timeout: Union[float, DefaultValue] = DEFAULT_NONE, quote: bool = None, tzinfo: pytz.BaseTzInfo = pytz.utc, + allow_sending_without_reply: bool = None, ): self._parse_mode = parse_mode self._disable_notification = disable_notification self._disable_web_page_preview = disable_web_page_preview + self._allow_sending_without_reply = allow_sending_without_reply self._timeout = timeout self._quote = quote self._tzinfo = tzinfo @@ -114,6 +120,17 @@ def disable_web_page_preview(self, value: Any) -> NoReturn: "not have any effect." ) + @property + def allow_sending_without_reply(self) -> Optional[bool]: + return self._allow_sending_without_reply + + @allow_sending_without_reply.setter + def allow_sending_without_reply(self, value: Any) -> NoReturn: + raise AttributeError( + "You can not assign a new value to defaults after because it would " + "not have any effect." + ) + @property def timeout(self) -> Union[float, DefaultValue]: return self._timeout @@ -153,6 +170,7 @@ def __hash__(self) -> int: self._parse_mode, self._disable_notification, self._disable_web_page_preview, + self._allow_sending_without_reply, self._timeout, self._quote, self._tzinfo, diff --git a/tests/test_animation.py b/tests/test_animation.py index 774923feb74..fee205c7346 100644 --- a/tests/test_animation.py +++ b/tests/test_animation.py @@ -22,6 +22,7 @@ from flaky import flaky from telegram import PhotoSize, Animation, Voice, TelegramError +from telegram.error import BadRequest from telegram.utils.helpers import escape_markdown @@ -161,6 +162,41 @@ def test_send_animation_default_parse_mode_3(self, default_bot, chat_id, animati assert message.caption == test_markdown_string assert message.caption_markdown == escape_markdown(test_markdown_string) + @flaky(3, 1) + @pytest.mark.timeout(10) + @pytest.mark.parametrize( + 'default_bot,custom', + [ + ({'allow_sending_without_reply': True}, None), + ({'allow_sending_without_reply': False}, None), + ({'allow_sending_without_reply': False}, True), + ], + indirect=['default_bot'], + ) + def test_send_animation_default_allow_sending_without_reply( + self, default_bot, chat_id, animation, custom + ): + reply_to_message = default_bot.send_message(chat_id, 'test') + reply_to_message.delete() + if custom is not None: + message = default_bot.send_animation( + chat_id, + animation, + allow_sending_without_reply=custom, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + elif default_bot.defaults.allow_sending_without_reply: + message = default_bot.send_animation( + chat_id, animation, reply_to_message_id=reply_to_message.message_id + ) + assert message.reply_to_message is None + else: + with pytest.raises(BadRequest, match='message not found'): + default_bot.send_animation( + chat_id, animation, reply_to_message_id=reply_to_message.message_id + ) + @flaky(3, 1) @pytest.mark.timeout(10) def test_resend(self, bot, chat_id, animation): diff --git a/tests/test_bot.py b/tests/test_bot.py index f12e51671fe..96bb98ec46d 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -423,6 +423,48 @@ def test_send_poll_default_parse_mode(self, default_bot, super_group_id): assert message.poll.explanation == explanation_markdown assert message.poll.explanation_entities == [] + @flaky(3, 1) + @pytest.mark.timeout(10) + @pytest.mark.parametrize( + 'default_bot,custom', + [ + ({'allow_sending_without_reply': True}, None), + ({'allow_sending_without_reply': False}, None), + ({'allow_sending_without_reply': False}, True), + ], + indirect=['default_bot'], + ) + def test_send_poll_default_allow_sending_without_reply(self, default_bot, chat_id, custom): + question = 'Is this a test?' + answers = ['Yes', 'No', 'Maybe'] + reply_to_message = default_bot.send_message(chat_id, 'test') + reply_to_message.delete() + if custom is not None: + message = default_bot.send_poll( + chat_id, + question=question, + options=answers, + allow_sending_without_reply=custom, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + elif default_bot.defaults.allow_sending_without_reply: + message = default_bot.send_poll( + chat_id, + question=question, + options=answers, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + else: + with pytest.raises(BadRequest, match='message not found'): + default_bot.send_poll( + chat_id, + question=question, + options=answers, + reply_to_message_id=reply_to_message.message_id, + ) + @flaky(3, 1) @pytest.mark.timeout(10) @pytest.mark.parametrize('emoji', Dice.ALL_EMOJI + [None]) @@ -435,6 +477,37 @@ def test_send_dice(self, bot, chat_id, emoji): else: assert message.dice.emoji == emoji + @flaky(3, 1) + @pytest.mark.timeout(10) + @pytest.mark.parametrize( + 'default_bot,custom', + [ + ({'allow_sending_without_reply': True}, None), + ({'allow_sending_without_reply': False}, None), + ({'allow_sending_without_reply': False}, True), + ], + indirect=['default_bot'], + ) + def test_send_dice_default_allow_sending_without_reply(self, default_bot, chat_id, custom): + reply_to_message = default_bot.send_message(chat_id, 'test') + reply_to_message.delete() + if custom is not None: + message = default_bot.send_dice( + chat_id, + allow_sending_without_reply=custom, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + elif default_bot.defaults.allow_sending_without_reply: + message = default_bot.send_dice( + chat_id, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + else: + with pytest.raises(BadRequest, match='message not found'): + default_bot.send_dice(chat_id, reply_to_message_id=reply_to_message.message_id) + @flaky(3, 1) @pytest.mark.timeout(10) def test_send_chat_action(self, bot, chat_id): @@ -998,6 +1071,42 @@ def test_send_game(self, bot, chat_id): assert message.game.animation.file_id != '' assert message.game.photo[0].file_size == 851 + @flaky(3, 1) + @pytest.mark.timeout(10) + @pytest.mark.parametrize( + 'default_bot,custom', + [ + ({'allow_sending_without_reply': True}, None), + ({'allow_sending_without_reply': False}, None), + ({'allow_sending_without_reply': False}, True), + ], + indirect=['default_bot'], + ) + def test_send_game_default_allow_sending_without_reply(self, default_bot, chat_id, custom): + game_short_name = 'test_game' + reply_to_message = default_bot.send_message(chat_id, 'test') + reply_to_message.delete() + if custom is not None: + message = default_bot.send_game( + chat_id, + game_short_name, + allow_sending_without_reply=custom, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + elif default_bot.defaults.allow_sending_without_reply: + message = default_bot.send_game( + chat_id, + game_short_name, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + else: + with pytest.raises(BadRequest, match='message not found'): + default_bot.send_game( + chat_id, game_short_name, reply_to_message_id=reply_to_message.message_id + ) + @flaky(3, 1) @pytest.mark.timeout(10) def test_set_game_score_1(self, bot, chat_id): @@ -1342,6 +1451,39 @@ def test_send_message_default_parse_mode(self, default_bot, chat_id): assert message.text == test_markdown_string assert message.text_markdown == escape_markdown(test_markdown_string) + @flaky(3, 1) + @pytest.mark.timeout(10) + @pytest.mark.parametrize( + 'default_bot,custom', + [ + ({'allow_sending_without_reply': True}, None), + ({'allow_sending_without_reply': False}, None), + ({'allow_sending_without_reply': False}, True), + ], + indirect=['default_bot'], + ) + def test_send_message_default_allow_sending_without_reply(self, default_bot, chat_id, custom): + reply_to_message = default_bot.send_message(chat_id, 'test') + reply_to_message.delete() + if custom is not None: + message = default_bot.send_message( + chat_id, + 'test', + allow_sending_without_reply=custom, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + elif default_bot.defaults.allow_sending_without_reply: + message = default_bot.send_message( + chat_id, 'test', reply_to_message_id=reply_to_message.message_id + ) + assert message.reply_to_message is None + else: + with pytest.raises(BadRequest, match='message not found'): + default_bot.send_message( + chat_id, 'test', reply_to_message_id=reply_to_message.message_id + ) + @flaky(3, 1) @pytest.mark.timeout(10) def test_set_and_get_my_commands(self, bot): diff --git a/tests/test_contact.py b/tests/test_contact.py index 204f6328174..cfcc01313ef 100644 --- a/tests/test_contact.py +++ b/tests/test_contact.py @@ -18,8 +18,10 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. import pytest +from flaky import flaky from telegram import Contact, Voice +from telegram.error import BadRequest @pytest.fixture(scope='class') @@ -70,6 +72,41 @@ def test(url, data, **kwargs): message = bot.send_contact(contact=contact, chat_id=chat_id) assert message + @flaky(3, 1) + @pytest.mark.timeout(10) + @pytest.mark.parametrize( + 'default_bot,custom', + [ + ({'allow_sending_without_reply': True}, None), + ({'allow_sending_without_reply': False}, None), + ({'allow_sending_without_reply': False}, True), + ], + indirect=['default_bot'], + ) + def test_send_contact_default_allow_sending_without_reply( + self, default_bot, chat_id, contact, custom + ): + reply_to_message = default_bot.send_message(chat_id, 'test') + reply_to_message.delete() + if custom is not None: + message = default_bot.send_contact( + chat_id, + contact=contact, + allow_sending_without_reply=custom, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + elif default_bot.defaults.allow_sending_without_reply: + message = default_bot.send_contact( + chat_id, contact=contact, reply_to_message_id=reply_to_message.message_id + ) + assert message.reply_to_message is None + else: + with pytest.raises(BadRequest, match='message not found'): + default_bot.send_contact( + chat_id, contact=contact, reply_to_message_id=reply_to_message.message_id + ) + def test_send_contact_without_required(self, bot, chat_id): with pytest.raises(ValueError, match='Either contact or phone_number and first_name'): bot.send_contact(chat_id=chat_id) diff --git a/tests/test_defaults.py b/tests/test_defaults.py index 5344f538d38..dcf0fb1b444 100644 --- a/tests/test_defaults.py +++ b/tests/test_defaults.py @@ -33,6 +33,8 @@ def test_data_assignment(self, cdp): defaults.disable_notification = True with pytest.raises(AttributeError): defaults.disable_web_page_preview = True + with pytest.raises(AttributeError): + defaults.allow_sending_without_reply = True with pytest.raises(AttributeError): defaults.timeout = True with pytest.raises(AttributeError): diff --git a/tests/test_document.py b/tests/test_document.py index a9b135f3697..aa4c25c0466 100644 --- a/tests/test_document.py +++ b/tests/test_document.py @@ -22,6 +22,7 @@ from flaky import flaky from telegram import Document, PhotoSize, TelegramError, Voice +from telegram.error import BadRequest from telegram.utils.helpers import escape_markdown @@ -184,6 +185,41 @@ def test_send_document_default_parse_mode_3(self, default_bot, chat_id, document assert message.caption == test_markdown_string assert message.caption_markdown == escape_markdown(test_markdown_string) + @flaky(3, 1) + @pytest.mark.timeout(10) + @pytest.mark.parametrize( + 'default_bot,custom', + [ + ({'allow_sending_without_reply': True}, None), + ({'allow_sending_without_reply': False}, None), + ({'allow_sending_without_reply': False}, True), + ], + indirect=['default_bot'], + ) + def test_send_document_default_allow_sending_without_reply( + self, default_bot, chat_id, document, custom + ): + reply_to_message = default_bot.send_message(chat_id, 'test') + reply_to_message.delete() + if custom is not None: + message = default_bot.send_document( + chat_id, + document, + allow_sending_without_reply=custom, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + elif default_bot.defaults.allow_sending_without_reply: + message = default_bot.send_document( + chat_id, document, reply_to_message_id=reply_to_message.message_id + ) + assert message.reply_to_message is None + else: + with pytest.raises(BadRequest, match='message not found'): + default_bot.send_document( + chat_id, document, reply_to_message_id=reply_to_message.message_id + ) + def test_de_json(self, bot, document): json_dict = { 'file_id': self.document_file_id, diff --git a/tests/test_inputmedia.py b/tests/test_inputmedia.py index c012be37e0b..b4a040de803 100644 --- a/tests/test_inputmedia.py +++ b/tests/test_inputmedia.py @@ -30,6 +30,7 @@ ) # noinspection PyUnresolvedReferences +from telegram.error import BadRequest from .test_animation import animation, animation_file # noqa: F401 # noinspection PyUnresolvedReferences @@ -386,6 +387,41 @@ def func(): assert all([isinstance(mes, Message) for mes in messages]) assert all([mes.media_group_id == messages[0].media_group_id for mes in messages]) + @flaky(3, 1) + @pytest.mark.timeout(10) + @pytest.mark.parametrize( + 'default_bot,custom', + [ + ({'allow_sending_without_reply': True}, None), + ({'allow_sending_without_reply': False}, None), + ({'allow_sending_without_reply': False}, True), + ], + indirect=['default_bot'], + ) + def test_send_media_group_default_allow_sending_without_reply( + self, default_bot, chat_id, media_group, custom + ): + reply_to_message = default_bot.send_message(chat_id, 'test') + reply_to_message.delete() + if custom is not None: + messages = default_bot.send_media_group( + chat_id, + media_group, + allow_sending_without_reply=custom, + reply_to_message_id=reply_to_message.message_id, + ) + assert [m.reply_to_message is None for m in messages] + elif default_bot.defaults.allow_sending_without_reply: + messages = default_bot.send_media_group( + chat_id, media_group, reply_to_message_id=reply_to_message.message_id + ) + assert [m.reply_to_message is None for m in messages] + else: + with pytest.raises(BadRequest, match='message not found'): + default_bot.send_media_group( + chat_id, media_group, reply_to_message_id=reply_to_message.message_id + ) + @flaky(3, 1) @pytest.mark.timeout(10) def test_edit_message_media(self, bot, chat_id, media_group): diff --git a/tests/test_invoice.py b/tests/test_invoice.py index 9174737a75e..12c648cd78b 100644 --- a/tests/test_invoice.py +++ b/tests/test_invoice.py @@ -21,6 +21,7 @@ from flaky import flaky from telegram import LabeledPrice, Invoice +from telegram.error import BadRequest @pytest.fixture(scope='class') @@ -147,6 +148,63 @@ def test(url, data, **kwargs): provider_data={'test_data': 123456789}, ) + @flaky(3, 1) + @pytest.mark.timeout(10) + @pytest.mark.parametrize( + 'default_bot,custom', + [ + ({'allow_sending_without_reply': True}, None), + ({'allow_sending_without_reply': False}, None), + ({'allow_sending_without_reply': False}, True), + ], + indirect=['default_bot'], + ) + def test_send_invoice_default_allow_sending_without_reply( + self, default_bot, chat_id, custom, provider_token + ): + reply_to_message = default_bot.send_message(chat_id, 'test') + reply_to_message.delete() + if custom is not None: + message = default_bot.send_invoice( + chat_id, + self.title, + self.description, + self.payload, + provider_token, + self.start_parameter, + self.currency, + self.prices, + allow_sending_without_reply=custom, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + elif default_bot.defaults.allow_sending_without_reply: + message = default_bot.send_invoice( + chat_id, + self.title, + self.description, + self.payload, + provider_token, + self.start_parameter, + self.currency, + self.prices, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + else: + with pytest.raises(BadRequest, match='message not found'): + default_bot.send_invoice( + chat_id, + self.title, + self.description, + self.payload, + provider_token, + self.start_parameter, + self.currency, + self.prices, + reply_to_message_id=reply_to_message.message_id, + ) + def test_equality(self): a = Invoice('invoice', 'desc', 'start', 'EUR', 7) b = Invoice('invoice', 'desc', 'start', 'EUR', 7) diff --git a/tests/test_location.py b/tests/test_location.py index c111110d041..78e1ae58aba 100644 --- a/tests/test_location.py +++ b/tests/test_location.py @@ -93,6 +93,41 @@ def test(url, data, **kwargs): monkeypatch.setattr(bot.request, 'post', test) assert bot.send_location(location=location, chat_id=chat_id) + @flaky(3, 1) + @pytest.mark.timeout(10) + @pytest.mark.parametrize( + 'default_bot,custom', + [ + ({'allow_sending_without_reply': True}, None), + ({'allow_sending_without_reply': False}, None), + ({'allow_sending_without_reply': False}, True), + ], + indirect=['default_bot'], + ) + def test_send_location_default_allow_sending_without_reply( + self, default_bot, chat_id, location, custom + ): + reply_to_message = default_bot.send_message(chat_id, 'test') + reply_to_message.delete() + if custom is not None: + message = default_bot.send_location( + chat_id, + location=location, + allow_sending_without_reply=custom, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + elif default_bot.defaults.allow_sending_without_reply: + message = default_bot.send_location( + chat_id, location=location, reply_to_message_id=reply_to_message.message_id + ) + assert message.reply_to_message is None + else: + with pytest.raises(BadRequest, match='message not found'): + default_bot.send_location( + chat_id, location=location, reply_to_message_id=reply_to_message.message_id + ) + def test_edit_live_location_with_location(self, monkeypatch, bot, location): def test(url, data, **kwargs): lat = data['latitude'] == location.latitude diff --git a/tests/test_photo.py b/tests/test_photo.py index 24c39dc3695..696c352cb9b 100644 --- a/tests/test_photo.py +++ b/tests/test_photo.py @@ -23,6 +23,7 @@ from flaky import flaky from telegram import Sticker, TelegramError, PhotoSize, InputFile +from telegram.error import BadRequest from telegram.utils.helpers import escape_markdown from tests.conftest import expect_bad_request @@ -198,6 +199,41 @@ def test_send_photo_default_parse_mode_3(self, default_bot, chat_id, photo_file, assert message.caption == test_markdown_string assert message.caption_markdown == escape_markdown(test_markdown_string) + @flaky(3, 1) + @pytest.mark.timeout(10) + @pytest.mark.parametrize( + 'default_bot,custom', + [ + ({'allow_sending_without_reply': True}, None), + ({'allow_sending_without_reply': False}, None), + ({'allow_sending_without_reply': False}, True), + ], + indirect=['default_bot'], + ) + def test_send_photo_default_allow_sending_without_reply( + self, default_bot, chat_id, photo_file, thumb, photo, custom + ): + reply_to_message = default_bot.send_message(chat_id, 'test') + reply_to_message.delete() + if custom is not None: + message = default_bot.send_photo( + chat_id, + photo_file, + allow_sending_without_reply=custom, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + elif default_bot.defaults.allow_sending_without_reply: + message = default_bot.send_photo( + chat_id, photo_file, reply_to_message_id=reply_to_message.message_id + ) + assert message.reply_to_message is None + else: + with pytest.raises(BadRequest, match='message not found'): + default_bot.send_photo( + chat_id, photo_file, reply_to_message_id=reply_to_message.message_id + ) + @flaky(3, 1) @pytest.mark.timeout(10) def test_get_and_download(self, bot, photo): diff --git a/tests/test_sticker.py b/tests/test_sticker.py index 047c8162eaa..906474b24e7 100644 --- a/tests/test_sticker.py +++ b/tests/test_sticker.py @@ -203,6 +203,41 @@ def test(url, data, **kwargs): message = bot.send_sticker(sticker=sticker, chat_id=chat_id) assert message + @flaky(3, 1) + @pytest.mark.timeout(10) + @pytest.mark.parametrize( + 'default_bot,custom', + [ + ({'allow_sending_without_reply': True}, None), + ({'allow_sending_without_reply': False}, None), + ({'allow_sending_without_reply': False}, True), + ], + indirect=['default_bot'], + ) + def test_send_sticker_default_allow_sending_without_reply( + self, default_bot, chat_id, sticker, custom + ): + reply_to_message = default_bot.send_message(chat_id, 'test') + reply_to_message.delete() + if custom is not None: + message = default_bot.send_sticker( + chat_id, + sticker, + allow_sending_without_reply=custom, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + elif default_bot.defaults.allow_sending_without_reply: + message = default_bot.send_sticker( + chat_id, sticker, reply_to_message_id=reply_to_message.message_id + ) + assert message.reply_to_message is None + else: + with pytest.raises(BadRequest, match='message not found'): + default_bot.send_sticker( + chat_id, sticker, reply_to_message_id=reply_to_message.message_id + ) + def test_to_dict(self, sticker): sticker_dict = sticker.to_dict() diff --git a/tests/test_venue.py b/tests/test_venue.py index e0098fcf882..cbf6f7b3b8e 100644 --- a/tests/test_venue.py +++ b/tests/test_venue.py @@ -18,8 +18,10 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. import pytest +from flaky import flaky from telegram import Location, Venue +from telegram.error import BadRequest @pytest.fixture(scope='class') @@ -71,6 +73,41 @@ def test(url, data, **kwargs): message = bot.send_venue(chat_id, venue=venue) assert message + @flaky(3, 1) + @pytest.mark.timeout(10) + @pytest.mark.parametrize( + 'default_bot,custom', + [ + ({'allow_sending_without_reply': True}, None), + ({'allow_sending_without_reply': False}, None), + ({'allow_sending_without_reply': False}, True), + ], + indirect=['default_bot'], + ) + def test_send_venue_default_allow_sending_without_reply( + self, default_bot, chat_id, venue, custom + ): + reply_to_message = default_bot.send_message(chat_id, 'test') + reply_to_message.delete() + if custom is not None: + message = default_bot.send_venue( + chat_id, + venue=venue, + allow_sending_without_reply=custom, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + elif default_bot.defaults.allow_sending_without_reply: + message = default_bot.send_venue( + chat_id, venue=venue, reply_to_message_id=reply_to_message.message_id + ) + assert message.reply_to_message is None + else: + with pytest.raises(BadRequest, match='message not found'): + default_bot.send_venue( + chat_id, venue=venue, reply_to_message_id=reply_to_message.message_id + ) + def test_send_venue_without_required(self, bot, chat_id): with pytest.raises(ValueError, match='Either venue or latitude, longitude, address and'): bot.send_venue(chat_id=chat_id) diff --git a/tests/test_video.py b/tests/test_video.py index 6b188ae7a81..3d3deb965ab 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -22,6 +22,7 @@ from flaky import flaky from telegram import Video, TelegramError, Voice, PhotoSize +from telegram.error import BadRequest from telegram.utils.helpers import escape_markdown @@ -202,6 +203,41 @@ def test_send_video_default_parse_mode_3(self, default_bot, chat_id, video): assert message.caption == test_markdown_string assert message.caption_markdown == escape_markdown(test_markdown_string) + @flaky(3, 1) + @pytest.mark.timeout(10) + @pytest.mark.parametrize( + 'default_bot,custom', + [ + ({'allow_sending_without_reply': True}, None), + ({'allow_sending_without_reply': False}, None), + ({'allow_sending_without_reply': False}, True), + ], + indirect=['default_bot'], + ) + def test_send_video_default_allow_sending_without_reply( + self, default_bot, chat_id, video, custom + ): + reply_to_message = default_bot.send_message(chat_id, 'test') + reply_to_message.delete() + if custom is not None: + message = default_bot.send_video( + chat_id, + video, + allow_sending_without_reply=custom, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + elif default_bot.defaults.allow_sending_without_reply: + message = default_bot.send_video( + chat_id, video, reply_to_message_id=reply_to_message.message_id + ) + assert message.reply_to_message is None + else: + with pytest.raises(BadRequest, match='message not found'): + default_bot.send_video( + chat_id, video, reply_to_message_id=reply_to_message.message_id + ) + def test_de_json(self, bot): json_dict = { 'file_id': self.video_file_id, diff --git a/tests/test_videonote.py b/tests/test_videonote.py index 49431f2a1f6..a33b83895d8 100644 --- a/tests/test_videonote.py +++ b/tests/test_videonote.py @@ -22,6 +22,7 @@ from flaky import flaky from telegram import VideoNote, TelegramError, Voice, PhotoSize +from telegram.error import BadRequest @pytest.fixture(scope='function') @@ -149,6 +150,41 @@ def test_to_dict(self, video_note): assert video_note_dict['duration'] == video_note.duration assert video_note_dict['file_size'] == video_note.file_size + @flaky(3, 1) + @pytest.mark.timeout(10) + @pytest.mark.parametrize( + 'default_bot,custom', + [ + ({'allow_sending_without_reply': True}, None), + ({'allow_sending_without_reply': False}, None), + ({'allow_sending_without_reply': False}, True), + ], + indirect=['default_bot'], + ) + def test_send_video_note_default_allow_sending_without_reply( + self, default_bot, chat_id, video_note, custom + ): + reply_to_message = default_bot.send_message(chat_id, 'test') + reply_to_message.delete() + if custom is not None: + message = default_bot.send_video_note( + chat_id, + video_note, + allow_sending_without_reply=custom, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + elif default_bot.defaults.allow_sending_without_reply: + message = default_bot.send_video_note( + chat_id, video_note, reply_to_message_id=reply_to_message.message_id + ) + assert message.reply_to_message is None + else: + with pytest.raises(BadRequest, match='message not found'): + default_bot.send_video_note( + chat_id, video_note, reply_to_message_id=reply_to_message.message_id + ) + @flaky(3, 1) @pytest.mark.timeout(10) def test_error_send_empty_file(self, bot, chat_id): diff --git a/tests/test_voice.py b/tests/test_voice.py index 39628433115..8f3fce4850f 100644 --- a/tests/test_voice.py +++ b/tests/test_voice.py @@ -22,6 +22,7 @@ from flaky import flaky from telegram import Audio, Voice, TelegramError +from telegram.error import BadRequest from telegram.utils.helpers import escape_markdown @@ -162,6 +163,41 @@ def test_send_voice_default_parse_mode_3(self, default_bot, chat_id, voice): assert message.caption == test_markdown_string assert message.caption_markdown == escape_markdown(test_markdown_string) + @flaky(3, 1) + @pytest.mark.timeout(10) + @pytest.mark.parametrize( + 'default_bot,custom', + [ + ({'allow_sending_without_reply': True}, None), + ({'allow_sending_without_reply': False}, None), + ({'allow_sending_without_reply': False}, True), + ], + indirect=['default_bot'], + ) + def test_send_voice_default_allow_sending_without_reply( + self, default_bot, chat_id, voice, custom + ): + reply_to_message = default_bot.send_message(chat_id, 'test') + reply_to_message.delete() + if custom is not None: + message = default_bot.send_voice( + chat_id, + voice, + allow_sending_without_reply=custom, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + elif default_bot.defaults.allow_sending_without_reply: + message = default_bot.send_voice( + chat_id, voice, reply_to_message_id=reply_to_message.message_id + ) + assert message.reply_to_message is None + else: + with pytest.raises(BadRequest, match='message not found'): + default_bot.send_voice( + chat_id, voice, reply_to_message_id=reply_to_message.message_id + ) + def test_de_json(self, bot): json_dict = { 'file_id': self.voice_file_id,