Bump Tornado Version and Remove Workaround from #2067 (#2494)

This commit is contained in:
Bibo-Joshi 2021-05-05 20:59:06 +02:00 committed by GitHub
parent 94a9b7f983
commit 9737b1d3c7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 26 additions and 170 deletions

View file

@ -22,7 +22,7 @@ repos:
- --rcfile=setup.cfg
additional_dependencies:
- certifi
- tornado>=5.1
- tornado>=6.1
- APScheduler==3.6.3
- . # this basically does `pip install -e .`
- repo: https://github.com/pre-commit/mirrors-mypy
@ -33,7 +33,7 @@ repos:
files: ^telegram/.*\.py$
additional_dependencies:
- certifi
- tornado>=5.1
- tornado>=6.1
- APScheduler==3.6.3
- . # this basically does `pip install -e .`
- id: mypy
@ -44,7 +44,7 @@ repos:
- --follow-imports=silent
additional_dependencies:
- certifi
- tornado>=5.1
- tornado>=6.1
- APScheduler==3.6.3
- . # this basically does `pip install -e .`
- repo: https://github.com/asottile/pyupgrade

View file

@ -2,6 +2,6 @@
# pre-commit hooks for pylint & mypy
certifi
# only telegram.ext: # Keep this line here; used in setup(-raw).py
tornado>=5.1
tornado>=6.1
APScheduler==3.6.3
pytz>=2018.6

View file

@ -334,7 +334,7 @@ class Updater:
bootstrap_retries: int = 0,
webhook_url: str = None,
allowed_updates: List[str] = None,
force_event_loop: bool = False,
force_event_loop: bool = None,
drop_pending_updates: bool = None,
ip_address: str = None,
) -> Optional[Queue]:
@ -349,15 +349,6 @@ class Updater:
:meth:`start_webhook` now *always* calls :meth:`telegram.Bot.set_webhook`, so pass
``webhook_url`` instead of calling ``updater.bot.set_webhook(webhook_url)`` manually.
Note:
Due to an incompatibility of the Tornado library PTB uses for the webhook with Python
3.8+ on Windows machines, PTB will attempt to set the event loop to
:attr:`asyncio.SelectorEventLoop` and raise an exception, if an incompatible event loop
has already been specified. See this `thread`_ for more details. To suppress the
exception, set :attr:`force_event_loop` to :obj:`True`.
.. _thread: https://github.com/tornadoweb/tornado/issues/2608
Args:
listen (:obj:`str`, optional): IP-Address to listen on. Default ``127.0.0.1``.
port (:obj:`int`, optional): Port the bot should be listening on. Default ``80``.
@ -387,8 +378,12 @@ class Updater:
.. versionadded :: 13.4
allowed_updates (List[:obj:`str`], optional): Passed to
:meth:`telegram.Bot.set_webhook`.
force_event_loop (:obj:`bool`, optional): Force using the current event loop. See above
note for details. Defaults to :obj:`False`
force_event_loop (:obj:`bool`, optional): Legacy parameter formerly used for a
workaround on Windows + Python 3.8+. No longer has any effect.
.. deprecated:: 13.6
Since version 13.6, ``tornade>=6.1`` is required, which resolves the former
issue.
Returns:
:obj:`Queue`: The update queue that can be filled from the main thread.
@ -405,6 +400,14 @@ class Updater:
stacklevel=2,
)
if force_event_loop is not None:
warnings.warn(
'The argument `force_event_loop` of `start_webhook` is deprecated and no longer '
'has any effect.',
category=TelegramDeprecationWarning,
stacklevel=2,
)
drop_pending_updates = drop_pending_updates if drop_pending_updates is not None else clean
with self.__lock:
@ -429,7 +432,6 @@ class Updater:
webhook_url,
allowed_updates,
ready=webhook_ready,
force_event_loop=force_event_loop,
ip_address=ip_address,
)
@ -563,7 +565,6 @@ class Updater:
webhook_url,
allowed_updates,
ready=None,
force_event_loop=False,
ip_address=None,
):
self.logger.debug('Updater thread started (webhook)')
@ -606,7 +607,7 @@ class Updater:
ip_address=ip_address,
)
self.httpd.serve_forever(force_event_loop=force_event_loop, ready=ready)
self.httpd.serve_forever(ready=ready)
@staticmethod
def _gen_webhook_url(listen: str, port: int, url_path: str) -> str:

View file

