stonebig: Jupyterlab and Jupyter Notebook are both broken under Tornado-6.0 in waiting of this.
ploxiln: just mypy is dissatisfied:
```
tornado/websocket.py:561: error: Item "None" of "Optional[WebSocketProtocol]" has no attribute "stream"
```
stonebig: apparently, problem resolved per Notebook-5.7.5

https://github.com/jupyter/notebook/blob/master/docs/source/changelog.rst
diff --git a/maint/scripts/download_wheels.py b/maint/scripts/download_wheels.py
deleted file mode 100755
index 03379058..00000000
--- a/maint/scripts/download_wheels.py
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/usr/bin/env python3
-
-import asyncio
-import json
-import pathlib
-import sys
-from tornado.httpclient import AsyncHTTPClient
-
-BASE_URL = "https://ci.appveyor.com/api"
-
-
-async def fetch_job(directory, job):
-    http = AsyncHTTPClient()
-    artifacts = await http.fetch(f"{BASE_URL}/buildjobs/{job}/artifacts")
-    paths = [pathlib.PurePosixPath(a["fileName"]) for a in json.loads(artifacts.body)]
-
-    for path in paths:
-        artifact = await http.fetch(f"{BASE_URL}/buildjobs/{job}/artifacts/{path}")
-        with open(directory.joinpath(path.name), "wb") as f:
-            f.write(artifact.body)
-
-
-async def main():
-    http = AsyncHTTPClient()
-    try:
-        _, version = sys.argv
-    except ValueError:
-        print("usage: maint/scripts/download_wheels.py v6.0.1", file=sys.stderr)
-        sys.exit(1)
-
-    directory = pathlib.Path(f"downloads-{version}")
-    directory.mkdir(exist_ok=True)
-
-    build = await http.fetch(f"{BASE_URL}/projects/bdarnell/tornado/branch/{version}")
-    jobs = [job["jobId"] for job in json.loads(build.body)["build"]["jobs"]]
-
-    await asyncio.gather(*(fetch_job(directory, job) for job in jobs))
-
-
-if __name__ == "__main__":
-    asyncio.run(main())
diff --git a/tornado/platform/twisted.py b/tornado/platform/twisted.py
index e563094a..38816313 100644
--- a/tornado/platform/twisted.py
+++ b/tornado/platform/twisted.py
@@ -17,6 +17,8 @@
 This module lets you run applications and libraries written for
 Twisted in a Tornado application.  It can be used in two modes,
 depending on which library's underlying event loop you want to use.
+
+This module has been tested with Twisted versions 11.0.0 and newer.
 """
 
 import socket
diff --git a/tornado/websocket.py b/tornado/websocket.py
index 00d08bab..d991fee5 100644
--- a/tornado/websocket.py
+++ b/tornado/websocket.py
@@ -558,8 +558,8 @@ class WebSocketHandler(tornado.web.RequestHandler):
 
         .. versionadded:: 3.1
         """
-        assert self.stream is not None
-        self.stream.set_nodelay(value)
+        assert self.ws_connection is not None
+        self.ws_connection.set_nodelay(value)
 
     def on_connection_close(self) -> None:
         if self.ws_connection:
@@ -714,6 +714,10 @@ class WebSocketProtocol(abc.ABC):
     async def _receive_frame_loop(self) -> None:
         raise NotImplementedError()
 
