Skip to content

Commit 9737b1d

Browse files
authored
Bump Tornado Version and Remove Workaround from python-telegram-bot#2067 (python-telegram-bot#2494)
1 parent 94a9b7f commit 9737b1d

5 files changed

Lines changed: 26 additions & 170 deletions

File tree

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ repos:
2222
- --rcfile=setup.cfg
2323
additional_dependencies:
2424
- certifi
25-
- tornado>=5.1
25+
- tornado>=6.1
2626
- APScheduler==3.6.3
2727
- . # this basically does `pip install -e .`
2828
- repo: https://github.com/pre-commit/mirrors-mypy
@@ -33,7 +33,7 @@ repos:
3333
files: ^telegram/.*\.py$
3434
additional_dependencies:
3535
- certifi
36-
- tornado>=5.1
36+
- tornado>=6.1
3737
- APScheduler==3.6.3
3838
- . # this basically does `pip install -e .`
3939
- id: mypy
@@ -44,7 +44,7 @@ repos:
4444
- --follow-imports=silent
4545
additional_dependencies:
4646
- certifi
47-
- tornado>=5.1
47+
- tornado>=6.1
4848
- APScheduler==3.6.3
4949
- . # this basically does `pip install -e .`
5050
- repo: https://github.com/asottile/pyupgrade

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
# pre-commit hooks for pylint & mypy
33
certifi
44
# only telegram.ext: # Keep this line here; used in setup(-raw).py
5-
tornado>=5.1
5+
tornado>=6.1
66
APScheduler==3.6.3
77
pytz>=2018.6

