Skip to content

Commit 527e6c3

Browse files
authored
Merge pull request tornadoweb#2361 from bdarnell/ioloop-deprecation
iostream: Deprecate callback-based interfaces
2 parents 7d4c4cb + 32f58bf commit 527e6c3

9 files changed

+583
-471
lines changed

tornado/ioloop.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,8 @@ def set_blocking_signal_threshold(self, seconds, action):
441441
.. deprecated:: 5.0
442442
443443
Not implemented on the `asyncio` event loop. Use the environment
444-
variable ``PYTHONASYNCIODEBUG=1`` instead.
444+
variable ``PYTHONASYNCIODEBUG=1`` instead. This method will be
445+
removed in Tornado 6.0.
445446
"""
446447
raise NotImplementedError()
447448

@@ -455,14 +456,19 @@ def set_blocking_log_threshold(self, seconds):
455456
.. deprecated:: 5.0
456457
457458
Not implemented on the `asyncio` event loop. Use the environment
458-
variable ``PYTHONASYNCIODEBUG=1`` instead.
459+
variable ``PYTHONASYNCIODEBUG=1`` instead. This method will be
460+
removed in Tornado 6.0.
459461
"""
460462
self.set_blocking_signal_threshold(seconds, self.log_stack)
461463

462464
def log_stack(self, signal, frame):
463465
"""Signal handler to log the stack trace of the current thread.
464466
465467
For use with `set_blocking_signal_threshold`.
468+
469+
.. deprecated:: 5.1
470+
471+
This method will be removed in Tornado 6.0.
466472
"""
467473
gen_log.warning('IOLoop blocked for %f seconds in\n%s',
468474
self._blocking_signal_threshold,
@@ -789,6 +795,16 @@ def handle_callback_exception(self, callback):
789795
790796
The exception itself is not passed explicitly, but is available
791797
in `sys.exc_info`.
798+
799+
.. versionchanged:: 5.0
800+
801+
When the `asyncio` event loop is used (which is now the
802+
default on Python 3), some callback errors will be handled by
803+
`asyncio` instead of this method.
804+
805+
.. deprecated: 5.1
806+
807+
Support for this method will be removed in Tornado 6.0.
792808
"""
793809
app_log.error("Exception in callback %r", callback, exc_info=True)
794810

tornado/iostream.py

Lines changed: 82 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import socket
3434
import sys
3535
import re
36+
import warnings
3637

3738
from tornado.concurrent import Future
3839
from tornado import ioloop
@@ -342,6 +343,12 @@ def read_until_regex(self, regex, callback=None, max_bytes=None):
342343
.. versionchanged:: 4.0
343344
Added the ``max_bytes`` argument. The ``callback`` argument is
344345
now optional and a `.Future` will be returned if it is omitted.
346+
347+
.. deprecated:: 5.1
348+
349+
The ``callback`` argument is deprecated and will be removed
350+
in Tornado 6.0. Use the returned `.Future` instead.
351+
345352
"""
346353
future = self._set_read_callback(callback)
347354
self._read_regex = re.compile(regex)
@@ -375,6 +382,11 @@ def read_until(self, delimiter, callback=None, max_bytes=None):
375382
.. versionchanged:: 4.0
376383
Added the ``max_bytes`` argument. The ``callback`` argument is
377384
now optional and a `.Future` will be returned if it is omitted.
385+
386+
.. deprecated:: 5.1
387+
388+
The ``callback`` argument is deprecated and will be removed
389+
in Tornado 6.0. Use the returned `.Future` instead.
378390
"""
379391
future = self._set_read_callback(callback)
380392
self._read_delimiter = delimiter
@@ -408,12 +420,23 @@ def read_bytes(self, num_bytes, callback=None, streaming_callback=None,
408420
.. versionchanged:: 4.0
409421
Added the ``partial`` argument. The callback argument is now
410422
optional and a `.Future` will be returned if it is omitted.
423+
424+
.. deprecated:: 5.1
425+
426+
The ``callback`` and ``streaming_callback`` arguments are
427+
deprecated and will be removed in Tornado 6.0. Use the
428+
returned `.Future` (and ``partial=True`` for
429+
``streaming_callback``) instead.
430+
411431
"""
412432
future = self._set_read_callback(callback)
413433
assert isinstance(num_bytes, numbers.Integral)
414434
self._read_bytes = num_bytes
415435
self._read_partial = partial
416-
self._streaming_callback = stack_context.wrap(streaming_callback)
436+
if streaming_callback is not None:
437+
warnings.warn("streaming_callback is deprecated, use partial instead",
438+
DeprecationWarning)
439+
self._streaming_callback = stack_context.wrap(streaming_callback)
417440
try:
418441
self._try_inline_read()
419442
except:
@@ -434,6 +457,12 @@ def read_into(self, buf, callback=None, partial=False):
434457
entirely filled with read data.
435458
436459
.. versionadded:: 5.0
460+
461+
.. deprecated:: 5.1
462+
463+
The ``callback`` argument is deprecated and will be removed
464+
in Tornado 6.0. Use the returned `.Future` instead.
465+
437466
"""
438467
future = self._set_read_callback(callback)
439468

@@ -485,9 +514,19 @@ def read_until_close(self, callback=None, streaming_callback=None):
485514
The callback argument is now optional and a `.Future` will
486515
be returned if it is omitted.
487516
517+
.. deprecated:: 5.1
518+
519+
The ``callback`` and ``streaming_callback`` arguments are
520+
deprecated and will be removed in Tornado 6.0. Use the
521+
returned `.Future` (and `read_bytes` with ``partial=True``
522+
for ``streaming_callback``) instead.
523+
488524
"""
489525
future = self._set_read_callback(callback)
490-
self._streaming_callback = stack_context.wrap(streaming_callback)
526+
if streaming_callback is not None:
527+
warnings.warn("streaming_callback is deprecated, use read_bytes(partial=True) instead",
528+
DeprecationWarning)
529+
self._streaming_callback = stack_context.wrap(streaming_callback)
491530
if self.closed():
492531
if self._streaming_callback is not None:
493532
self._run_read_callback(self._read_buffer_size, True)
@@ -521,6 +560,12 @@ def write(self, data, callback=None):
521560
522561
.. versionchanged:: 4.5
523562
Added support for `memoryview` arguments.
563+
564+
.. deprecated:: 5.1
565+
566+
The ``callback`` argument is deprecated and will be removed
567+
in Tornado 6.0. Use the returned `.Future` instead.
568+
524569
"""
525570
self._check_closed()
526571
if data:
@@ -530,6 +575,8 @@ def write(self, data, callback=None):
530575
self._write_buffer.append(data)
531576
self._total_write_index += len(data)
532577
if callback is not None:
578+
warnings.warn("callback argument is deprecated, use returned Future instead",
579+
DeprecationWarning)
533580
self._write_callback = stack_context.wrap(callback)
534581
future = None
535582
else:
@@ -546,9 +593,14 @@ def write(self, data, callback=None):
546593
def set_close_callback(self, callback):
547594
"""Call the given callback when the stream is closed.
548595
549-
This is not necessary for applications that use the `.Future`
550-
interface; all outstanding ``Futures`` will resolve with a
551-
`StreamClosedError` when the stream is closed.
596+
This mostly is not necessary for applications that use the
597+
`.Future` interface; all outstanding ``Futures`` will resolve
598+
with a `StreamClosedError` when the stream is closed. However,
599+
it is still useful as a way to signal that the stream has been
600+
closed while no other read or write is in progress.
601+
602+
Unlike other callback-based interfaces, ``set_close_callback``
603+
will not be removed in Tornado 6.0.
552604
"""
553605
self._close_callback = stack_context.wrap(callback)
554606
self._maybe_add_error_listener()
@@ -807,6 +859,8 @@ def _set_read_callback(self, callback):
807859
assert self._read_callback is None, "Already reading"
808860
assert self._read_future is None, "Already reading"
809861
if callback is not None:
862+
warnings.warn("callbacks are deprecated, use returned Future instead",
863+
DeprecationWarning)
810864
self._read_callback = stack_context.wrap(callback)
811865
else:
812866
self._read_future = Future()
@@ -1237,9 +1291,17 @@ class is recommended instead of calling this method directly.
12371291
``ssl_options=dict(cert_reqs=ssl.CERT_NONE)`` or a
12381292
suitably-configured `ssl.SSLContext` to the
12391293
`SSLIOStream` constructor to disable.
1294+
1295+
.. deprecated:: 5.1
1296+
1297+
The ``callback`` argument is deprecated and will be removed
1298+
in Tornado 6.0. Use the returned `.Future` instead.
1299+
12401300
"""
12411301
self._connecting = True
12421302
if callback is not None:
1303+
warnings.warn("callback argument is deprecated, use returned Future instead",
1304+
DeprecationWarning)
12431305
self._connect_callback = stack_context.wrap(callback)
12441306
future = None
12451307
else:
@@ -1523,9 +1585,13 @@ def _handle_write(self):
15231585

15241586
def connect(self, address, callback=None, server_hostname=None):
15251587
self._server_hostname = server_hostname
1526-
# Pass a dummy callback to super.connect(), which is slightly
1527-
# more efficient than letting it return a Future we ignore.
1528-
super(SSLIOStream, self).connect(address, callback=lambda: None)
1588+
# Ignore the result of connect(). If it fails,
1589+
# wait_for_handshake will raise an error too. This is
1590+
# necessary for the old semantics of the connect callback
1591+
# (which takes no arguments). In 6.0 this can be refactored to
1592+
# be a regular coroutine.
1593+
fut = super(SSLIOStream, self).connect(address)
1594+
fut.add_done_callback(lambda f: f.exception())
15291595
return self.wait_for_handshake(callback)
15301596

15311597
def _handle_connect(self):
@@ -1569,11 +1635,19 @@ def wait_for_handshake(self, callback=None):
15691635
handshake to complete). It may only be called once per stream.
15701636
15711637
.. versionadded:: 4.2
1638+
1639+
.. deprecated:: 5.1
1640+
1641+
The ``callback`` argument is deprecated and will be removed
1642+
in Tornado 6.0. Use the returned `.Future` instead.
1643+
15721644
"""
15731645
if (self._ssl_connect_callback is not None or
15741646
self._ssl_connect_future is not None):
15751647
raise RuntimeError("Already waiting")
15761648
if callback is not None:
1649+
warnings.warn("callback argument is deprecated, use returned Future instead",
1650+
DeprecationWarning)
15771651
self._ssl_connect_callback = stack_context.wrap(callback)
15781652
future = None
15791653
else:

tornado/test/concurrent_test.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import socket
2121
import sys
2222
import traceback
23+
import warnings
2324

2425
from tornado.concurrent import (Future, return_future, ReturnValueIgnoredError,
2526
run_on_executor, future_set_result_unless_cancelled)
@@ -249,20 +250,16 @@ def f():
249250

250251

251252
class CapServer(TCPServer):
253+
@gen.coroutine
252254
def handle_stream(self, stream, address):
253-
logging.debug("handle_stream")
254-
self.stream = stream
255-
self.stream.read_until(b"\n", self.handle_read)
256-
257-
def handle_read(self, data):
258-
logging.debug("handle_read")
255+
data = yield stream.read_until(b"\n")
259256
data = to_unicode(data)
260257
if data == data.upper():
261-
self.stream.write(b"error\talready capitalized\n")
258+
stream.write(b"error\talready capitalized\n")
262259
else:
263260
# data already has \n
264-
self.stream.write(utf8("ok\t%s" % data.upper()))
265-
self.stream.close()
261+
stream.write(utf8("ok\t%s" % data.upper()))
262+
stream.close()
266263

267264

268265
class CapError(Exception):
@@ -397,10 +394,30 @@ def f():
397394
class ManualClientTest(ClientTestMixin, AsyncTestCase):
398395
client_class = ManualCapClient
399396

397+
def setUp(self):
398+
self.warning_catcher = warnings.catch_warnings()
399+
self.warning_catcher.__enter__()
400+
warnings.simplefilter('ignore', DeprecationWarning)
401+
super(ManualClientTest, self).setUp()
402+
403+
def tearDown(self):
404+
super(ManualClientTest, self).tearDown()
405+
self.warning_catcher.__exit__(None, None, None)
406+
400407

401408
class DecoratorClientTest(ClientTestMixin, AsyncTestCase):
402409
client_class = DecoratorCapClient
403410

411+
def setUp(self):
412+
self.warning_catcher = warnings.catch_warnings()
413+
self.warning_catcher.__enter__()
414+
warnings.simplefilter('ignore', DeprecationWarning)
415+
super(DecoratorClientTest, self).setUp()
416+
417+
def tearDown(self):
418+
super(DecoratorClientTest, self).tearDown()
419+
self.warning_catcher.__exit__(None, None, None)
420+
404421

405422
class GeneratorClientTest(ClientTestMixin, AsyncTestCase):
406423
client_class = GeneratorCapClient

tornado/test/httpclient_test.py

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import binascii
55
from contextlib import closing
66
import copy
7-
import functools
87
import sys
98
import threading
109
import datetime
@@ -190,10 +189,15 @@ def test_chunked_close(self):
190189
# over several ioloop iterations, but the connection is already closed.
191190
sock, port = bind_unused_port()
192191
with closing(sock):
193-
def write_response(stream, request_data):
192+
@gen.coroutine
193+
def accept_callback(conn, address):
194+
# fake an HTTP server using chunked encoding where the final chunks
195+
# and connection close all happen at once
196+
stream = IOStream(conn)
197+
request_data = yield stream.read_until(b"\r\n\r\n")
194198
if b"HTTP/1." not in request_data:
195199
self.skipTest("requires HTTP/1.x")
196-
stream.write(b"""\
200+
yield stream.write(b"""\
197201
HTTP/1.1 200 OK
198202
Transfer-Encoding: chunked
199203
@@ -203,14 +207,8 @@ def write_response(stream, request_data):
203207
2
204208
0
205209
206-
""".replace(b"\n", b"\r\n"), callback=stream.close)
207-
208-
def accept_callback(conn, address):
209-
# fake an HTTP server using chunked encoding where the final chunks
210-
# and connection close all happen at once
211-
stream = IOStream(conn)
212-
stream.read_until(b"\r\n\r\n",
213-
functools.partial(write_response, stream))
210+
""".replace(b"\n", b"\r\n"))
211+
stream.close()
214212
netutil.add_accept_handler(sock, accept_callback)
215213
resp = self.fetch("http://127.0.0.1:%d/" % port)
216214
resp.rethrow()
@@ -383,20 +381,20 @@ def test_multi_line_headers(self):
383381
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
384382
sock, port = bind_unused_port()
385383
with closing(sock):
386-
def write_response(stream, request_data):
384+
@gen.coroutine
385+
def accept_callback(conn, address):
386+
stream = IOStream(conn)
387+
request_data = yield stream.read_until(b"\r\n\r\n")
387388
if b"HTTP/1." not in request_data:
388389
self.skipTest("requires HTTP/1.x")
389-
stream.write(b"""\
390+
yield stream.write(b"""\
390391
HTTP/1.1 200 OK
391392
X-XSS-Protection: 1;
392393
\tmode=block
393394
394-
""".replace(b"\n", b"\r\n"), callback=stream.close)
395+
""".replace(b"\n", b"\r\n"))
396+
stream.close()
395397

396-
def accept_callback(conn, address):
397-
stream = IOStream(conn)
398-
stream.read_until(b"\r\n\r\n",
399-
functools.partial(write_response, stream))
400398
netutil.add_accept_handler(sock, accept_callback)
401399
resp = self.fetch("http://127.0.0.1:%d/" % port)
402400
resp.rethrow()

0 commit comments

Comments
 (0)