mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2026-06-28 04:05:01 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d9f77d6ee1 | |||
| 698a91427a | |||
| 5956aae235 |
+10
@@ -1,6 +1,16 @@
|
||||
=======
|
||||
Changes
|
||||
=======
|
||||
**2018-03-05**
|
||||
*Released 10.0.1*
|
||||
|
||||
Fixes:
|
||||
- Fix conversationhandler timeout (PR `#1032`_)
|
||||
- Add missing docs utils (PR `#912`_)
|
||||
|
||||
.. _`#1032`: https://github.com/python-telegram-bot/python-telegram-bot/pull/826
|
||||
.. _`#912`: https://github.com/python-telegram-bot/python-telegram-bot/pull/826
|
||||
|
||||
**2018-03-02**
|
||||
*Released 10.0.0*
|
||||
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ author = u'Leandro Toledo'
|
||||
# The short X.Y version.
|
||||
version = '10.0' # telegram.__version__[:3]
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '10.0.0' # telegram.__version__
|
||||
release = '10.0.1' # telegram.__version__
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
||||
@@ -3,8 +3,8 @@ telegram package
|
||||
|
||||
.. toctree::
|
||||
|
||||
telegram.utils.helpers
|
||||
telegram.ext
|
||||
telegram.utils
|
||||
telegram.audio
|
||||
telegram.bot
|
||||
telegram.callbackquery
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
telegram.utils.promise.Promise
|
||||
==============================
|
||||
|
||||
.. autoclass:: telegram.utils.promise.Promise
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,6 @@
|
||||
telegram.utils.request.Request
|
||||
==============================
|
||||
|
||||
.. autoclass:: telegram.utils.request.Request
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,8 @@
|
||||
telegram.utils package
|
||||
======================
|
||||
|
||||
.. toctree::
|
||||
|
||||
telegram.utils.helpers
|
||||
telegram.utils.promise
|
||||
telegram.utils.request
|
||||
+1
-1
@@ -2639,7 +2639,7 @@ class Bot(TelegramObject):
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target`channel (in the format @channelusername).
|
||||
photo (`telegram.InputFile`): New chat photo.
|
||||
photo (`filelike object`): New chat photo.
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
|
||||
the read timeout from the server (instead of the one specified during creation of
|
||||
the connection pool).
|
||||
|
||||
@@ -303,11 +303,10 @@ class ConversationHandler(Handler):
|
||||
|
||||
"""
|
||||
new_state = self.current_handler.handle_update(update, dispatcher)
|
||||
timeout_job = self.timeout_jobs.get(self.current_conversation)
|
||||
timeout_job = self.timeout_jobs.pop(self.current_conversation, None)
|
||||
|
||||
if timeout_job is not None or new_state == self.END:
|
||||
if timeout_job is not None:
|
||||
timeout_job.schedule_removal()
|
||||
del self.timeout_jobs[self.current_conversation]
|
||||
if self.conversation_timeout and new_state != self.END:
|
||||
self.timeout_jobs[self.current_conversation] = dispatcher.job_queue.run_once(
|
||||
self._trigger_timeout, self.conversation_timeout,
|
||||
@@ -330,4 +329,5 @@ class ConversationHandler(Handler):
|
||||
self.conversations[key] = new_state
|
||||
|
||||
def _trigger_timeout(self, bot, job):
|
||||
del self.timeout_jobs[job.context]
|
||||
self.update_state(self.END, job.context)
|
||||
|
||||
@@ -27,7 +27,20 @@ logger.addHandler(logging.NullHandler())
|
||||
|
||||
|
||||
class Promise(object):
|
||||
"""A simple Promise implementation for the run_async decorator."""
|
||||
"""A simple Promise implementation for use with the run_async decorator, DelayQueue etc.
|
||||
|
||||
Args:
|
||||
pooled_function (:obj:`callable`): The callable that will be called concurrently.
|
||||
args (:obj:`list` | :obj:`tuple`): Positional arguments for :attr:`pooled_function`.
|
||||
kwargs (:obj:`dict`): Keyword arguments for :attr:`pooled_function`.
|
||||
|
||||
Attributes:
|
||||
pooled_function (:obj:`callable`): The callable that will be called concurrently.
|
||||
args (:obj:`list` | :obj:`tuple`): Positional arguments for :attr:`pooled_function`.
|
||||
kwargs (:obj:`dict`): Keyword arguments for :attr:`pooled_function`.
|
||||
done (:obj:`threading.Event`): Is set when the result is available.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, pooled_function, args, kwargs):
|
||||
self.pooled_function = pooled_function
|
||||
@@ -38,6 +51,8 @@ class Promise(object):
|
||||
self._exception = None
|
||||
|
||||
def run(self):
|
||||
"""Calls the :attr:`pooled_function` callable."""
|
||||
|
||||
try:
|
||||
self._result = self.pooled_function(*self.args, **self.kwargs)
|
||||
|
||||
@@ -52,6 +67,19 @@ class Promise(object):
|
||||
self.run()
|
||||
|
||||
def result(self, timeout=None):
|
||||
"""Return the result of the ``Promise``.
|
||||
|
||||
Args:
|
||||
timeout (:obj:`float`, optional): Maximum time in seconds to wait for the result to be
|
||||
calculated. ``None`` means indefinite. Default is ``None``.
|
||||
|
||||
Returns:
|
||||
Returns the return value of :attr:`pooled_function` or ``None`` if the ``timeout``
|
||||
expires.
|
||||
|
||||
Raises:
|
||||
Any exception raised by :attr:`pooled_function`.
|
||||
"""
|
||||
self.done.wait(timeout=timeout)
|
||||
if self._exception is not None:
|
||||
raise self._exception # pylint: disable=raising-bad-type
|
||||
@@ -59,4 +87,6 @@ class Promise(object):
|
||||
|
||||
@property
|
||||
def exception(self):
|
||||
"""The exception raised by :attr:`pooled_function` or ``None`` if no exception has been
|
||||
raised (yet)."""
|
||||
return self._exception
|
||||
|
||||
@@ -242,6 +242,7 @@ class Request(object):
|
||||
|
||||
def post(self, url, data, timeout=None):
|
||||
"""Request an URL.
|
||||
|
||||
Args:
|
||||
url (:obj:`str`): The web location we want to retrieve.
|
||||
data (dict[str, str|int]): A dict of key/value pairs. Note: On py2.7 value is unicode.
|
||||
@@ -291,6 +292,7 @@ class Request(object):
|
||||
|
||||
def download(self, url, filename, timeout=None):
|
||||
"""Download a file by its URL.
|
||||
|
||||
Args:
|
||||
url (str): The web location we want to retrieve.
|
||||
timeout (:obj:`int` | :obj:`float`): If this value is specified, use it as the read
|
||||
|
||||
+1
-1
@@ -17,4 +17,4 @@
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
|
||||
__version__ = '10.0.0'
|
||||
__version__ = '10.0.1'
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
import logging
|
||||
from time import sleep
|
||||
|
||||
import pytest
|
||||
@@ -55,7 +56,8 @@ class TestConversationHandler(object):
|
||||
self.BREWING: [CommandHandler('pourCoffee', self.drink)],
|
||||
self.DRINKING:
|
||||
[CommandHandler('startCoding', self.code),
|
||||
CommandHandler('drinkMore', self.drink)],
|
||||
CommandHandler('drinkMore', self.drink),
|
||||
CommandHandler('end', self.end)],
|
||||
self.CODING: [
|
||||
CommandHandler('keepCoding', self.code),
|
||||
CommandHandler('gettingThirsty', self.start),
|
||||
@@ -73,6 +75,9 @@ class TestConversationHandler(object):
|
||||
def start(self, bot, update):
|
||||
return self._set_state(update, self.THIRSTY)
|
||||
|
||||
def end(self, bot, update):
|
||||
return self._set_state(update, self.END)
|
||||
|
||||
def start_end(self, bot, update):
|
||||
return self._set_state(update, self.END)
|
||||
|
||||
@@ -123,6 +128,25 @@ class TestConversationHandler(object):
|
||||
with pytest.raises(KeyError):
|
||||
self.current_state[user2.id]
|
||||
|
||||
def test_conversation_handler_end(self, caplog, dp, bot, user1):
|
||||
handler = ConversationHandler(entry_points=self.entry_points, states=self.states,
|
||||
fallbacks=self.fallbacks)
|
||||
dp.add_handler(handler)
|
||||
|
||||
message = Message(0, user1, None, self.group, text='/start', bot=bot)
|
||||
dp.process_update(Update(update_id=0, message=message))
|
||||
message.text = '/brew'
|
||||
dp.process_update(Update(update_id=0, message=message))
|
||||
message.text = '/pourCoffee'
|
||||
dp.process_update(Update(update_id=0, message=message))
|
||||
message.text = '/end'
|
||||
with caplog.at_level(logging.ERROR):
|
||||
dp.process_update(Update(update_id=0, message=message))
|
||||
assert len(caplog.records) == 0
|
||||
assert self.current_state[user1.id] == self.END
|
||||
with pytest.raises(KeyError):
|
||||
print(handler.conversations[(self.group.id, user1.id)])
|
||||
|
||||
def test_conversation_handler_fallback(self, dp, bot, user1, user2):
|
||||
handler = ConversationHandler(entry_points=self.entry_points, states=self.states,
|
||||
fallbacks=self.fallbacks)
|
||||
@@ -309,16 +333,50 @@ class TestConversationHandler(object):
|
||||
assert handler.conversations.get((self.group.id, user1.id)) is None
|
||||
|
||||
# Start state machine, do something, then reach timeout
|
||||
dp.process_update(Update(update_id=0, message=message))
|
||||
dp.process_update(Update(update_id=1, message=message))
|
||||
assert handler.conversations.get((self.group.id, user1.id)) == self.THIRSTY
|
||||
message.text = '/brew'
|
||||
dp.job_queue.tick()
|
||||
dp.process_update(Update(update_id=0, message=message))
|
||||
dp.process_update(Update(update_id=2, message=message))
|
||||
assert handler.conversations.get((self.group.id, user1.id)) == self.BREWING
|
||||
sleep(0.5)
|
||||
dp.job_queue.tick()
|
||||
assert handler.conversations.get((self.group.id, user1.id)) is None
|
||||
|
||||
def test_conversation_timeout_keeps_extending(self, dp, bot, user1):
|
||||
handler = ConversationHandler(entry_points=self.entry_points, states=self.states,
|
||||
fallbacks=self.fallbacks, conversation_timeout=0.5)
|
||||
dp.add_handler(handler)
|
||||
|
||||
# Start state machine, wait, do something, verify the timeout is extended.
|
||||
# t=0 /start (timeout=.5)
|
||||
# t=.25 /brew (timeout=.75)
|
||||
# t=.5 original timeout
|
||||
# t=.6 /pourCoffee (timeout=1.1)
|
||||
# t=.75 second timeout
|
||||
# t=1.1 actual timeout
|
||||
message = Message(0, user1, None, self.group, text='/start', bot=bot)
|
||||
dp.process_update(Update(update_id=0, message=message))
|
||||
assert handler.conversations.get((self.group.id, user1.id)) == self.THIRSTY
|
||||
sleep(0.25) # t=.25
|
||||
dp.job_queue.tick()
|
||||
assert handler.conversations.get((self.group.id, user1.id)) == self.THIRSTY
|
||||
message.text = '/brew'
|
||||
dp.process_update(Update(update_id=0, message=message))
|
||||
assert handler.conversations.get((self.group.id, user1.id)) == self.BREWING
|
||||
sleep(0.35) # t=.6
|
||||
dp.job_queue.tick()
|
||||
assert handler.conversations.get((self.group.id, user1.id)) == self.BREWING
|
||||
message.text = '/pourCoffee'
|
||||
dp.process_update(Update(update_id=0, message=message))
|
||||
assert handler.conversations.get((self.group.id, user1.id)) == self.DRINKING
|
||||
sleep(.4) # t=1
|
||||
dp.job_queue.tick()
|
||||
assert handler.conversations.get((self.group.id, user1.id)) == self.DRINKING
|
||||
sleep(.1) # t=1.1
|
||||
dp.job_queue.tick()
|
||||
assert handler.conversations.get((self.group.id, user1.id)) is None
|
||||
|
||||
def test_conversation_timeout_two_users(self, dp, bot, user1, user2):
|
||||
handler = ConversationHandler(entry_points=self.entry_points, states=self.states,
|
||||
fallbacks=self.fallbacks, conversation_timeout=0.5)
|
||||
|
||||
Reference in New Issue
Block a user