Merge branch 'master' into dec04

This commit is contained in:
Jannes Höke 2017-01-16 00:15:09 +01:00
commit 975d193441
25 changed files with 662 additions and 199 deletions

View file

@ -9,12 +9,14 @@ Contributors
The following wonderful people contributed directly or indirectly to this project:
- `Avanatiker <https://github.com/Avanatiker>`_
- `Anton Tagunov <https://github.com/anton-tagunov>`_
- `Balduro <https://github.com/Balduro>`_
- `bimmlerd <https://github.com/bimmlerd>`_
- `Eli Gao <https://github.com/eligao>`_
- `ErgoZ Riftbit Vaper <https://github.com/ergoz>`_
- `Eugene Lisitsky <https://github.com/lisitsky>`_
- `franciscod <https://github.com/franciscod>`_
- `Hugo Damer <https://github.com/HakimusGIT>`_
- `Jacob Bom <https://github.com/bomjacob>`_
- `JASON0916 <https://github.com/JASON0916>`_
- `jh0ker <https://github.com/jh0ker>`_
@ -31,6 +33,7 @@ The following wonderful people contributed directly or indirectly to this projec
- `overquota <https://github.com/overquota>`_
- `Patrick Hofmann <https://github.com/PH89>`_
- `Rahiel Kasim <https://github.com/rahiel>`_
- `Joscha Götzer <https://github.com/Rostgnom>`_
- `Shelomentsev D <https://github.com/shelomentsevd>`_
- `sooyhwang <https://github.com/sooyhwang>`_
- `Valentijn <https://github.com/Faalentijn>`_

View file

@ -55,6 +55,8 @@ class Animation(TelegramObject):
if not data:
return None
data = super(Animation, Animation).de_json(data, bot)
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
return Animation(**data)

View file

@ -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:
@ -1205,7 +1234,8 @@ class Bot(TelegramObject):
offset=None,
limit=100,
timeout=0,
network_delay=5.,
network_delay=None,
read_latency=2.,
allowed_updates=None,
**kwargs):
"""Use this method to receive incoming updates using long polling.
@ -1227,13 +1257,25 @@ class Bot(TelegramObject):
Please note that this parameter doesn't affect updates created before the call to
the setWebhook, so unwanted updates may be received for a short period of time.
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`]
@ -1243,6 +1285,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:
@ -1252,9 +1298,12 @@ class Bot(TelegramObject):
if allowed_updates is not None:
data['allowed_updates'] = allowed_updates
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])
@ -1292,8 +1341,9 @@ class Bot(TelegramObject):
specified, the previous setting will be used.
Please note that this parameter doesn't affect updates created before the call to
the setWebhook, so unwanted updates may be received for a short period of time.
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:
@ -1363,8 +1413,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:
@ -1390,8 +1441,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:
@ -1420,8 +1472,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:
@ -1446,8 +1499,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:
@ -1473,8 +1527,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:
@ -1492,11 +1547,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`
@ -1505,7 +1565,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)
@ -1518,6 +1578,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.
@ -1537,10 +1598,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
@ -1568,7 +1628,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:
@ -1579,9 +1639,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.
@ -1598,7 +1664,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]
@ -1647,7 +1713,6 @@ class Bot(TelegramObject):
edit_message_reply_markup = editMessageReplyMarkup
get_updates = getUpdates
set_webhook = setWebhook
delete_webhook = deleteWebhook
leave_chat = leaveChat
get_chat = getChat
get_chat_administrators = getChatAdministrators

View file

@ -60,6 +60,8 @@ class CallbackQuery(TelegramObject):
if not data:
return None
data = super(CallbackQuery, CallbackQuery).de_json(data, bot)
data['from_user'] = User.de_json(data.get('from'), bot)
data['message'] = Message.de_json(data.get('message'), bot)

View file

@ -59,6 +59,8 @@ class ChatMember(TelegramObject):
if not data:
return None
data = super(ChatMember, ChatMember).de_json(data, bot)
data['user'] = User.de_json(data.get('user'), bot)
return ChatMember(**data)

View file

@ -74,7 +74,8 @@ class ChosenInlineResult(TelegramObject):
if not data:
return None
# Required
data = super(ChosenInlineResult, ChosenInlineResult).de_json(data, bot)
# Required
data['from_user'] = User.de_json(data.pop('from'), bot)
# Optionals
data['location'] = Location.de_json(data.get('location'), bot)

View file

@ -63,6 +63,8 @@ class Document(TelegramObject):
if not data:
return None
data = super(Document, Document).de_json(data, bot)
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
return Document(**data)

View file

@ -28,7 +28,9 @@ from telegram.utils.promise import Promise
class ConversationHandler(Handler):
"""
A handler to hold a conversation with a user by managing four collections of other handlers.
A handler to hold a conversation with a single user by managing four collections of other
handlers. Note that neither posts in Telegram Channels, nor group interactions with multiple
users are managed by instances of this class.
The first collection, a ``list`` named ``entry_points``, is used to initiate the conversation,
for example with a ``CommandHandler`` or ``RegexHandler``.
@ -113,7 +115,8 @@ class ConversationHandler(Handler):
def check_update(self, update):
if not isinstance(update, Update):
# Ignore messages in channels
if not isinstance(update, Update) or update.channel_post:
return False
chat, user = extract_chat_and_user(update)

