Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle deprecations in python 3.10 #3097

Merged
merged 9 commits into from
Jan 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ jobs:
tox_env: py38-full
- python: '3.9'
tox_env: py39-full
- python: 'pypy3'
- python: '3.10'
tox_env: py310-full
- python: 'pypy-3.8'
# Pypy is a lot slower due to jit warmup costs, so don't run the
# "full" test config there.
tox_env: pypy3
Expand Down
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ installed in this way, so you may wish to download a copy of the
source tarball or clone the `git repository
<https://github.com/tornadoweb/tornado>`_ as well.

**Prerequisites**: Tornado 6.0 requires Python 3.6 or newer (See
**Prerequisites**: Tornado 6.0 requires Python 3.7 or newer (See
`Tornado 5.1 <https://www.tornadoweb.org/en/branch5.1/>`_ if
compatibility with Python 2.7 is required). The following optional
packages may be useful:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tool.cibuildwheel]
build = "cp3[789]*"
build = "cp3[789]* cp310*"
test-command = "python -m tornado.test"

[tool.cibuildwheel.macos]
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
license_file = LICENSE

[mypy]
python_version = 3.6
python_version = 3.7
no_implicit_optional = True

[mypy-tornado.*,tornado.platform.*]
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def build_extension(self, ext):


if setuptools is not None:
python_requires = ">= 3.6"
python_requires = ">= 3.7"
kwargs["python_requires"] = python_requires