+    @abc.abstractmethod
+    def set_nodelay(self, x: bool) -> None:
+        raise NotImplementedError()
+
 
 class _PerMessageDeflateCompressor(object):
     def __init__(
@@ -1345,6 +1349,9 @@ class WebSocketProtocol13(WebSocketProtocol):
         self.write_ping(b"")
         self.last_ping = now
 
+    def set_nodelay(self, x: bool) -> None:
+        self.stream.set_nodelay(x)
+
 
 class WebSocketClientConnection(simple_httpclient._HTTPConnection):
     """WebSocket client connection.

WebsocketHandler.set_nodelay results in assertion failure
In previous versions of Torando, WebsocketHandler.set_nodelay could be called from open to set the nodelay option.  In Tornado 6.0, this results in an assertion failure:

  File ".../tornado/websocket.py", line 561, in set_nodelay
    assert self.stream is not None

diff --git a/tornado/test/websocket_test.py b/tornado/test/websocket_test.py
index 715ecf1e..d4b7a741 100644
--- a/tornado/test/websocket_test.py
+++ b/tornado/test/websocket_test.py
@@ -198,6 +198,12 @@ class ErrorInAsyncOpenHandler(TestWebSocketHandler):
         raise Exception("boom")
 
 
+class NoDelayHandler(TestWebSocketHandler):
+    def open(self):
+        self.set_nodelay(True)
+        self.write_message("hello")
+
+
 class WebSocketBaseTestCase(AsyncHTTPTestCase):
     @gen.coroutine
     def ws_connect(self, path, **kwargs):
@@ -258,6 +264,7 @@ class WebSocketTest(WebSocketBaseTestCase):
                 ),
                 ("/error_in_open", ErrorInOpenHandler),
                 ("/error_in_async_open", ErrorInAsyncOpenHandler),
+                ("/nodelay", NoDelayHandler),
             ],
             template_loader=DictLoader({"message.html": "<b>{{ message }}</b>"}),
         )
@@ -562,6 +569,12 @@ class WebSocketTest(WebSocketBaseTestCase):
             res = yield ws.read_message()
         self.assertIsNone(res)
 
+    @gen_test
+    def test_nodelay(self):
+        ws = yield self.ws_connect("/nodelay")
+        res = yield ws.read_message()
+        self.assertEqual(res, "hello")
+
 
 class NativeCoroutineOnMessageHandler(TestWebSocketHandler):
     def initialize(self, **kwargs):

pip install -e .[dev]
parse_log_pytest
pytest
pytest flake8 black mypy
apt-get update apt-get install -y build-essential python3-dev
3.7
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs @ file:///croot/attrs_1668696182826/work
black==23.3.0
certifi @ file:///croot/certifi_1671487769961/work/certifi
click==8.1.8
flake8==5.0.4
flit_core @ file:///opt/conda/conda-bld/flit-core_1644941570762/work/source/flit_core
importlib-metadata==4.2.0
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
mccabe==0.7.0
mypy==1.4.1
mypy-extensions==1.0.0
packaging @ file:///croot/packaging_1671697413597/work
pathspec==0.11.2
platformdirs==4.0.0
pluggy @ file:///tmp/build/80754af9/pluggy_1648042572264/work
py @ file:///opt/conda/conda-bld/py_1644396412707/work
pycodestyle==2.9.1
pyflakes==2.5.0
pytest==7.1.2
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work
-e git+https://github.com/tornadoweb/tornado.git@8c8e154ae0b91df1ca41d0da567e5baa6499d6e4#egg=tornado
typed-ast==1.5.5
typing_extensions==4.7.1
zipp @ file:///croot/zipp_1672387121353/work

name: tornado
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - attrs=22.1.0=py37h06a4308_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - flit-core=3.6.0=pyhd3eb1b0_0
  - importlib_metadata=4.11.3=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=22.0=py37h06a4308_0
  - pip=22.3.1=py37h06a4308_0
  - pluggy=1.0.0=py37h06a4308_1
  - py=1.11.0=pyhd3eb1b0_0
  - pytest=7.1.2=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py37h06a4308_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.11.0=py37h06a4308_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - black==23.3.0
      - click==8.1.8
      - flake8==5.0.4
      - importlib-metadata==4.2.0
      - mccabe==0.7.0
      - mypy==1.4.1
      - mypy-extensions==1.0.0
      - pathspec==0.11.2
      - platformdirs==4.0.0
      - pycodestyle==2.9.1
      - pyflakes==2.5.0
      - typed-ast==1.5.5
      - typing-extensions==4.7.1
prefix: /opt/conda/envs/tornado

tornado/test/websocket_test.py::WebSocketTest::test_nodelay
tornado/test/websocket_test.py::WebSocketTest::test_async_prepare tornado/test/websocket_test.py::WebSocketTest::test_bad_websocket_version tornado/test/websocket_test.py::WebSocketTest::test_binary_message tornado/test/websocket_test.py::WebSocketTest::test_check_origin_invalid tornado/test/websocket_test.py::WebSocketTest::test_check_origin_invalid_partial_url tornado/test/websocket_test.py::WebSocketTest::test_check_origin_invalid_subdomains tornado/test/websocket_test.py::WebSocketTest::test_check_origin_valid_no_path tornado/test/websocket_test.py::WebSocketTest::test_check_origin_valid_with_path tornado/test/websocket_test.py::WebSocketTest::test_client_close_reason tornado/test/websocket_test.py::WebSocketTest::test_coroutine tornado/test/websocket_test.py::WebSocketTest::test_error_in_async_open tornado/test/websocket_test.py::WebSocketTest::test_error_in_on_message tornado/test/websocket_test.py::WebSocketTest::test_error_in_open tornado/test/websocket_test.py::WebSocketTest::test_http_request tornado/test/websocket_test.py::WebSocketTest::test_missing_websocket_key tornado/test/websocket_test.py::WebSocketTest::test_open_coroutine tornado/test/websocket_test.py::WebSocketTest::test_path_args tornado/test/websocket_test.py::WebSocketTest::test_render_message tornado/test/websocket_test.py::WebSocketTest::test_server_close_reason tornado/test/websocket_test.py::WebSocketTest::test_subprotocols tornado/test/websocket_test.py::WebSocketTest::test_subprotocols_not_offered tornado/test/websocket_test.py::WebSocketTest::test_unicode_message tornado/test/websocket_test.py::WebSocketTest::test_websocket_callbacks tornado/test/websocket_test.py::WebSocketTest::test_websocket_close_buffered_data tornado/test/websocket_test.py::WebSocketTest::test_websocket_gen tornado/test/websocket_test.py::WebSocketTest::test_websocket_header_echo tornado/test/websocket_test.py::WebSocketTest::test_websocket_headers tornado/test/websocket_test.py::WebSocketTest::test_websocket_http_fail tornado/test/websocket_test.py::WebSocketTest::test_websocket_http_success tornado/test/websocket_test.py::WebSocketTest::test_websocket_network_fail tornado/test/websocket_test.py::WebSocketTest::test_write_after_close tornado/test/websocket_test.py::WebSocketNativeCoroutineTest::test_native_coroutine tornado/test/websocket_test.py::NoCompressionTest::test_message_sizes tornado/test/websocket_test.py::NoCompressionTest::test_size_limit tornado/test/websocket_test.py::ServerOnlyCompressionTest::test_message_sizes tornado/test/websocket_test.py::ServerOnlyCompressionTest::test_size_limit tornado/test/websocket_test.py::ClientOnlyCompressionTest::test_message_sizes tornado/test/websocket_test.py::ClientOnlyCompressionTest::test_size_limit tornado/test/websocket_test.py::DefaultCompressionTest::test_message_sizes tornado/test/websocket_test.py::DefaultCompressionTest::test_size_limit tornado/test/websocket_test.py::PythonMaskFunctionTest::test_mask tornado/test/websocket_test.py::CythonMaskFunctionTest::test_mask tornado/test/websocket_test.py::ServerPeriodicPingTest::test_server_ping tornado/test/websocket_test.py::ClientPeriodicPingTest::test_client_ping tornado/test/websocket_test.py::ManualPingTest::test_manual_ping tornado/test/websocket_test.py::MaxMessageSizeTest::test_large_message
maint/scripts/download_wheels.py tornado/websocket.py tornado/platform/twisted.py
maint/scripts/download_wheels.py tornado/websocket.py tornado/platform/twisted.py