View file

@ -22,6 +22,7 @@ import logging
import time
import warnings
import datetime
import weakref
from numbers import Number
from threading import Thread, Lock, Event
from queue import PriorityQueue, Empty
@ -68,40 +69,182 @@ class JobQueue(object):
Args:
job (telegram.ext.Job): The ``Job`` instance representing the new job
next_t (Optional[int, float, datetime.timedelta]): Time in which the job
should be executed first. Defaults to ``job.interval``. ``int`` and ``float``
will be interpreted as seconds.
next_t (Optional[int, float, datetime.timedelta, datetime.datetime, datetime.time]):
Time in or at which the job should run for the first time. This parameter will be
interpreted depending on its type.
``int`` or ``float`` will be interpreted as "seconds from now" in which the job
should run.
``datetime.timedelta`` will be interpreted as "time from now" in which the job
should run.
``datetime.datetime`` will be interpreted as a specific date and time at which the
job should run.
``datetime.time`` will be interpreted as a specific time at which the job should
run. This could be either today or, if the time has already passed, tomorrow.
"""
warnings.warn("'JobQueue.put' is being deprecated, use 'JobQueue.one_time_job', "
"'JobQueue.repeating_job' or 'JobQueue.daily_job' instead")
if job.job_queue is None:
job.job_queue = self
self._put(job, next_t=next_t)
def _put(self, job, next_t=None, last_t=None):
"""Queue a new job.
Args:
job (telegram.ext.Job): The ``Job`` instance representing the new job
next_t (Optional[int, float, datetime.timedelta, datetime.datetime, datetime.time]):
Time in or at which the job should run for the first time. This parameter will be
interpreted depending on its type.
* ``int`` or ``float`` will be interpreted as "seconds from now" in which the job
should run.
* ``datetime.timedelta`` will be interpreted as "time from now" in which the job
should run.
* ``datetime.datetime`` will be interpreted as a specific date and time at which
the job should run.
* ``datetime.time`` will be interpreted as a specific time of day at which the job
should run. This could be either today or, if the time has already passed,
tomorrow.
last_t (Optional[float]): Timestamp of the time when ``job`` was scheduled for in the
last ``put`` call. If provided, it will be used to calculate the next timestamp
more accurately by accounting for the execution time of the job (and possibly
others). If None, `now` will be assumed.
"""
job.job_queue = self
if next_t is None:
interval = job.interval
next_t = job.interval
if next_t is None:
raise ValueError('next_t is None')
if isinstance(next_t, datetime.datetime):
next_t = (next_t - datetime.datetime.now()).total_seconds()
elif isinstance(next_t, datetime.time):
next_datetime = datetime.datetime.combine(datetime.date.today(), next_t)
if datetime.datetime.now().time() > next_t:
next_datetime += datetime.timedelta(days=1)
next_t = (next_datetime - datetime.datetime.now()).total_seconds()
if isinstance(interval, Number):
next_t = interval
elif isinstance(interval, datetime.timedelta):
next_t = interval.total_seconds()
else:
raise ValueError("The interval argument should be of type datetime.timedelta,"
" int or float")
elif isinstance(next_t, datetime.timedelta):
next_t = next_t.total_second()
next_t = next_t.total_seconds()
now = time.time()
next_t += now
next_t += last_t or time.time()
self.logger.debug('Putting job %s with t=%f', job.name, next_t)
self.queue.put((next_t, job))
# Wake up the loop if this job should be executed next
self._set_next_peek(next_t)
def run_once(self, callback, when, context=None, name=None):
"""Creates a new ``Job`` that runs once and adds it to the queue.
Args:
callback (function): The callback function that should be executed by the new job. It
should take two parameters ``bot`` and ``job``, where ``job`` is the ``Job``
instance. It can be used to access it's ``context`` or change it to a repeating
job.
when (int, float, datetime.timedelta, datetime.datetime, datetime.time):
Time in or at which the job should run. This parameter will be interpreted
depending on its type.
* ``int`` or ``float`` will be interpreted as "seconds from now" in which the job
should run.
* ``datetime.timedelta`` will be interpreted as "time from now" in which the job
should run.
* ``datetime.datetime`` will be interpreted as a specific date and time at which
the job should run.
* ``datetime.time`` will be interpreted as a specific time of day at which the job
should run. This could be either today or, if the time has already passed,
tomorrow.
context (Optional[object]): Additional data needed for the callback function. Can be
accessed through ``job.context`` in the callback. Defaults to ``None``
name (Optional[str]): The name of the new job. Defaults to ``callback.__name__``
Returns:
Job: The new ``Job`` instance that has been added to the job queue.
"""
job = Job(callback, repeat=False, context=context, name=name, job_queue=self)
self._put(job, next_t=when)
return job
def run_repeating(self, callback, interval, first=None, context=None, name=None):
"""Creates a new ``Job`` that runs once and adds it to the queue.
Args:
callback (function): The callback function that should be executed by the new job. It
should take two parameters ``bot`` and ``job``, where ``job`` is the ``Job``
instance. It can be used to access it's ``context``, terminate the job or change
its interval.
interval (int, float, datetime.timedelta): The interval in which the job will run.
If it is an ``int`` or a ``float``, it will be interpreted as seconds.
first (int, float, datetime.timedelta, datetime.datetime, datetime.time):
* ``int`` or ``float`` will be interpreted as "seconds from now" in which the job
should run.
* ``datetime.timedelta`` will be interpreted as "time from now" in which the job
should run.
* ``datetime.datetime`` will be interpreted as a specific date and time at which
the job should run.
* ``datetime.time`` will be interpreted as a specific time of day at which the job
should run. This could be either today or, if the time has already passed,
tomorrow.
Defaults to ``interval``
context (Optional[object]): Additional data needed for the callback function. Can be
accessed through ``job.context`` in the callback. Defaults to ``None``
name (Optional[str]): The name of the new job. Defaults to ``callback.__name__``
Returns:
Job: The new ``Job`` instance that has been added to the job queue.
"""
job = Job(callback,
interval=interval,
repeat=True,
context=context,
name=name,
job_queue=self)
self._put(job, next_t=first)
return job
def run_daily(self, callback, time, days=Days.EVERY_DAY, context=None, name=None):
"""Creates a new ``Job`` that runs once and adds it to the queue.
Args:
callback (function): The callback function that should be executed by the new job. It
should take two parameters ``bot`` and ``job``, where ``job`` is the ``Job``
instance. It can be used to access it's ``context`` or terminate the job.
time (datetime.time): Time of day at which the job should run.
days (Optional[tuple[int]]): Defines on which days of the week the job should run.
Defaults to ``Days.EVERY_DAY``
context (Optional[object]): Additional data needed for the callback function. Can be
accessed through ``job.context`` in the callback. Defaults to ``None``
name (Optional[str]): The name of the new job. Defaults to ``callback.__name__``
Returns:
Job: The new ``Job`` instance that has been added to the job queue.
"""
job = Job(callback,
interval=datetime.timedelta(days=1),
repeat=True,
days=days,
context=context,
name=name,
job_queue=self)
self._put(job, next_t=time)
return job
def _set_next_peek(self, t):
"""
Set next peek if not defined or `t` is before next peek.
In case the next peek was set, also trigger the `self.__tick` event.
"""
with self.__next_peek_lock:
if not self._next_peek or self._next_peek > t:
@ -126,9 +269,9 @@ class JobQueue(object):
self.logger.debug('Peeked at %s with t=%f', job.name, t)
if t > now:
# we can get here in two conditions:
# 1. At the second or later pass of the while loop, after we've already processed
# the job(s) we were supposed to at this time.
# We can get here in two conditions:
# 1. At the second or later pass of the while loop, after we've already
# processed the job(s) we were supposed to at this time.
# 2. At the first iteration of the loop only if `self.put()` had triggered
# `self.__tick` because `self._next_peek` wasn't set
self.logger.debug("Next task isn't due yet. Finished!")
@ -136,7 +279,7 @@ class JobQueue(object):
self._set_next_peek(t)
break
if job._remove.is_set():
if job.removed:
self.logger.debug('Removing job %s', job.name)
continue
@ -146,15 +289,17 @@ class JobQueue(object):
if any(day == current_week_day for day in job.days):
self.logger.debug('Running job %s', job.name)
job.run(self.bot)
except:
self.logger.exception('An uncaught error was raised while executing job %s',
job.name)
else:
self.logger.debug('Skipping disabled job %s', job.name)
if job.repeat:
self.put(job)
if job.repeat and not job.removed:
self._put(job, last_t=t)
else:
self.logger.debug('Dropping non-repeating or removed job %s', job.name)
def start(self):
"""
@ -169,7 +314,6 @@ class JobQueue(object):
self.__thread = Thread(target=self._main_loop, name="job_queue")
self.__thread.start()
self.logger.debug('%s thread started', self.__class__.__name__)
else:
self.__start_lock.release()
@ -182,7 +326,7 @@ class JobQueue(object):
while self._running:
# self._next_peek may be (re)scheduled during self.tick() or self.put()
with self.__next_peek_lock:
tmout = self._next_peek and self._next_peek - time.time()
tmout = self._next_peek - time.time() if self._next_peek else None
self._next_peek = None
self.__tick.clear()
@ -216,46 +360,58 @@ class Job(object):
"""This class encapsulates a Job
Attributes:
callback (function):
interval (float):
days: (tuple)
repeat (bool):
name (str):
callback (function): The function that the job executes when it's due
interval (int, float, datetime.timedelta): The interval in which the job runs
days (tuple[int]): A tuple of ``int`` values that determine on which days of the week the
job runs
repeat (bool): If the job runs periodically or only once
name (str): The name of this job
job_queue (JobQueue): The ``JobQueue`` this job belongs to
enabled (bool): Boolean property that decides if this job is currently active
Args:
callback (function): The callback function that should be executed by the Job. It should
take two parameters ``bot`` and ``job``, where ``job`` is the ``Job`` instance. It
can be used to terminate the job or modify its interval.
interval ([int, float, datetime.timedelta]): The interval in which the job will execute its
callback function. ``int`` and ``float`` will be interpreted as seconds.
interval (Optional[int, float, datetime.timedelta]): The interval in which the job will
execute its callback function. ``int`` and ``float`` will be interpreted as seconds.
If you don't set this value, you must set ``repeat=False`` and specify ``next_t`` when
you put the job into the job queue.
repeat (Optional[bool]): If this job should be periodically execute its callback function
(``True``) or only once (``False``). Defaults to ``True``
context (Optional[object]): Additional data needed for the callback function. Can be
accessed through ``job.context`` in the callback. Defaults to ``None``
days (Tuple): Defines on which days the job should be ran.
days (Optional[tuple[int]]): Defines on which days of the week the job should run.
Defaults to ``Days.EVERY_DAY``
name (Optional[str]): The name of this job. Defaults to ``callback.__name__``
job_queue (Optional[class:`telegram.ext.JobQueue`]): The ``JobQueue`` this job belongs to.
Only optional for backward compatibility with ``JobQueue.put()``.
"""
job_queue = None
def __init__(self, callback, interval, repeat=True, context=None, days=Days.EVERY_DAY):
def __init__(self,
callback,
interval=None,
repeat=True,
context=None,
days=Days.EVERY_DAY,
name=None,
job_queue=None):
self.callback = callback
self.context = context
self.name = name or callback.__name__
self._repeat = repeat
self._interval = None
self.interval = interval
self.repeat = repeat
self.context = context
if not isinstance(days, tuple):
raise ValueError("The 'days argument should be of type 'tuple'")
if not all(isinstance(day, int) for day in days):
raise ValueError("The elements of the 'days' argument should be of type 'int'")
if not all(day >= 0 and day <= 6 for day in days):
raise ValueError("The elements of the 'days' argument should be from 0 up to and "
"including 6")
self._days = None
self.days = days
self.name = callback.__name__
self._job_queue = weakref.proxy(job_queue) if job_queue is not None else None
self._remove = Event()
self._enabled = Event()
self._enabled.set()
@ -268,20 +424,86 @@ class Job(object):
"""
Schedules this job for removal from the ``JobQueue``. It will be removed without executing
its callback function again.
"""
self._remove.set()
def is_enabled(self):
@property
def removed(self):
return self._remove.is_set()
@property
def enabled(self):
return self._enabled.is_set()
def set_enabled(self, status):
@enabled.setter
def enabled(self, status):
if status:
self._enabled.set()
else:
self._enabled.clear()
enabled = property(is_enabled, set_enabled)
@property
def interval(self):
return self._interval
@interval.setter
def interval(self, interval):
if interval is None and self.repeat:
raise ValueError("The 'interval' can not be 'None' when 'repeat' is set to 'True'")
if not (interval is None or isinstance(interval, (Number, datetime.timedelta))):
raise ValueError("The 'interval' must be of type 'datetime.timedelta',"
" 'int' or 'float'")
self._interval = interval
@property
def interval_seconds(self):
if isinstance(self.interval, datetime.timedelta):
return self.interval.total_seconds()
else:
return self.interval
@property
def repeat(self):
return self._repeat
@repeat.setter
def repeat(self, repeat):
if self.interval is None and repeat:
raise ValueError("'repeat' can not be set to 'True' when no 'interval' is set")
self._repeat = repeat
@property
def days(self):
return self._days
@days.setter
def days(self, days):
if not isinstance(days, tuple):
raise ValueError("The 'days' argument should be of type 'tuple'")
if not all(isinstance(day, int) for day in days):
raise ValueError("The elements of the 'days' argument should be of type 'int'")
if not all(0 <= day <= 6 for day in days):
raise ValueError("The elements of the 'days' argument should be from 0 up to and "
"including 6")
self._days = days
@property
def job_queue(self):
""" :rtype: JobQueue """
return self._job_queue
@job_queue.setter
def job_queue(self, job_queue):
# Property setter for backward compatibility with JobQueue.put()
if not self._job_queue:
self._job_queue = weakref.proxy(job_queue)
else:
raise RuntimeError("The 'job_queue' attribute can only be set once.")
def __lt__(self, other):
return False