@ -18,10 +18,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
# pylint: disable=C0114
import asyncio
import logging
import os
import sys
from queue import Queue
from ssl import SSLContext
from threading import Event, Lock
@ -57,11 +54,11 @@ class WebhookServer:
self.server_lock = Lock()
self.shutdown_lock = Lock()
def serve_forever(self, force_event_loop: bool = False, ready: Event = None) -> None:
def serve_forever(self, ready: Event = None) -> None:
with self.server_lock:
IOLoop().make_current()
self.is_running = True
self.logger.debug('Webhook Server started.')
self._ensure_event_loop(force_event_loop=force_event_loop)
self.loop = IOLoop.current()
self.http_server.listen(self.port, address=self.listen)
@ -87,54 +84,6 @@ class WebhookServer:
exc_info=True,
)
def _ensure_event_loop(self, force_event_loop: bool = False) -> None:
"""If there's no asyncio event loop set for the current thread - create one."""
try:
loop = asyncio.get_event_loop()
if (
not force_event_loop
and os.name == 'nt'
and sys.version_info >= (3, 8)
and isinstance(loop, asyncio.ProactorEventLoop) # type: ignore[attr-defined]
):
raise TypeError(
'`ProactorEventLoop` is incompatible with '
'Tornado. Please switch to `SelectorEventLoop`.'
)
except RuntimeError:
# Python 3.8 changed default asyncio event loop implementation on windows
# from SelectorEventLoop to ProactorEventLoop. At the time of this writing
# Tornado doesn't support ProactorEventLoop and suggests that end users
# change asyncio event loop policy to WindowsSelectorEventLoopPolicy.
# https://github.com/tornadoweb/tornado/issues/2608
# To avoid changing the global event loop policy, we manually construct
# a SelectorEventLoop instance instead of using asyncio.new_event_loop().
# Note that the fix is not applied in the main thread, as that can break
# user code in even more ways than changing the global event loop policy can,
# and because Updater always starts its webhook server in a separate thread.
# Ideally, we would want to check that Tornado actually raises the expected
# NotImplementedError, but it's not possible to cleanly recover from that
# exception in current Tornado version.
if (
os.name == 'nt'
and sys.version_info >= (3, 8)
# OS+version check makes hasattr check redundant, but just to be sure
and hasattr(asyncio, 'WindowsProactorEventLoopPolicy')
and (
isinstance(
asyncio.get_event_loop_policy(),
asyncio.WindowsProactorEventLoopPolicy, # type: ignore # pylint: disable
)
)
): # pylint: disable=E1101
self.logger.debug(
'Applying Tornado asyncio event loop fix for Python 3.8+ on Windows'
)
loop = asyncio.SelectorEventLoop()
else:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
class WebhookAppClass(tornado.web.Application):
def __init__(self, webhook_path: str, bot: 'Bot', update_queue: Queue):

View file

@ -221,101 +221,6 @@ class TestUpdater:
updater.stop()
assert not caplog.records
@pytest.mark.skipif(
os.name != 'nt' or sys.version_info < (3, 8),
reason='Workaround only relevant on windows with py3.8+',
)
def test_start_webhook_ensure_event_loop(self, updater, monkeypatch):
def serve_forever(self, force_event_loop=False, ready=None):
with self.server_lock:
self.is_running = True
self._ensure_event_loop(force_event_loop=force_event_loop)
if ready is not None:
ready.set()
monkeypatch.setattr(WebhookServer, 'serve_forever', serve_forever)
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
ip = '127.0.0.1'
port = randrange(1024, 49152) # Select random port
with set_asyncio_event_loop(None):
updater._start_webhook(
ip,
port,
url_path='TOKEN',
cert=None,
key=None,
bootstrap_retries=0,
drop_pending_updates=False,
webhook_url=None,
allowed_updates=None,
)
assert isinstance(asyncio.get_event_loop(), asyncio.SelectorEventLoop)
@pytest.mark.skipif(
os.name != 'nt' or sys.version_info < (3, 8),
reason='Workaround only relevant on windows with py3.8+',
)
def test_start_webhook_force_event_loop_false(self, updater, monkeypatch):
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
ip = '127.0.0.1'
port = randrange(1024, 49152) # Select random port
with set_asyncio_event_loop(asyncio.ProactorEventLoop()):
with pytest.raises(TypeError, match='`ProactorEventLoop` is incompatible'):
updater._start_webhook(
ip,
port,
url_path='TOKEN',
cert=None,
key=None,
bootstrap_retries=0,
drop_pending_updates=False,
webhook_url=None,
allowed_updates=None,
)
@pytest.mark.skipif(
os.name != 'nt' or sys.version_info < (3, 8),
reason='Workaround only relevant on windows with py3.8+',
)
def test_start_webhook_force_event_loop_true(self, updater, monkeypatch):
def serve_forever(self, force_event_loop=False, ready=None):
with self.server_lock:
self.is_running = True
self._ensure_event_loop(force_event_loop=force_event_loop)
if ready is not None:
ready.set()
monkeypatch.setattr(WebhookServer, 'serve_forever', serve_forever)
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
ip = '127.0.0.1'
port = randrange(1024, 49152) # Select random port
with set_asyncio_event_loop(asyncio.ProactorEventLoop()):
updater._start_webhook(
ip,
port,
url_path='TOKEN',
cert=None,
key=None,
bootstrap_retries=0,
drop_pending_updates=False,
webhook_url=None,
allowed_updates=None,
force_event_loop=True,
)
assert isinstance(asyncio.get_event_loop(), asyncio.ProactorEventLoop)
def test_webhook_ssl(self, monkeypatch, updater):
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
@ -450,7 +355,7 @@ class TestUpdater:
)
assert self.test_flag is True
def test_clean_deprecation_warning_webhook(self, recwarn, updater, monkeypatch):
def test_deprecation_warnings_start_webhook(self, recwarn, updater, monkeypatch):
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
# prevent api calls from @info decorator when updater.bot.id is used in thread names
@ -459,11 +364,12 @@ class TestUpdater:
ip = '127.0.0.1'
port = randrange(1024, 49152) # Select random port
updater.start_webhook(ip, port, clean=True)
updater.start_webhook(ip, port, clean=True, force_event_loop=False)
updater.stop()
assert len(recwarn) == 2
assert len(recwarn) == 3
assert str(recwarn[0].message).startswith('Old Handler API')
assert str(recwarn[1].message).startswith('The argument `clean` of')
assert str(recwarn[2].message).startswith('The argument `force_event_loop` of')
def test_clean_deprecation_warning_polling(self, recwarn, updater, monkeypatch):
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)