setup(
Expand Down Expand Up @@ -180,10 +180,10 @@ def build_extension(self, ext):
classifiers=[
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
],
Expand Down
39 changes: 30 additions & 9 deletions tornado/ioloop.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import time
import math
import random
import warnings
from inspect import isawaitable

from tornado.concurrent import (
Expand Down Expand Up @@ -86,6 +87,7 @@ class IOLoop(Configurable):

.. testcode::

import asyncio
import errno
import functools
import socket
Expand All @@ -108,7 +110,7 @@ def connection_ready(sock, fd, events):
io_loop = tornado.ioloop.IOLoop.current()
io_loop.spawn_callback(handle_connection, connection, address)

if __name__ == '__main__':
async def main():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setblocking(0)
Expand All @@ -118,18 +120,18 @@ def connection_ready(sock, fd, events):
io_loop = tornado.ioloop.IOLoop.current()
callback = functools.partial(connection_ready, sock)
io_loop.add_handler(sock.fileno(), callback, io_loop.READ)
io_loop.start()
await asyncio.Event().wait()

if __name__ == "__main__":
asyncio.run(main())

.. testoutput::
:hide:

By default, a newly-constructed `IOLoop` becomes the thread's current
`IOLoop`, unless there already is a current `IOLoop`. This behavior
can be controlled with the ``make_current`` argument to the `IOLoop`
constructor: if ``make_current=True``, the new `IOLoop` will always
try to become current and it raises an error if there is already a
current instance. If ``make_current=False``, the new `IOLoop` will
not try to become current.
Do not attempt to construct an `IOLoop` directly; this is deprecated
since Tornado 6.2. Instead, initialize the `asyncio` event loop and
use `IOLoop.current()` to access an `IOLoop` wrapper around the
current event loop.

In general, an `IOLoop` cannot survive a fork or be shared across
processes in any way. When multiple processes are being used, each
Expand All @@ -151,6 +153,12 @@ def connection_ready(sock, fd, events):
``IOLoop.configure`` method cannot be used on Python 3 except
to redundantly specify the `asyncio` event loop.

.. deprecated:: 6.2
It is deprecated to create an event loop that is "current" but not
currently running. This means it is deprecated to pass
``make_current=True`` to the ``IOLoop`` constructor, or to create
an ``IOLoop`` while no asyncio event loop is running unless
``make_current=False`` is used.
"""

# These constants were originally based on constants from the epoll module.
Expand Down Expand Up @@ -259,6 +267,10 @@ def current(instance: bool = True) -> Optional["IOLoop"]: # noqa: F811
an alias for this method). ``instance=False`` is deprecated,
since even if we do not create an `IOLoop`, this method
may initialize the asyncio loop.

.. deprecated:: 6.2
It is deprecated to call ``IOLoop.current()`` when no `asyncio`
event loop is running.
"""
try:
loop = asyncio.get_event_loop()
Expand Down Expand Up @@ -292,6 +304,13 @@ def make_current(self) -> None:

.. versionchanged:: 5.0
This method also sets the current `asyncio` event loop.

.. deprecated:: 6.2
The concept of an event loop that is "current" without
currently running is deprecated in asyncio since Python
3.10. All related functionality in Tornado is also
deprecated. Instead, start the event loop with `asyncio.run`
before interacting with it.
"""
# The asyncio event loops override this method.
raise NotImplementedError()
Expand All @@ -304,7 +323,9 @@ def clear_current() -> None:

.. versionchanged:: 5.0
This method also clears the current `asyncio` event loop.
.. deprecated:: 6.2
"""
warnings.warn("clear_current is deprecated", DeprecationWarning)
old = IOLoop.current(instance=False)
if old is not None:
old._clear_current_hook()
Expand Down
1 change: 1 addition & 0 deletions tornado/iostream.py
Original file line number Diff line number Diff line change
Expand Up @@ -1530,6 +1530,7 @@ def _handle_connect(self) -> None:
self._ssl_options,
server_hostname=self._server_hostname,
do_handshake_on_connect=False,
server_side=False,
)
self._add_io_state(old_state)

Expand Down
40 changes: 33 additions & 7 deletions tornado/netutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,8 @@ def resolve(


def ssl_options_to_context(
ssl_options: Union[Dict[str, Any], ssl.SSLContext]
ssl_options: Union[Dict[str, Any], ssl.SSLContext],
server_side: Optional[bool] = None,
) -> ssl.SSLContext:
"""Try to convert an ``ssl_options`` dictionary to an
`~ssl.SSLContext` object.
Expand All @@ -570,19 +571,34 @@ def ssl_options_to_context(
`~ssl.SSLContext` equivalent, and may be used when a component which
accepts both forms needs to upgrade to the `~ssl.SSLContext` version
to use features like SNI or NPN.

.. versionchanged:: 6.2

Added server_side argument. Omitting this argument will
result in a DeprecationWarning on Python 3.10.

"""
if isinstance(ssl_options, ssl.SSLContext):
return ssl_options
assert isinstance(ssl_options, dict)
assert all(k in _SSL_CONTEXT_KEYWORDS for k in ssl_options), ssl_options
# Can't use create_default_context since this interface doesn't
# tell us client vs server.
context = ssl.SSLContext(ssl_options.get("ssl_version", ssl.PROTOCOL_SSLv23))
# TODO: Now that we have the server_side argument, can we switch to
# create_default_context or would that change behavior?
default_version = ssl.PROTOCOL_TLS
if server_side:
default_version = ssl.PROTOCOL_TLS_SERVER
elif server_side is not None:
default_version = ssl.PROTOCOL_TLS_CLIENT
context = ssl.SSLContext(ssl_options.get("ssl_version", default_version))
if "certfile" in ssl_options:
context.load_cert_chain(
ssl_options["certfile"], ssl_options.get("keyfile", None)
)
if "cert_reqs" in ssl_options:
if ssl_options["cert_reqs"] == ssl.CERT_NONE:
# This may have been set automatically by PROTOCOL_TLS_CLIENT but is
# incompatible with CERT_NONE so we must manually clear it.
context.check_hostname = False
context.verify_mode = ssl_options["cert_reqs"]
if "ca_certs" in ssl_options:
context.load_verify_locations(ssl_options["ca_certs"])
Expand All @@ -601,6 +617,7 @@ def ssl_wrap_socket(
socket: socket.socket,
ssl_options: Union[Dict[str, Any], ssl.SSLContext],
server_hostname: Optional[str] = None,
server_side: Optional[bool] = None,
**kwargs: Any
) -> ssl.SSLSocket:
"""Returns an ``ssl.SSLSocket`` wrapping the given socket.
Expand All @@ -610,14 +627,23 @@ def ssl_wrap_socket(
keyword arguments are passed to ``wrap_socket`` (either the
`~ssl.SSLContext` method or the `ssl` module function as
appropriate).

.. versionchanged:: 6.2

Added server_side argument. Omitting this argument will
result in a DeprecationWarning on Python 3.10.
"""
context = ssl_options_to_context(ssl_options)
context = ssl_options_to_context(ssl_options, server_side=server_side)
if server_side is None:
server_side = False
if ssl.HAS_SNI:
# In python 3.4, wrap_socket only accepts the server_hostname
# argument if HAS_SNI is true.
# TODO: add a unittest (python added server-side SNI support in 3.4)
# In the meantime it can be manually tested with
# python3 -m tornado.httpclient https://sni.velox.ch
return context.wrap_socket(socket, server_hostname=server_hostname, **kwargs)
return context.wrap_socket(
socket, server_hostname=server_hostname, server_side=server_side, **kwargs
)
else:
return context.wrap_socket(socket, **kwargs)
return context.wrap_socket(socket, server_side=server_side, **kwargs)
32 changes: 30 additions & 2 deletions tornado/platform/asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import sys
import threading
import typing
import warnings
from tornado.gen import convert_yielded
from tornado.ioloop import IOLoop, _Selectable

Expand Down Expand Up @@ -190,7 +191,9 @@ def _handle_events(self, fd: int, events: int) -> None:

def start(self) -> None:
try:
old_loop = asyncio.get_event_loop()
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
old_loop = asyncio.get_event_loop()
except (RuntimeError, AssertionError):
old_loop = None # type: ignore
try:
Expand Down Expand Up @@ -321,10 +324,18 @@ def initialize(self, **kwargs: Any) -> None: # type: ignore

def close(self, all_fds: bool = False) -> None:
if self.is_current:
self.clear_current()
with warnings.catch_warnings():
# We can't get here unless the warning in make_current
# was swallowed, so swallow the one from clear_current too.
warnings.simplefilter("ignore", DeprecationWarning)
self.clear_current()
super().close(all_fds=all_fds)

def make_current(self) -> None:
warnings.warn(
"make_current is deprecated; start the event loop first",
DeprecationWarning,
)
if not self.is_current:
try:
self.old_asyncio = asyncio.get_event_loop()
Expand Down Expand Up @@ -391,8 +402,25 @@ class AnyThreadEventLoopPolicy(_BasePolicy): # type: ignore

.. versionadded:: 5.0

.. deprecated:: 6.2

``AnyThreadEventLoopPolicy`` affects the implicit creation
of an event loop, which is deprecated in Python 3.10 and
will be removed in a future version of Python. At that time
``AnyThreadEventLoopPolicy`` will no longer be useful.
If you are relying on it, use `asyncio.new_event_loop`
or `asyncio.run` explicitly in any non-main threads that
need event loops.
"""

def __init__(self) -> None:
super().__init__()
warnings.warn(
"AnyThreadEventLoopPolicy is deprecated, use asyncio.run "
"or asyncio.new_event_loop instead",
DeprecationWarning,
)

def get_event_loop(self) -> asyncio.AbstractEventLoop:
try:
return super().get_event_loop()
Expand Down
Loading