View file

@ -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,
read_latency=2.,
allowed_updates=None):
"""
Starts polling updates from Telegram.
@ -135,7 +143,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.
@ -144,15 +153,24 @@ 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
allowed_updates (Optional[list[str]]): Passed to Bot.getUpdates
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
@ -161,7 +179,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, allowed_updates)
read_latency, bootstrap_retries, clean, allowed_updates)
# Return the update queue so the main thread can insert updates
return self.update_queue
@ -218,7 +236,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,
allowed_updates):
"""
Thread target of thread 'updater'. Runs in background, pulls
@ -236,7 +254,7 @@ class Updater(object):
updates = self.bot.getUpdates(
self.last_update_id,
timeout=timeout,
network_delay=network_delay,
read_latency=read_latency,
allowed_updates=allowed_updates)
except RetryAfter as e:
self.logger.info(str(e))

View file

@ -65,17 +65,39 @@ class File(TelegramObject):
return File(bot=bot, **data)
def download(self, custom_path=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.
Args:
custom_path (str):
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:
raise ValueError('custom_path and out are mutually exclusive')
url = self.file_path
if custom_path:
filename = custom_path
else:
filename = basename(url)
if out:
buf = self.bot.request.retrieve(url)
out.write(buf)
self.bot.request.download(url, filename)
else:
if custom_path:
filename = custom_path
else:
filename = basename(url)
self.bot.request.download(url, filename, timeout=timeout)

View file

@ -73,6 +73,8 @@ class Game(TelegramObject):
if not data:
return None
data = super(Game, Game).de_json(data, bot)
data['photo'] = PhotoSize.de_list(data.get('photo'), bot)
data['text_entities'] = MessageEntity.de_list(data.get('text_entities'), bot)
data['animation'] = Animation.de_json(data.get('animation'), bot)

View file

@ -49,6 +49,8 @@ class GameHighScore(TelegramObject):
if not data:
return None
data = super(GameHighScore, GameHighScore).de_json(data, bot)
data['user'] = User.de_json(data.get('user'), bot)
return GameHighScore(**data)

View file

@ -200,6 +200,8 @@ class Message(TelegramObject):
if not data:
return None
data = super(Message, Message).de_json(data, bot)
data['from_user'] = User.de_json(data.get('from'), bot)
data['date'] = datetime.fromtimestamp(data['date'])
data['chat'] = Chat.de_json(data.get('chat'), bot)

View file

@ -67,6 +67,8 @@ class ReplyKeyboardMarkup(ReplyMarkup):
if not data:
return None
data = super(ReplyKeyboardMarkup, ReplyKeyboardMarkup).de_json(data, bot)
data['keyboard'] = [KeyboardButton.de_list(keyboard, bot) for keyboard in data['keyboard']]
return ReplyKeyboardMarkup(**data)

View file

@ -67,6 +67,8 @@ class Sticker(TelegramObject):
if not data:
return None
data = super(Sticker, Sticker).de_json(data, bot)
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
return Sticker(**data)

View file

@ -86,6 +86,8 @@ class Update(TelegramObject):
if not data:
return None
data = super(Update, Update).de_json(data, bot)
data['message'] = Message.de_json(data.get('message'), bot)
data['edited_message'] = Message.de_json(data.get('edited_message'), bot)
data['inline_query'] = InlineQuery.de_json(data.get('inline_query'), bot)

View file

@ -77,6 +77,8 @@ class User(TelegramObject):
if not data:
return None
data = super(User, User).de_json(data, bot)
return User(bot=bot, **data)
def get_profile_photos(self, *args, **kwargs):

View file

@ -52,6 +52,8 @@ class UserProfilePhotos(TelegramObject):
if not data:
return None
data = super(UserProfilePhotos, UserProfilePhotos).de_json(data, bot)
data['photos'] = [PhotoSize.de_list(photo, bot) for photo in data['photos']]
return UserProfilePhotos(**data)

View file

@ -17,22 +17,23 @@
# 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, InvalidToken, NetworkError, TimedOut, BadRequest, \
ChatMigrated, RetryAfter
from telegram.error import (Unauthorized, NetworkError, TimedOut, BadRequest, ChatMigrated,
RetryAfter, InvalidToken)
logging.getLogger('urllib3').setLevel(logging.WARNING)
@ -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)
@ -128,6 +146,12 @@ class Request(object):
TelegramError
"""
# Make sure to hint Telegram servers that we reuse connections by sending
# "Connection: keep-alive" in the HTTP headers.
if 'headers' not in kwargs:
kwargs['headers'] = {}
kwargs['headers']['connection'] = 'keep-alive'
try:
resp = self._con_pool.request(*args, **kwargs)
except urllib3.exceptions.TimeoutError:
@ -157,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.
@ -192,11 +197,33 @@ 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)
result = self._request_wrapper('POST', url, body=data.to_form(), headers=data.headers)
result = self._request_wrapper(
'POST', url, body=data.to_form(), headers=data.headers, **urlopen_kwargs)
else:
data = json.dumps(data)
result = self._request_wrapper(
@ -208,16 +235,34 @@ class Request(object):
return self._parse(result)
def download(self, url, filename):
def retrieve(self, url, timeout=None):
"""Retrieve the contents of a file by its URL.
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._request_wrapper('GET', url)
buf = self.retrieve(url, timeout=timeout)
with open(filename, 'wb') as fobj:
fobj.write(buf)