telegram/ext/updater.py

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ def start_webhook(
334334
bootstrap_retries: int = 0,
335335
webhook_url: str = None,
336336
allowed_updates: List[str] = None,
337-
force_event_loop: bool = False,
337+
force_event_loop: bool = None,
338338
drop_pending_updates: bool = None,
339339
ip_address: str = None,
340340
) -> Optional[Queue]:
@@ -349,15 +349,6 @@ def start_webhook(
349349
:meth:`start_webhook` now *always* calls :meth:`telegram.Bot.set_webhook`, so pass
350350
``webhook_url`` instead of calling ``updater.bot.set_webhook(webhook_url)`` manually.
351351
352-
Note:
353-
Due to an incompatibility of the Tornado library PTB uses for the webhook with Python
354-
3.8+ on Windows machines, PTB will attempt to set the event loop to
355-
:attr:`asyncio.SelectorEventLoop` and raise an exception, if an incompatible event loop
356-
has already been specified. See this `thread`_ for more details. To suppress the
357-
exception, set :attr:`force_event_loop` to :obj:`True`.
358-
359-
.. _thread: https://github.com/tornadoweb/tornado/issues/2608
360-
361352
Args:
362353
listen (:obj:`str`, optional): IP-Address to listen on. Default ``127.0.0.1``.
363354
port (:obj:`int`, optional): Port the bot should be listening on. Default ``80``.
@@ -387,8 +378,12 @@ def start_webhook(
387378
.. versionadded :: 13.4
388379
allowed_updates (List[:obj:`str`], optional): Passed to
389380
:meth:`telegram.Bot.set_webhook`.
390-
force_event_loop (:obj:`bool`, optional): Force using the current event loop. See above
391-
note for details. Defaults to :obj:`False`
381+
force_event_loop (:obj:`bool`, optional): Legacy parameter formerly used for a
382+
workaround on Windows + Python 3.8+. No longer has any effect.
383+
384+
.. deprecated:: 13.6
385+
Since version 13.6, ``tornade>=6.1`` is required, which resolves the former
386+
issue.
392387
393388
Returns:
394389
:obj:`Queue`: The update queue that can be filled from the main thread.
@@ -405,6 +400,14 @@ def start_webhook(
405400
stacklevel=2,
406401
)
407402

403+
if force_event_loop is not None:
404+
warnings.warn(
405+
'The argument `force_event_loop` of `start_webhook` is deprecated and no longer '
406+
'has any effect.',
407+
category=TelegramDeprecationWarning,
408+
stacklevel=2,
409+
)
410+
408411
drop_pending_updates = drop_pending_updates if drop_pending_updates is not None else clean
409412

410413
with self.__lock:
@@ -429,7 +432,6 @@ def start_webhook(
429432
webhook_url,
430433
allowed_updates,
431434
ready=webhook_ready,
432-
force_event_loop=force_event_loop,
433435
ip_address=ip_address,
434436
)
435437

@@ -563,7 +565,6 @@ def _start_webhook(
563565
webhook_url,
564566
allowed_updates,
565567
ready=None,
566-
force_event_loop=False,
567568
ip_address=None,
568569
):
569570
self.logger.debug('Updater thread started (webhook)')
@@ -606,7 +607,7 @@ def _start_webhook(
606607
ip_address=ip_address,
607608
)
608609

609-
self.httpd.serve_forever(force_event_loop=force_event_loop, ready=ready)
610+
self.httpd.serve_forever(ready=ready)
610611

611612
@staticmethod
612613
def _gen_webhook_url(listen: str, port: int, url_path: str) -> str:

telegram/ext/utils/webhookhandler.py

Lines changed: 2 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,7 @@
1818
# along with this program. If not, see [http://www.gnu.org/licenses/].
1919
# pylint: disable=C0114
2020

21-
import asyncio
2221
import logging
23-
import os
24-
import sys
2522
from queue import Queue
2623
from ssl import SSLContext
2724
from threading import Event, Lock
@@ -57,11 +54,11 @@ def __init__(
5754
self.server_lock = Lock()
5855
self.shutdown_lock = Lock()
5956

60-
def serve_forever(self, force_event_loop: bool = False, ready: Event = None) -> None:
57+
def serve_forever(self, ready: Event = None) -> None:
6158
with self.server_lock:
59+
IOLoop().make_current()
6260
self.is_running = True
6361
self.logger.debug('Webhook Server started.')
64-
self._ensure_event_loop(force_event_loop=force_event_loop)
6562
self.loop = IOLoop.current()
6663
self.http_server.listen(self.port, address=self.listen)
6764

@@ -87,54 +84,6 @@ def handle_error(self, request: object, client_address: str) -> None: # pylint:
8784
exc_info=True,
8885
)
8986

90-
def _ensure_event_loop(self, force_event_loop: bool = False) -> None:
91-
"""If there's no asyncio event loop set for the current thread - create one."""
92-
try:
93-
loop = asyncio.get_event_loop()
94-
if (
95-
not force_event_loop
96-
and os.name == 'nt'
97-
and sys.version_info >= (3, 8)
98-
and isinstance(loop, asyncio.ProactorEventLoop) # type: ignore[attr-defined]
99-
):
100-
raise TypeError(
101-
'`ProactorEventLoop` is incompatible with '
102-
'Tornado. Please switch to `SelectorEventLoop`.'
103-
)
104-
except RuntimeError:
105-
# Python 3.8 changed default asyncio event loop implementation on windows
106-
# from SelectorEventLoop to ProactorEventLoop. At the time of this writing
107-
# Tornado doesn't support ProactorEventLoop and suggests that end users
108-
# change asyncio event loop policy to WindowsSelectorEventLoopPolicy.
109-
# https://github.com/tornadoweb/tornado/issues/2608
110-
# To avoid changing the global event loop policy, we manually construct
111-
# a SelectorEventLoop instance instead of using asyncio.new_event_loop().
112-
# Note that the fix is not applied in the main thread, as that can break
113-
# user code in even more ways than changing the global event loop policy can,
114-
# and because Updater always starts its webhook server in a separate thread.
115-
# Ideally, we would want to check that Tornado actually raises the expected
116-
# NotImplementedError, but it's not possible to cleanly recover from that
117-
# exception in current Tornado version.
118-
if (
119-
os.name == 'nt'
120-
and sys.version_info >= (3, 8)
121-
# OS+version check makes hasattr check redundant, but just to be sure
122-
and hasattr(asyncio, 'WindowsProactorEventLoopPolicy')
123-
and (
124-
isinstance(
125-
asyncio.get_event_loop_policy(),
126-
asyncio.WindowsProactorEventLoopPolicy, # type: ignore # pylint: disable
127-
)
128-
)
129-
): # pylint: disable=E1101
130-
self.logger.debug(
131-
'Applying Tornado asyncio event loop fix for Python 3.8+ on Windows'
132-
)
133-
loop = asyncio.SelectorEventLoop()
134-
else:
135-
loop = asyncio.new_event_loop()
136-
asyncio.set_event_loop(loop)
137-
13887

13988
class WebhookAppClass(tornado.web.Application):
14089
def __init__(self, webhook_path: str, bot: 'Bot', update_queue: Queue):

tests/test_updater.py

Lines changed: 4 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -221,101 +221,6 @@ def test_start_webhook_no_warning_or_error_logs(self, caplog, updater, monkeypat
221221
updater.stop()
222222
assert not caplog.records
223223

224-
@pytest.mark.skipif(
225-
os.name != 'nt' or sys.version_info < (3, 8),
226-
reason='Workaround only relevant on windows with py3.8+',
227-
)
228-
def test_start_webhook_ensure_event_loop(self, updater, monkeypatch):
229-
def serve_forever(self, force_event_loop=False, ready=None):
230-
with self.server_lock:
231-
self.is_running = True
232-
self._ensure_event_loop(force_event_loop=force_event_loop)
233-
234-
if ready is not None:
235-
ready.set()
236-
237-
monkeypatch.setattr(WebhookServer, 'serve_forever', serve_forever)
238-
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
239-
monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
240-
241-
ip = '127.0.0.1'
242-
port = randrange(1024, 49152) # Select random port
243-
244-
with set_asyncio_event_loop(None):
245-
updater._start_webhook(
246-
ip,
247-
port,
248-
url_path='TOKEN',
249-
cert=None,
250-
key=None,
251-
bootstrap_retries=0,
252-
drop_pending_updates=False,
253-
webhook_url=None,
254-
allowed_updates=None,
255-
)
256-
257-
assert isinstance(asyncio.get_event_loop(), asyncio.SelectorEventLoop)
258-
259-
@pytest.mark.skipif(
260-
os.name != 'nt' or sys.version_info < (3, 8),
261-
reason='Workaround only relevant on windows with py3.8+',
262-
)
263-
def test_start_webhook_force_event_loop_false(self, updater, monkeypatch):
264-
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
265-
monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
266-
267-
ip = '127.0.0.1'
268-
port = randrange(1024, 49152) # Select random port
269-
270-
with set_asyncio_event_loop(asyncio.ProactorEventLoop()):
271-
with pytest.raises(TypeError, match='`ProactorEventLoop` is incompatible'):
272-
updater._start_webhook(
273-
ip,
274-
port,
275-
url_path='TOKEN',
276-
cert=None,
277-
key=None,
278-
bootstrap_retries=0,
279-
drop_pending_updates=False,
280-
webhook_url=None,
281-
allowed_updates=None,
282-
)
283-
284-
@pytest.mark.skipif(
285-
os.name != 'nt' or sys.version_info < (3, 8),
286-
reason='Workaround only relevant on windows with py3.8+',
287-
)
288-
def test_start_webhook_force_event_loop_true(self, updater, monkeypatch):
289-
def serve_forever(self, force_event_loop=False, ready=None):
290-
with self.server_lock:
291-
self.is_running = True
292-
self._ensure_event_loop(force_event_loop=force_event_loop)
293-
294-
if ready is not None:
295-
ready.set()
296-
297-
monkeypatch.setattr(WebhookServer, 'serve_forever', serve_forever)
298-
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
299-
monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
300-
301-
ip = '127.0.0.1'
302-
port = randrange(1024, 49152) # Select random port
303-
304-
with set_asyncio_event_loop(asyncio.ProactorEventLoop()):
305-
updater._start_webhook(
306-
ip,
307-
port,
308-
url_path='TOKEN',
309-
cert=None,
310-
key=None,
311-
bootstrap_retries=0,
312-
drop_pending_updates=False,
313-
webhook_url=None,
314-
allowed_updates=None,
315-
force_event_loop=True,
316-
)
317-
assert isinstance(asyncio.get_event_loop(), asyncio.ProactorEventLoop)
318-
319224
def test_webhook_ssl(self, monkeypatch, updater):
320225
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
321226
monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
@@ -450,7 +355,7 @@ def delete_webhook(**kwargs):
450355
)
451356
assert self.test_flag is True
452357

453-
def test_clean_deprecation_warning_webhook(self, recwarn, updater, monkeypatch):
358+
def test_deprecation_warnings_start_webhook(self, recwarn, updater, monkeypatch):
454359
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
455360
monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
456361
# prevent api calls from @info decorator when updater.bot.id is used in thread names
@@ -459,11 +364,12 @@ def test_clean_deprecation_warning_webhook(self, recwarn, updater, monkeypatch):
459364

460365
ip = '127.0.0.1'
461366
port = randrange(1024, 49152) # Select random port
462-
updater.start_webhook(ip, port, clean=True)
367+
updater.start_webhook(ip, port, clean=True, force_event_loop=False)
463368
updater.stop()
464-
assert len(recwarn) == 2
369+
assert len(recwarn) == 3
465370
assert str(recwarn[0].message).startswith('Old Handler API')
466371
assert str(recwarn[1].message).startswith('The argument `clean` of')
372+
assert str(recwarn[2].message).startswith('The argument `force_event_loop` of')
467373

468374
def test_clean_deprecation_warning_polling(self, recwarn, updater, monkeypatch):
469375
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)

0 commit comments

Comments
 (0)