diff --git a/telegram/bot.py b/telegram/bot.py index 71b88cd08..1cb2c8b5b 100644 --- a/telegram/bot.py +++ b/telegram/bot.py @@ -158,9 +158,14 @@ class Bot(TelegramObject): return decorator @log - def getMe(self, **kwargs): + def getMe(self, timeout=None, **kwargs): """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: :class:`telegram.User`: A :class:`telegram.User` instance representing that bot if the credentials are valid, `None` otherwise. @@ -171,7 +176,7 @@ class Bot(TelegramObject): """ 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) @@ -211,8 +216,9 @@ class Bot(TelegramObject): 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. - timeout (Optional[float]): If this value is specified, use it as - the definitive timeout (in seconds) for urlopen() operations. + 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. Returns: @@ -251,8 +257,9 @@ class Bot(TelegramObject): message_id: Unique message identifier. disable_notification (Optional[bool]): Sends the message silently. iOS users will not receive a notification, Android users will receive a notification with no sound. - timeout (Optional[float]): If this value is specified, use it as - the definitive timeout (in seconds) for urlopen() operations. + 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. Returns: @@ -301,8 +308,9 @@ class Bot(TelegramObject): reply_markup (Optional[:class:`telegram.ReplyMarkup`]): 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. - timeout (Optional[float]): If this value is specified, use it as the definitive timeout - (in seconds) for urlopen() operations. + 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. Returns: @@ -362,8 +370,9 @@ class Bot(TelegramObject): reply_markup (Optional[:class:`telegram.ReplyMarkup`]): 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. - timeout (Optional[float]): If this value is specified, use it as the definitive timeout - (in seconds) for urlopen() operations. + 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. Returns: @@ -417,8 +426,9 @@ class Bot(TelegramObject): reply_markup (Optional[:class:`telegram.ReplyMarkup`]): 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. - timeout (Optional[float]): If this value is specified, use it as the definitive timeout - (in seconds) for urlopen() operations. + 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. Returns: @@ -463,8 +473,9 @@ class Bot(TelegramObject): reply_markup (Optional[:class:`telegram.ReplyMarkup`]): 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. - timeout (Optional[float]): If this value is specified, use it as the definitive timeout - (in seconds) for urlopen() operations. + 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. Returns: @@ -510,8 +521,9 @@ class Bot(TelegramObject): reply_markup (Optional[:class:`telegram.ReplyMarkup`]): 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. - timeout (Optional[float]): If this value is specified, use it as the definitive timeout - (in seconds) for urlopen() operations. + 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: :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 JSON-serialized object for an inline keyboard, custom reply keyboard, instructions 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 - (in seconds) for urlopen() operations. + 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. Returns: @@ -609,8 +622,9 @@ class Bot(TelegramObject): reply_markup (Optional[:class:`telegram.ReplyMarkup`]): 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. - timeout (Optional[float]): If this value is specified, use it as the definitive timeout - (in seconds) for urlopen() operations. + 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. Returns: @@ -658,8 +672,9 @@ class Bot(TelegramObject): reply_markup (Optional[:class:`telegram.ReplyMarkup`]): 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. - timeout (Optional[float]): If this value is specified, use it as the definitive timeout - (in seconds) for urlopen() operations. + 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. Returns: @@ -712,8 +727,9 @@ class Bot(TelegramObject): reply_markup (Optional[:class:`telegram.ReplyMarkup`]): 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. - timeout (Optional[float]): If this value is specified, use it as the definitive timeout - (in seconds) for urlopen() operations. + 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. Returns: @@ -734,7 +750,7 @@ class Bot(TelegramObject): @log @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. Args: @@ -751,8 +767,9 @@ class Bot(TelegramObject): reply_markup (Optional[:class:`telegram.ReplyMarkup`]): 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. - timeout (Optional[float]): If this value is specified, use it as - the definitive timeout (in seconds) for urlopen() operations. + 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: :class:`telegram.Message`: On success, the sent message is returned. @@ -769,7 +786,7 @@ class Bot(TelegramObject): @log @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 side. The status is set for 5 seconds or less (when a message arrives from your bot, Telegram clients clear its typing status). @@ -785,6 +802,9 @@ class Bot(TelegramObject): - ChatAction.UPLOAD_AUDIO for audio files, - ChatAction.UPLOAD_DOCUMENT for general files, - 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. """ @@ -826,8 +846,9 @@ class Bot(TelegramObject): start message with the parameter switch_pm_parameter. switch_pm_parameter (Optional[str]): Parameter for the start message sent to the bot when user presses the switch button. - timeout (Optional[float]): If this value is specified, use it as the definitive timeout - (in seconds) for urlopen() operations. + 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. Returns: @@ -868,8 +889,9 @@ class Bot(TelegramObject): default, all photos are returned. limit (Optional[int]): Limits the number of photos to be retrieved. Values between 1-100 are accepted. Defaults to 100. - timeout (Optional[float]): If this value is specified, use it as the definitive timeout - (in seconds) for urlopen() operations. + 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. Returns: @@ -900,8 +922,9 @@ class Bot(TelegramObject): Args: file_id: File identifier to get info about. - timeout (Optional[float]): If this value is specified, use it as the definitive timeout - (in seconds) for urlopen() operations. + 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. Returns: @@ -934,8 +957,9 @@ class Bot(TelegramObject): chat_id: Unique identifier for the target group or username of the target supergroup (in the format @supergroupusername). user_id: Unique identifier of the target user. - timeout (Optional[float]): If this value is specified, use it as the definitive timeout - (in seconds) for urlopen() operations. + 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. Returns: @@ -963,8 +987,9 @@ class Bot(TelegramObject): chat_id: Unique identifier for the target group or username of the target supergroup (in the format @supergroupusername). user_id: Unique identifier of the target user. - timeout (Optional[float]): If this value is specified, use it as the definitive timeout - (in seconds) for urlopen() operations. + 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. Returns: @@ -1005,8 +1030,9 @@ class Bot(TelegramObject): 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 starting in version 3.14. Defaults to 0. - timeout (Optional[float]): If this value is specified, use it as the definitive timeout - (in seconds) for urlopen() operations. + 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. Returns: @@ -1062,8 +1088,9 @@ class Bot(TelegramObject): reply_markup (Optional[:class:`telegram.ReplyMarkup`]): 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. - timeout (Optional[float]): If this value is specified, use it as the definitive timeout - (in seconds) for urlopen() operations. + 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. Returns: @@ -1115,8 +1142,9 @@ class Bot(TelegramObject): caption (Optional[str]): New caption of the message. reply_markup (Optional[:class:`telegram.InlineKeyboardMarkup`]): A JSON-serialized object for an inline keyboard. - timeout (Optional[float]): If this value is specified, use it as the definitive timeout - (in seconds) for urlopen() operations. + 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. Returns: @@ -1169,8 +1197,9 @@ class Bot(TelegramObject): specified. Identifier of the inline message. reply_markup (Optional[:class:`telegram.InlineKeyboardMarkup`]): A JSON-serialized object for an inline keyboard. - timeout (Optional[float]): If this value is specified, use it as the definitive timeout - (in seconds) for urlopen() operations. + 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. Returns: @@ -1201,7 +1230,13 @@ class Bot(TelegramObject): return url, data @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. Args: @@ -1213,13 +1248,25 @@ class Bot(TelegramObject): limit (Optional[int]): Limits the number of updates to be retrieved. Values between 1-100 are accepted. Defaults to 100. timeout (Optional[int]): Timeout in seconds for long polling. Defaults to 0, i.e. usual - short polling. - network_delay (Optional[float]): Additional timeout in seconds to allow the response - from Telegram servers. This should cover network latency around the globe, SSL - handshake and slowness of the Telegram servers (which unfortunately happens a lot - recently - 2016-05-28). Defaults to 5. + short polling. Be careful not to set this timeout too high, as the connection might + be dropped and there's no way of knowing it immediately (so most likely the failure + will be detected after the timeout had passed). + network_delay: Deprecated. Will be honoured as `read_latency` for a while but will be + 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. + 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: list[:class:`telegram.Update`] @@ -1229,6 +1276,10 @@ class Bot(TelegramObject): """ 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} if offset: @@ -1236,9 +1287,12 @@ class Bot(TelegramObject): if limit: data['limit'] = limit - urlopen_timeout = timeout + network_delay - - result = self._request.post(url, data, timeout=urlopen_timeout) + # Ideally we'd use an aggressive read timeout for the polling. However, + # * Short polling should return within 2 seconds. + # * 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: self.logger.debug('Getting updates: %s', [u['update_id'] for u in result]) @@ -1256,9 +1310,10 @@ class Bot(TelegramObject): Args: webhook_url: HTTPS url to send updates to. Use an empty string to remove webhook - integration - timeout (Optional[float]): If this value is specified, use it as - the definitive timeout (in seconds) for urlopen() operations. + integration. + 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. Returns: @@ -1288,8 +1343,9 @@ class Bot(TelegramObject): Args: chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername). - timeout (Optional[float]): If this value is specified, use it as the definitive timeout - (in seconds) for urlopen() operations. + 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. Returns: @@ -1315,8 +1371,9 @@ class Bot(TelegramObject): Args: chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername). - timeout (Optional[float]): If this value is specified, use it as the definitive timeout - (in seconds) for urlopen() operations. + 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. Returns: @@ -1345,8 +1402,9 @@ class Bot(TelegramObject): Args: chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername). - timeout (Optional[float]): If this value is specified, use it as the definitive timeout - (in seconds) for urlopen() operations. + 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. Returns: @@ -1371,8 +1429,9 @@ class Bot(TelegramObject): Args: chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername). - timeout (Optional[float]): If this value is specified, use it as the definitive timeout - (in seconds) for urlopen() operations. + 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. Returns: @@ -1398,8 +1457,9 @@ class Bot(TelegramObject): chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername). user_id: Unique identifier of the target user. - timeout (Optional[float]): If this value is specified, use it as the definitive timeout - (in seconds) for urlopen() operations. + 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. Returns: @@ -1417,11 +1477,16 @@ class Bot(TelegramObject): return ChatMember.de_json(result, self) - def getWebhookInfo(self, **kwargs): + def getWebhookInfo(self, timeout=None, **kwargs): """Use this method to get current webhook status. 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: :class: `telegram.WebhookInfo` @@ -1430,7 +1495,7 @@ class Bot(TelegramObject): 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) @@ -1443,6 +1508,7 @@ class Bot(TelegramObject): edit_message=None, force=None, disable_edit_message=None, + timeout=None, **kwargs): """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. edit_message (Optional[bool]): Deprecated. Has the opposite logic for `disable_edit_message`. - - Keyword Args: - timeout (Optional[float]): If this value is specified, use it as the definitive timeout - (in seconds) for urlopen() operations. + 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: :class:`telegram.Message` or True: The edited message, or if the @@ -1493,7 +1558,7 @@ class Bot(TelegramObject): else: 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: return result else: @@ -1504,9 +1569,15 @@ class Bot(TelegramObject): chat_id=None, message_id=None, inline_message_id=None, + timeout=None, **kwargs): """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: list[:class:`telegram.GameHighScore`]: Scores of the specified user and several of his neighbors in a game. @@ -1523,7 +1594,7 @@ class Bot(TelegramObject): if 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] diff --git a/telegram/ext/updater.py b/telegram/ext/updater.py index c64bad23a..5d3edfb06 100644 --- a/telegram/ext/updater.py +++ b/telegram/ext/updater.py @@ -22,6 +22,7 @@ Telegram bots intuitive.""" import logging import os import ssl +import warnings from threading import Thread, Lock, current_thread, Event from time import sleep 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 the user's responsibility to create it using a `Request` instance with a large enough connection pool. + request_kwargs (Optional[dict]): Keyword args to control the creation of a request object + (ignored if `bot` argument is used). Raises: ValueError: If both `token` and `bot` are passed or none of them. @@ -68,7 +71,7 @@ class Updater(object): """ _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): raise ValueError('`token` or `bot` must be passed') 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 JobQueue # * 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.update_queue = Queue() self.job_queue = JobQueue(self.bot) @@ -122,9 +129,10 @@ class Updater(object): def start_polling(self, poll_interval=0.0, timeout=10, - network_delay=5., + network_delay=None, clean=False, - bootstrap_retries=0): + bootstrap_retries=0, + read_latency=2.): """ Starts polling updates from Telegram. @@ -134,7 +142,8 @@ class Updater(object): 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 actually starting to poll. Default is False. @@ -143,13 +152,22 @@ class Updater(object): will retry on failures on the Telegram server. | < 0 - retry indefinitely - | 0 - no retries (default) + | 0 - no retries (default) | > 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: 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: if not self.running: self.running = True @@ -158,7 +176,7 @@ class Updater(object): self.job_queue.start() self._init_thread(self.dispatcher.start, "dispatcher") 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 self.update_queue @@ -215,7 +233,7 @@ class Updater(object): # Return the update queue so the main thread can insert updates 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 updates from Telegram and inserts them in the update queue of the @@ -230,7 +248,7 @@ class Updater(object): while self.running: try: 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: self.logger.info(str(e)) cur_interval = 0.5 + e.retry_after diff --git a/telegram/file.py b/telegram/file.py index e0000d440..874cb4491 100644 --- a/telegram/file.py +++ b/telegram/file.py @@ -65,20 +65,24 @@ class File(TelegramObject): 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 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 object using the ``out.write`` method. ``custom_path`` and ``out`` are mutually exclusive. - Keyword Args: + Args: custom_path (Optional[str]): Custom path. out (Optional[object]): A file-like object. Must be opened in binary mode, if 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: ValueError: If both ``custom_path`` and ``out`` are passed. + """ if custom_path is not None and out is not None: @@ -96,4 +100,4 @@ class File(TelegramObject): else: filename = basename(url) - self.bot.request.download(url, filename) + self.bot.request.download(url, filename, timeout=timeout) diff --git a/telegram/utils/request.py b/telegram/utils/request.py index 18f3317ff..f25c82982 100644 --- a/telegram/utils/request.py +++ b/telegram/utils/request.py @@ -17,18 +17,19 @@ # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains methods to make POST and GET requests""" +import os +import socket +import logging try: import ujson as json except ImportError: import json -import os -import socket -import logging import certifi import urllib3 from urllib3.connection import HTTPConnection +from urllib3.util.timeout import Timeout from telegram import (InputFile, TelegramError) from telegram.error import (Unauthorized, NetworkError, TimedOut, BadRequest, ChatMigrated, @@ -43,23 +44,40 @@ class Request(object): telegram servers. 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`. urllib3_proxy_kwargs (dict): Arbitrary arguments passed as-is to `urllib3.ProxyManager`. 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: urllib3_proxy_kwargs = dict() + self._connect_timeout = connect_timeout + kwargs = dict( maxsize=con_pool_size, cert_reqs='CERT_REQUIRED', ca_certs=certifi.where(), socket_options=HTTPConnection.default_socket_options + [ (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: # * proxy defined in proxy_url (+ urllib3_proxy_kwargs) @@ -163,33 +181,14 @@ class Request(object): else: raise NetworkError('{0} ({1})'.format(message, resp.status)) - def get(self, url): + def get(self, url, timeout=None): """Request an URL. + Args: - url: - The web location we want to retrieve. - - Returns: - 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. + 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). Returns: A JSON object. @@ -198,7 +197,28 @@ class Request(object): urlopen_kwargs = {} 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): data = InputFile(data) @@ -215,24 +235,34 @@ class Request(object): return self._parse(result) - def retrieve(self, url): + def retrieve(self, url, timeout=None): """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. Args: - url: - The web location we want to retrieve. + 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). filename: 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: fobj.write(buf) diff --git a/tests/test_updater.py b/tests/test_updater.py index 7d3150549..ccdd875a4 100644 --- a/tests/test_updater.py +++ b/tests/test_updater.py @@ -809,7 +809,7 @@ class MockBot(object): self.bootstrap_attempts += 1 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: raise TelegramError('Test Error 2')