View file

@ -78,6 +78,8 @@ class Video(TelegramObject):
if not data:
return None
data = super(Video, Video).de_json(data, bot)
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
return Video(**data)

View file

@ -61,4 +61,6 @@ class Voice(TelegramObject):
if not data:
return None
data = super(Voice, Voice).de_json(data, bot)
return Voice(**data)

View file

@ -438,6 +438,9 @@ class BotTest(BaseTest, unittest.TestCase):
@flaky(3, 1)
@timeout(10)
def test_send_contact(self):
# test disabled due to telegram servers annoyances repeatedly returning:
# "Flood control exceeded. Retry in 2036 seconds"
return
phone = '+3-54-5445445'
name = 'name'
last = 'last'

View file

@ -62,9 +62,6 @@ class JobQueueTest(BaseTest, unittest.TestCase):
if self.jq is not None:
self.jq.stop()
def getSeconds(self):
return int(ceil(time.time()))
def job1(self, bot, job):
self.result += 1
@ -79,7 +76,7 @@ class JobQueueTest(BaseTest, unittest.TestCase):
self.result += job.context
def job5(self, bot, job):
self.job_time = self.getSeconds()
self.job_time = time.time()
def test_basic(self):
self.jq.put(Job(self.job1, 0.1))
@ -181,22 +178,78 @@ class JobQueueTest(BaseTest, unittest.TestCase):
def test_time_unit_int(self):
# Testing seconds in int
seconds_interval = 5
expected_time = self.getSeconds() + seconds_interval
delta = 2
expected_time = time.time() + delta
self.jq.put(Job(self.job5, seconds_interval, repeat=False))
sleep(6)
self.assertEqual(self.job_time, expected_time)
self.jq.put(Job(self.job5, delta, repeat=False))
sleep(2.5)
self.assertAlmostEqual(self.job_time, expected_time, delta=0.1)
def test_time_unit_dt_time(self):
def test_time_unit_dt_timedelta(self):
# Testing seconds, minutes and hours as datetime.timedelta object
# This is sufficient to test that it actually works.
interval = datetime.timedelta(seconds=5)
expected_time = self.getSeconds() + interval.total_seconds()
interval = datetime.timedelta(seconds=2)
expected_time = time.time() + interval.total_seconds()
self.jq.put(Job(self.job5, interval, repeat=False))
sleep(6)
self.assertEqual(self.job_time, expected_time)
sleep(2.5)
self.assertAlmostEqual(self.job_time, expected_time, delta=0.1)
def test_time_unit_dt_datetime(self):
# Testing running at a specific datetime
delta = datetime.timedelta(seconds=2)
next_t = datetime.datetime.now() + delta
expected_time = time.time() + delta.total_seconds()
self.jq.put(Job(self.job5, repeat=False), next_t=next_t)
sleep(2.5)
self.assertAlmostEqual(self.job_time, expected_time, delta=0.1)
def test_time_unit_dt_time_today(self):
# Testing running at a specific time today
delta = 2
next_t = (datetime.datetime.now() + datetime.timedelta(seconds=delta)).time()
expected_time = time.time() + delta
self.jq.put(Job(self.job5, repeat=False), next_t=next_t)
sleep(2.5)
self.assertAlmostEqual(self.job_time, expected_time, delta=0.1)
def test_time_unit_dt_time_tomorrow(self):
# Testing running at a specific time that has passed today. Since we can't wait a day, we
# test if the jobs next_t has been calculated correctly
delta = -2
next_t = (datetime.datetime.now() + datetime.timedelta(seconds=delta)).time()
expected_time = time.time() + delta + 60 * 60 * 24
self.jq.put(Job(self.job5, repeat=False), next_t=next_t)
self.assertAlmostEqual(self.jq.queue.get(False)[0], expected_time, delta=0.1)
def test_run_once(self):
delta = 2
expected_time = time.time() + delta
self.jq.run_once(self.job5, delta)
sleep(2.5)
self.assertAlmostEqual(self.job_time, expected_time, delta=0.1)
def test_run_repeating(self):
interval = 0.1
first = 1.5
self.jq.run_repeating(self.job1, interval, first=first)
sleep(2.505)
self.assertAlmostEqual(self.result, 10, delta=1)
def test_run_daily(self):
delta = 1
time_of_day = (datetime.datetime.now() + datetime.timedelta(seconds=delta)).time()
expected_time = time.time() + 60 * 60 * 24 + delta
self.jq.run_daily(self.job1, time_of_day)
sleep(2 * delta)
self.assertEqual(self.result, 1)
self.assertAlmostEqual(self.jq.queue.get(False)[0], expected_time, delta=0.1)
if __name__ == '__main__':

View file

@ -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')