Control the read timeout from telegram servers

refs #495
refs #364
This commit is contained in:
Noam Meltzer 2017-01-06 23:48:34 +02:00
parent 68a7d9fa1b
commit 58dddfd9c3
5 changed files with 253 additions and 130 deletions

View file

@ -158,9 +158,14 @@ class Bot(TelegramObject):
return decorator return decorator
@log @log
def getMe(self, **kwargs): def getMe(self, timeout=None, **kwargs):
"""A simple method for testing your bot's auth token. """A simple method for testing your bot's auth token.
Args:
timeout (Optional[int|float]): 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).
Returns: Returns:
:class:`telegram.User`: A :class:`telegram.User` instance representing that bot if the :class:`telegram.User`: A :class:`telegram.User` instance representing that bot if the
credentials are valid, `None` otherwise. credentials are valid, `None` otherwise.
@ -171,7 +176,7 @@ class Bot(TelegramObject):
""" """
url = '{0}/getMe'.format(self.base_url) url = '{0}/getMe'.format(self.base_url)
result = self._request.get(url) result = self._request.get(url, timeout=timeout)
self.bot = User.de_json(result, self) self.bot = User.de_json(result, self)
@ -211,8 +216,9 @@ class Bot(TelegramObject):
interface options. A JSON-serialized object for an inline interface options. A JSON-serialized object for an inline
keyboard, custom reply keyboard, instructions to remove reply keyboard, custom reply keyboard, instructions to remove reply
keyboard or to force a reply from the user. keyboard or to force a reply from the user.
timeout (Optional[float]): If this value is specified, use it as timeout (Optional[int|float]): If this value is specified, use it as the read timeout
the definitive timeout (in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -251,8 +257,9 @@ class Bot(TelegramObject):
message_id: Unique message identifier. message_id: Unique message identifier.
disable_notification (Optional[bool]): Sends the message silently. iOS users will not disable_notification (Optional[bool]): Sends the message silently. iOS users will not
receive a notification, Android users will receive a notification with no sound. receive a notification, Android users will receive a notification with no sound.
timeout (Optional[float]): If this value is specified, use it as timeout (Optional[int|float]): If this value is specified, use it as the read timeout
the definitive timeout (in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -301,8 +308,9 @@ class Bot(TelegramObject):
reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
to remove reply keyboard or to force a reply from the user. to remove reply keyboard or to force a reply from the user.
timeout (Optional[float]): If this value is specified, use it as the definitive timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
(in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -362,8 +370,9 @@ class Bot(TelegramObject):
reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
to remove reply keyboard or to force a reply from the user. to remove reply keyboard or to force a reply from the user.
timeout (Optional[float]): If this value is specified, use it as the definitive timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
(in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -417,8 +426,9 @@ class Bot(TelegramObject):
reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
to remove reply keyboard or to force a reply from the user. to remove reply keyboard or to force a reply from the user.
timeout (Optional[float]): If this value is specified, use it as the definitive timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
(in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -463,8 +473,9 @@ class Bot(TelegramObject):
reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
to remove reply keyboard or to force a reply from the user. to remove reply keyboard or to force a reply from the user.
timeout (Optional[float]): If this value is specified, use it as the definitive timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
(in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -510,8 +521,9 @@ class Bot(TelegramObject):
reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
to remove reply keyboard or to force a reply from the user. to remove reply keyboard or to force a reply from the user.
timeout (Optional[float]): If this value is specified, use it as the definitive timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
(in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
Returns: Returns:
:class:`telegram.Message`: On success, instance representing the message posted. :class:`telegram.Message`: On success, instance representing the message posted.
@ -563,8 +575,9 @@ class Bot(TelegramObject):
reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
to remove reply keyboard or to force a reply from the user. to remove reply keyboard or to force a reply from the user.
timeout (Optional[float]): If this value is specified, use it as the definitive timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
(in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -609,8 +622,9 @@ class Bot(TelegramObject):
reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
to remove reply keyboard or to force a reply from the user. to remove reply keyboard or to force a reply from the user.
timeout (Optional[float]): If this value is specified, use it as the definitive timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
(in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -658,8 +672,9 @@ class Bot(TelegramObject):
reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
to remove reply keyboard or to force a reply from the user. to remove reply keyboard or to force a reply from the user.
timeout (Optional[float]): If this value is specified, use it as the definitive timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
(in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -712,8 +727,9 @@ class Bot(TelegramObject):
reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
to remove reply keyboard or to force a reply from the user. to remove reply keyboard or to force a reply from the user.
timeout (Optional[float]): If this value is specified, use it as the definitive timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
(in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -734,7 +750,7 @@ class Bot(TelegramObject):
@log @log
@message @message
def sendGame(self, chat_id, game_short_name, **kwargs): def sendGame(self, chat_id, game_short_name, timeout=None, **kwargs):
"""Use this method to send a game. """Use this method to send a game.
Args: Args:
@ -751,8 +767,9 @@ class Bot(TelegramObject):
reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options.
A JSON-serialized object for an inline keyboard, custom reply keyboard, A JSON-serialized object for an inline keyboard, custom reply keyboard,
instructions to remove reply keyboard or to force a reply from the user. instructions to remove reply keyboard or to force a reply from the user.
timeout (Optional[float]): If this value is specified, use it as timeout (Optional[int|float]): If this value is specified, use it as the read timeout
the definitive timeout (in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
Returns: Returns:
:class:`telegram.Message`: On success, the sent message is returned. :class:`telegram.Message`: On success, the sent message is returned.
@ -769,7 +786,7 @@ class Bot(TelegramObject):
@log @log
@message @message
def sendChatAction(self, chat_id, action, **kwargs): def sendChatAction(self, chat_id, action, timeout=None, **kwargs):
"""Use this method when you need to tell the user that something is happening on the bot's """Use this method when you need to tell the user that something is happening on the bot's
side. The status is set for 5 seconds or less (when a message arrives from your bot, side. The status is set for 5 seconds or less (when a message arrives from your bot,
Telegram clients clear its typing status). Telegram clients clear its typing status).
@ -785,6 +802,9 @@ class Bot(TelegramObject):
- ChatAction.UPLOAD_AUDIO for audio files, - ChatAction.UPLOAD_AUDIO for audio files,
- ChatAction.UPLOAD_DOCUMENT for general files, - ChatAction.UPLOAD_DOCUMENT for general files,
- ChatAction.FIND_LOCATION for location data. - ChatAction.FIND_LOCATION for location data.
timeout (Optional[int|float]): 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).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
""" """
@ -826,8 +846,9 @@ class Bot(TelegramObject):
start message with the parameter switch_pm_parameter. start message with the parameter switch_pm_parameter.
switch_pm_parameter (Optional[str]): Parameter for the start message sent to the bot switch_pm_parameter (Optional[str]): Parameter for the start message sent to the bot
when user presses the switch button. when user presses the switch button.
timeout (Optional[float]): If this value is specified, use it as the definitive timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
(in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -868,8 +889,9 @@ class Bot(TelegramObject):
default, all photos are returned. default, all photos are returned.
limit (Optional[int]): Limits the number of photos to be retrieved. Values between limit (Optional[int]): Limits the number of photos to be retrieved. Values between
1-100 are accepted. Defaults to 100. 1-100 are accepted. Defaults to 100.
timeout (Optional[float]): If this value is specified, use it as the definitive timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
(in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -900,8 +922,9 @@ class Bot(TelegramObject):
Args: Args:
file_id: File identifier to get info about. file_id: File identifier to get info about.
timeout (Optional[float]): If this value is specified, use it as the definitive timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
(in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -934,8 +957,9 @@ class Bot(TelegramObject):
chat_id: Unique identifier for the target group or username of the target supergroup chat_id: Unique identifier for the target group or username of the target supergroup
(in the format @supergroupusername). (in the format @supergroupusername).
user_id: Unique identifier of the target user. user_id: Unique identifier of the target user.
timeout (Optional[float]): If this value is specified, use it as the definitive timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
(in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -963,8 +987,9 @@ class Bot(TelegramObject):
chat_id: Unique identifier for the target group or username of the target supergroup chat_id: Unique identifier for the target group or username of the target supergroup
(in the format @supergroupusername). (in the format @supergroupusername).
user_id: Unique identifier of the target user. user_id: Unique identifier of the target user.
timeout (Optional[float]): If this value is specified, use it as the definitive timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
(in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -1005,8 +1030,9 @@ class Bot(TelegramObject):
cache_time (Optional[int]): The maximum amount of time in seconds that the result of cache_time (Optional[int]): The maximum amount of time in seconds that the result of
the callback query may be cached client-side. Telegram apps will support caching the callback query may be cached client-side. Telegram apps will support caching
starting in version 3.14. Defaults to 0. starting in version 3.14. Defaults to 0.
timeout (Optional[float]): If this value is specified, use it as the definitive timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
(in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -1062,8 +1088,9 @@ class Bot(TelegramObject):
reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
to remove reply keyboard or to force a reply from the user. to remove reply keyboard or to force a reply from the user.
timeout (Optional[float]): If this value is specified, use it as the definitive timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
(in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -1115,8 +1142,9 @@ class Bot(TelegramObject):
caption (Optional[str]): New caption of the message. caption (Optional[str]): New caption of the message.
reply_markup (Optional[:class:`telegram.InlineKeyboardMarkup`]): A JSON-serialized reply_markup (Optional[:class:`telegram.InlineKeyboardMarkup`]): A JSON-serialized
object for an inline keyboard. object for an inline keyboard.
timeout (Optional[float]): If this value is specified, use it as the definitive timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
(in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -1169,8 +1197,9 @@ class Bot(TelegramObject):
specified. Identifier of the inline message. specified. Identifier of the inline message.
reply_markup (Optional[:class:`telegram.InlineKeyboardMarkup`]): A JSON-serialized reply_markup (Optional[:class:`telegram.InlineKeyboardMarkup`]): A JSON-serialized
object for an inline keyboard. object for an inline keyboard.
timeout (Optional[float]): If this value is specified, use it as the definitive timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
(in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -1201,7 +1230,13 @@ class Bot(TelegramObject):
return url, data return url, data
@log @log
def getUpdates(self, offset=None, limit=100, timeout=0, network_delay=5., **kwargs): def getUpdates(self,
offset=None,
limit=100,
timeout=0,
network_delay=None,
read_latency=2.,
**kwargs):
"""Use this method to receive incoming updates using long polling. """Use this method to receive incoming updates using long polling.
Args: Args:
@ -1213,13 +1248,25 @@ class Bot(TelegramObject):
limit (Optional[int]): Limits the number of updates to be retrieved. Values between limit (Optional[int]): Limits the number of updates to be retrieved. Values between
1-100 are accepted. Defaults to 100. 1-100 are accepted. Defaults to 100.
timeout (Optional[int]): Timeout in seconds for long polling. Defaults to 0, i.e. usual timeout (Optional[int]): Timeout in seconds for long polling. Defaults to 0, i.e. usual
short polling. short polling. Be careful not to set this timeout too high, as the connection might
network_delay (Optional[float]): Additional timeout in seconds to allow the response be dropped and there's no way of knowing it immediately (so most likely the failure
from Telegram servers. This should cover network latency around the globe, SSL will be detected after the timeout had passed).
handshake and slowness of the Telegram servers (which unfortunately happens a lot network_delay: Deprecated. Will be honoured as `read_latency` for a while but will be
recently - 2016-05-28). Defaults to 5. removed in the future.
read_latency (Optional[float|int]): Grace time in seconds for receiving the reply from
server. Will be added to the `timeout` value and used as the read timeout from
server (Default: 2).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Notes:
The main problem with long polling is that a connection will be dropped and we won't
be getting the notification in time for it. For that, we need to use long polling, but
not too long as well read latency which is short, but not too short.
Long polling improves performance, but if it's too long and the connection is dropped
on many cases we won't know the connection dropped before the long polling timeout and
the read latency time had passed. If you experience connection timeouts, you should
tune these settings.
Returns: Returns:
list[:class:`telegram.Update`] list[:class:`telegram.Update`]
@ -1229,6 +1276,10 @@ class Bot(TelegramObject):
""" """
url = '{0}/getUpdates'.format(self.base_url) url = '{0}/getUpdates'.format(self.base_url)
if network_delay is not None:
warnings.warn('network_delay is deprecated, use read_latency instead')
read_latency = network_delay
data = {'timeout': timeout} data = {'timeout': timeout}
if offset: if offset:
@ -1236,9 +1287,12 @@ class Bot(TelegramObject):
if limit: if limit:
data['limit'] = limit data['limit'] = limit
urlopen_timeout = timeout + network_delay # Ideally we'd use an aggressive read timeout for the polling. However,
# * Short polling should return within 2 seconds.
result = self._request.post(url, data, timeout=urlopen_timeout) # * Long polling poses a different problem: the connection might have been dropped while
# waiting for the server to return and there's no way of knowing the connection had been
# dropped in real time.
result = self._request.post(url, data, timeout=float(read_latency) + float(timeout))
if result: if result:
self.logger.debug('Getting updates: %s', [u['update_id'] for u in result]) self.logger.debug('Getting updates: %s', [u['update_id'] for u in result])
@ -1256,9 +1310,10 @@ class Bot(TelegramObject):
Args: Args:
webhook_url: HTTPS url to send updates to. Use an empty string to remove webhook webhook_url: HTTPS url to send updates to. Use an empty string to remove webhook
integration integration.
timeout (Optional[float]): If this value is specified, use it as timeout (Optional[int|float]): If this value is specified, use it as the read timeout
the definitive timeout (in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -1288,8 +1343,9 @@ class Bot(TelegramObject):
Args: Args:
chat_id: Unique identifier for the target chat or username of the target channel (in chat_id: Unique identifier for the target chat or username of the target channel (in
the format @channelusername). the format @channelusername).
timeout (Optional[float]): If this value is specified, use it as the definitive timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
(in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -1315,8 +1371,9 @@ class Bot(TelegramObject):
Args: Args:
chat_id: Unique identifier for the target chat or username of the target channel (in chat_id: Unique identifier for the target chat or username of the target channel (in
the format @channelusername). the format @channelusername).
timeout (Optional[float]): If this value is specified, use it as the definitive timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
(in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -1345,8 +1402,9 @@ class Bot(TelegramObject):
Args: Args:
chat_id: Unique identifier for the target chat or username of the target channel (in chat_id: Unique identifier for the target chat or username of the target channel (in
the format @channelusername). the format @channelusername).
timeout (Optional[float]): If this value is specified, use it as the definitive timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
(in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -1371,8 +1429,9 @@ class Bot(TelegramObject):
Args: Args:
chat_id: Unique identifier for the target chat or username of the target channel (in chat_id: Unique identifier for the target chat or username of the target channel (in
the format @channelusername). the format @channelusername).
timeout (Optional[float]): If this value is specified, use it as the definitive timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
(in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -1398,8 +1457,9 @@ class Bot(TelegramObject):
chat_id: Unique identifier for the target chat or username of the target channel (in chat_id: Unique identifier for the target chat or username of the target channel (in
the format @channelusername). the format @channelusername).
user_id: Unique identifier of the target user. user_id: Unique identifier of the target user.
timeout (Optional[float]): If this value is specified, use it as the definitive timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
(in seconds) for urlopen() operations. from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -1417,11 +1477,16 @@ class Bot(TelegramObject):
return ChatMember.de_json(result, self) return ChatMember.de_json(result, self)
def getWebhookInfo(self, **kwargs): def getWebhookInfo(self, timeout=None, **kwargs):
"""Use this method to get current webhook status. """Use this method to get current webhook status.
If the bot is using getUpdates, will return an object with the url field empty. If the bot is using getUpdates, will return an object with the url field empty.
Args:
timeout (Optional[int|float]): 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).
Returns: Returns:
:class: `telegram.WebhookInfo` :class: `telegram.WebhookInfo`
@ -1430,7 +1495,7 @@ class Bot(TelegramObject):
data = {} data = {}
result = self._request.post(url, data, timeout=kwargs.get('timeout')) result = self._request.post(url, data, timeout=timeout)
return WebhookInfo.de_json(result, self) return WebhookInfo.de_json(result, self)
@ -1443,6 +1508,7 @@ class Bot(TelegramObject):
edit_message=None, edit_message=None,
force=None, force=None,
disable_edit_message=None, disable_edit_message=None,
timeout=None,
**kwargs): **kwargs):
"""Use this method to set the score of the specified user in a game. """Use this method to set the score of the specified user in a game.
@ -1462,10 +1528,9 @@ class Bot(TelegramObject):
automatically edited to include the current scoreboard. automatically edited to include the current scoreboard.
edit_message (Optional[bool]): Deprecated. Has the opposite logic for edit_message (Optional[bool]): Deprecated. Has the opposite logic for
`disable_edit_message`. `disable_edit_message`.
timeout (Optional[int|float]): If this value is specified, use it as the read timeout
Keyword Args: from the server (instead of the one specified during creation of the connection
timeout (Optional[float]): If this value is specified, use it as the definitive timeout pool).
(in seconds) for urlopen() operations.
Returns: Returns:
:class:`telegram.Message` or True: The edited message, or if the :class:`telegram.Message` or True: The edited message, or if the
@ -1493,7 +1558,7 @@ class Bot(TelegramObject):
else: else:
warnings.warn('edit_message is ignored when disable_edit_message is used') warnings.warn('edit_message is ignored when disable_edit_message is used')
result = self._request.post(url, data, timeout=kwargs.get('timeout')) result = self._request.post(url, data, timeout=timeout)
if result is True: if result is True:
return result return result
else: else:
@ -1504,9 +1569,15 @@ class Bot(TelegramObject):
chat_id=None, chat_id=None,
message_id=None, message_id=None,
inline_message_id=None, inline_message_id=None,
timeout=None,
**kwargs): **kwargs):
"""Use this method to get data for high score tables. """Use this method to get data for high score tables.
Args:
timeout (Optional[int|float]): 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).
Returns: Returns:
list[:class:`telegram.GameHighScore`]: Scores of the specified user and several of his list[:class:`telegram.GameHighScore`]: Scores of the specified user and several of his
neighbors in a game. neighbors in a game.
@ -1523,7 +1594,7 @@ class Bot(TelegramObject):
if inline_message_id: if inline_message_id:
data['inline_message_id'] = inline_message_id data['inline_message_id'] = inline_message_id
result = self._request.post(url, data, timeout=kwargs.get('timeout')) result = self._request.post(url, data, timeout=timeout)
return [GameHighScore.de_json(hs, self) for hs in result] return [GameHighScore.de_json(hs, self) for hs in result]

View file

@ -22,6 +22,7 @@ Telegram bots intuitive."""
import logging import logging
import os import os
import ssl import ssl
import warnings
from threading import Thread, Lock, current_thread, Event from threading import Thread, Lock, current_thread, Event
from time import sleep from time import sleep
import subprocess import subprocess
@ -61,6 +62,8 @@ class Updater(object):
bot (Optional[Bot]): A pre-initialized bot instance. If a pre-initizlied bot is used, it is bot (Optional[Bot]): A pre-initialized bot instance. If a pre-initizlied bot is used, it is
the user's responsibility to create it using a `Request` instance with a large enough the user's responsibility to create it using a `Request` instance with a large enough
connection pool. connection pool.
request_kwargs (Optional[dict]): Keyword args to control the creation of a request object
(ignored if `bot` argument is used).
Raises: Raises:
ValueError: If both `token` and `bot` are passed or none of them. ValueError: If both `token` and `bot` are passed or none of them.
@ -68,7 +71,7 @@ class Updater(object):
""" """
_request = None _request = None
def __init__(self, token=None, base_url=None, workers=4, bot=None): def __init__(self, token=None, base_url=None, workers=4, bot=None, request_kwargs=None):
if (token is None) and (bot is None): if (token is None) and (bot is None):
raise ValueError('`token` or `bot` must be passed') raise ValueError('`token` or `bot` must be passed')
if (token is not None) and (bot is not None): if (token is not None) and (bot is not None):
@ -83,7 +86,11 @@ class Updater(object):
# * 1 for polling Updater (even if webhook is used, we can spare a connection) # * 1 for polling Updater (even if webhook is used, we can spare a connection)
# * 1 for JobQueue # * 1 for JobQueue
# * 1 for main thread # * 1 for main thread
self._request = Request(con_pool_size=workers + 4) if request_kwargs is None:
request_kwargs = {}
if 'con_pool_size' not in request_kwargs:
request_kwargs['con_pool_size'] = workers + 4
self._request = Request(**request_kwargs)
self.bot = Bot(token, base_url, request=self._request) self.bot = Bot(token, base_url, request=self._request)
self.update_queue = Queue() self.update_queue = Queue()
self.job_queue = JobQueue(self.bot) self.job_queue = JobQueue(self.bot)
@ -122,9 +129,10 @@ class Updater(object):
def start_polling(self, def start_polling(self,
poll_interval=0.0, poll_interval=0.0,
timeout=10, timeout=10,
network_delay=5., network_delay=None,
clean=False, clean=False,
bootstrap_retries=0): bootstrap_retries=0,
read_latency=2.):
""" """
Starts polling updates from Telegram. Starts polling updates from Telegram.
@ -134,7 +142,8 @@ class Updater(object):
timeout (Optional[float]): Passed to Bot.getUpdates timeout (Optional[float]): Passed to Bot.getUpdates
network_delay (Optional[float]): Passed to Bot.getUpdates network_delay: Deprecated. Will be honoured as `read_latency` for a while but will be
removed in the future.
clean (Optional[bool]): Whether to clean any pending updates on Telegram servers before clean (Optional[bool]): Whether to clean any pending updates on Telegram servers before
actually starting to poll. Default is False. actually starting to poll. Default is False.
@ -143,13 +152,22 @@ class Updater(object):
will retry on failures on the Telegram server. will retry on failures on the Telegram server.
| < 0 - retry indefinitely | < 0 - retry indefinitely
| 0 - no retries (default) | 0 - no retries (default)
| > 0 - retry up to X times | > 0 - retry up to X times
read_latency (Optional[float|int]): Grace time in seconds for receiving the reply from
server. Will be added to the `timeout` value and used as the read timeout from
server (Default: 2).
Returns: Returns:
Queue: The update queue that can be filled from the main thread Queue: The update queue that can be filled from the main thread
""" """
if network_delay is not None:
warnings.warn('network_delay is deprecated, use read_latency instead')
read_latency = network_delay
with self.__lock: with self.__lock:
if not self.running: if not self.running:
self.running = True self.running = True
@ -158,7 +176,7 @@ class Updater(object):
self.job_queue.start() self.job_queue.start()
self._init_thread(self.dispatcher.start, "dispatcher") self._init_thread(self.dispatcher.start, "dispatcher")
self._init_thread(self._start_polling, "updater", poll_interval, timeout, self._init_thread(self._start_polling, "updater", poll_interval, timeout,
network_delay, bootstrap_retries, clean) read_latency, bootstrap_retries, clean)
# Return the update queue so the main thread can insert updates # Return the update queue so the main thread can insert updates
return self.update_queue return self.update_queue
@ -215,7 +233,7 @@ class Updater(object):
# Return the update queue so the main thread can insert updates # Return the update queue so the main thread can insert updates
return self.update_queue return self.update_queue
def _start_polling(self, poll_interval, timeout, network_delay, bootstrap_retries, clean): def _start_polling(self, poll_interval, timeout, read_latency, bootstrap_retries, clean):
""" """
Thread target of thread 'updater'. Runs in background, pulls Thread target of thread 'updater'. Runs in background, pulls
updates from Telegram and inserts them in the update queue of the updates from Telegram and inserts them in the update queue of the
@ -230,7 +248,7 @@ class Updater(object):
while self.running: while self.running:
try: try:
updates = self.bot.getUpdates( updates = self.bot.getUpdates(
self.last_update_id, timeout=timeout, network_delay=network_delay) self.last_update_id, timeout=timeout, read_latency=read_latency)
except RetryAfter as e: except RetryAfter as e:
self.logger.info(str(e)) self.logger.info(str(e))
cur_interval = 0.5 + e.retry_after cur_interval = 0.5 + e.retry_after

View file

@ -65,20 +65,24 @@ class File(TelegramObject):
return File(bot=bot, **data) return File(bot=bot, **data)
def download(self, custom_path=None, out=None): def download(self, custom_path=None, out=None, timeout=None):
""" """
Download this file. By default, the file is saved in the current working directory with its Download this file. By default, the file is saved in the current working directory with its
original filename as reported by Telegram. If a ``custom_path`` is supplied, it will be original filename as reported by Telegram. If a ``custom_path`` is supplied, it will be
saved to that path instead. If ``out`` is defined, the file contents will be saved to that saved to that path instead. If ``out`` is defined, the file contents will be saved to that
object using the ``out.write`` method. ``custom_path`` and ``out`` are mutually exclusive. object using the ``out.write`` method. ``custom_path`` and ``out`` are mutually exclusive.
Keyword Args: Args:
custom_path (Optional[str]): Custom path. custom_path (Optional[str]): Custom path.
out (Optional[object]): A file-like object. Must be opened in binary mode, if out (Optional[object]): A file-like object. Must be opened in binary mode, if
applicable. applicable.
timeout (Optional[int|float]): 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).
Raises: Raises:
ValueError: If both ``custom_path`` and ``out`` are passed. ValueError: If both ``custom_path`` and ``out`` are passed.
""" """
if custom_path is not None and out is not None: if custom_path is not None and out is not None:
@ -96,4 +100,4 @@ class File(TelegramObject):
else: else:
filename = basename(url) filename = basename(url)
self.bot.request.download(url, filename) self.bot.request.download(url, filename, timeout=timeout)

View file

@ -17,18 +17,19 @@
# You should have received a copy of the GNU Lesser Public License # You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/]. # along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains methods to make POST and GET requests""" """This module contains methods to make POST and GET requests"""
import os
import socket
import logging
try: try:
import ujson as json import ujson as json
except ImportError: except ImportError:
import json import json
import os
import socket
import logging
import certifi import certifi
import urllib3 import urllib3
from urllib3.connection import HTTPConnection from urllib3.connection import HTTPConnection
from urllib3.util.timeout import Timeout
from telegram import (InputFile, TelegramError) from telegram import (InputFile, TelegramError)
from telegram.error import (Unauthorized, NetworkError, TimedOut, BadRequest, ChatMigrated, from telegram.error import (Unauthorized, NetworkError, TimedOut, BadRequest, ChatMigrated,
@ -43,23 +44,40 @@ class Request(object):
telegram servers. telegram servers.
Args: Args:
con_pool_size (int): Number of connections to keep in the connection pool.
proxy_url (str): The URL to the proxy server. For example: `http://127.0.0.1:3128`. proxy_url (str): The URL to the proxy server. For example: `http://127.0.0.1:3128`.
urllib3_proxy_kwargs (dict): Arbitrary arguments passed as-is to `urllib3.ProxyManager`. urllib3_proxy_kwargs (dict): Arbitrary arguments passed as-is to `urllib3.ProxyManager`.
This value will be ignored if proxy_url is not set. This value will be ignored if proxy_url is not set.
connect_timeout (int|float): The maximum amount of time (in seconds) to wait for a
connection attempt to a server to succeed. None will set an infinite timeout for
connection attempts. (default: 5.)
read_timeout (int|float): The maximum amount of time (in seconds) to wait between
consecutive read operations for a response from the server. None will set an infinite
timeout. This value is usually overridden by the various ``telegram.Bot`` methods.
(default: 5.)
""" """
def __init__(self, con_pool_size=1, proxy_url=None, urllib3_proxy_kwargs=None): def __init__(self,
con_pool_size=1,
proxy_url=None,
urllib3_proxy_kwargs=None,
connect_timeout=5.,
read_timeout=5.):
if urllib3_proxy_kwargs is None: if urllib3_proxy_kwargs is None:
urllib3_proxy_kwargs = dict() urllib3_proxy_kwargs = dict()
self._connect_timeout = connect_timeout
kwargs = dict( kwargs = dict(
maxsize=con_pool_size, maxsize=con_pool_size,
cert_reqs='CERT_REQUIRED', cert_reqs='CERT_REQUIRED',
ca_certs=certifi.where(), ca_certs=certifi.where(),
socket_options=HTTPConnection.default_socket_options + [ socket_options=HTTPConnection.default_socket_options + [
(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1),
]) ],
timeout=urllib3.Timeout(
connect=self._connect_timeout, read=read_timeout),)
# Set a proxy according to the following order: # Set a proxy according to the following order:
# * proxy defined in proxy_url (+ urllib3_proxy_kwargs) # * proxy defined in proxy_url (+ urllib3_proxy_kwargs)
@ -163,33 +181,14 @@ class Request(object):
else: else:
raise NetworkError('{0} ({1})'.format(message, resp.status)) raise NetworkError('{0} ({1})'.format(message, resp.status))
def get(self, url): def get(self, url, timeout=None):
"""Request an URL. """Request an URL.
Args: Args:
url: url (str): The web location we want to retrieve.
The web location we want to retrieve. timeout (Optional[int|float]): If this value is specified, use it as the read timeout
from the server (instead of the one specified during creation of the connection
Returns: pool).
A JSON object.
"""
result = self._request_wrapper('GET', url)
return self._parse(result)
def post(self, url, data, timeout=None):
"""Request an URL.
Args:
url:
The web location we want to retrieve.
data:
A dict of (str, unicode) key/value pairs.
timeout:
float. If this value is specified, use it as the definitive timeout (in
seconds) for urlopen() operations. [Optional]
Notes:
If neither `timeout` nor `data['timeout']` is specified. The underlying
defaults are used.
Returns: Returns:
A JSON object. A JSON object.
@ -198,7 +197,28 @@ class Request(object):
urlopen_kwargs = {} urlopen_kwargs = {}
if timeout is not None: if timeout is not None:
urlopen_kwargs['timeout'] = timeout urlopen_kwargs['timeout'] = Timeout(read=timeout, connect=self._connect_timeout)
result = self._request_wrapper('GET', url, **urlopen_kwargs)
return self._parse(result)
def post(self, url, data, timeout=None):
"""Request an URL.
Args:
url (str): The web location we want to retrieve.
data (dict[str, str]): A dict of key/value pairs. Note: On py2.7 value is unicode.
timeout (Optional[int|float]): 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).
Returns:
A JSON object.
"""
urlopen_kwargs = {}
if timeout is not None:
urlopen_kwargs['timeout'] = Timeout(read=timeout, connect=self._connect_timeout)
if InputFile.is_inputfile(data): if InputFile.is_inputfile(data):
data = InputFile(data) data = InputFile(data)
@ -215,24 +235,34 @@ class Request(object):
return self._parse(result) return self._parse(result)
def retrieve(self, url): def retrieve(self, url, timeout=None):
"""Retrieve the contents of a file by its URL. """Retrieve the contents of a file by its URL.
Args:
url:
The web location we want to retrieve.
"""
return self._request_wrapper('GET', url)
def download(self, url, filename): Args:
url (str): The web location we want to retrieve.
timeout (Optional[int|float]): 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).
"""
urlopen_kwargs = {}
if timeout is not None:
urlopen_kwargs['timeout'] = Timeout(read=timeout, connect=self._connect_timeout)
return self._request_wrapper('GET', url, **urlopen_kwargs)
def download(self, url, filename, timeout=None):
"""Download a file by its URL. """Download a file by its URL.
Args: Args:
url: url (str): The web location we want to retrieve.
The web location we want to retrieve. timeout (Optional[int|float]): 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).
filename: filename:
The filename within the path to download the file. The filename within the path to download the file.
""" """
buf = self.retrieve(url) buf = self.retrieve(url, timeout=timeout)
with open(filename, 'wb') as fobj: with open(filename, 'wb') as fobj:
fobj.write(buf) fobj.write(buf)

View file

@ -809,7 +809,7 @@ class MockBot(object):
self.bootstrap_attempts += 1 self.bootstrap_attempts += 1
raise self.bootstrap_err raise self.bootstrap_err
def getUpdates(self, offset=None, limit=100, timeout=0, network_delay=2.): def getUpdates(self, offset=None, limit=100, timeout=0, network_delay=None, read_latency=2.):
if self.raise_error: if self.raise_error:
raise TelegramError('Test Error 2') raise TelegramError('Test Error 2')