Repository: jupyterhub/jupyterhub

Issue:
Minimum patch level requirement for Python 3.5 in 0.9-dev.
**How to reproduce the issue**

Use Python 3.5.1 with 0.9-dev.

Use KubeSpawner (or maybe something else where time to start notebook instance is long time).

Click on My Server.

Will be presented with startup progress bar page, but it stalls and doesn't do anything.

**What you expected to happen**

Ideally work, but realistically you might also simply document that there is a minimum patch level requirement if using Python 3.5 that is greater than 3.5.1. After bit of discussion on gitter with @minrk speculated that may work from 3.5.2 onwards due to asyncio related fixes in 3.5.2. May not be worth trying to provide compatibility with 3.5.1.

This would affect users of Python 3.5 from Software Collections Library (SCL) for Red Hat operating system family, as that has 3.5.1. They would have access to Python 3.6 as well from SCL and 3.6 works fine so no reason they couldn't use 3.6.

**What actually happens**

In addition to the startup progress bar page stalling, you get an error in the logs of:

```

[E 2018-06-06 00:07:39.571 JupyterHub web:1621] Uncaught exception GET /hub/api/users/grahamdumpleton/server/progress (::ffff:10.128.2.1)
--
  | HTTPServerRequest(protocol='http', host='poc-dask-cluster-2-dask-cluster.4b63.pro-ap-southeast-2.openshiftapps.com', method='GET', uri='/hub/api/users/grahamdumpleton/server/progress', version='HTTP/1.1', remote_ip='::ffff:10.128.2.1')
  | Traceback (most recent call last):
  | File "/opt/app-root/lib/python3.5/site-packages/tornado/web.py", line 1543, in _execute
  | result = yield result
  | File "/opt/rh/rh-python35/root/usr/lib64/python3.5/asyncio/futures.py", line 274, in result
  | raise self._exception
  | File "/opt/rh/rh-python35/root/usr/lib64/python3.5/asyncio/tasks.py", line 239, in _step
  | result = coro.send(None)
  | File "/opt/app-root/lib/python3.5/site-packages/jupyterhub/apihandlers/users.py", line 496, in get
  | async for event in events:
  | File "/opt/app-root/lib/python3.5/site-packages/async_generator/_impl.py", line 271, in __anext__
  | return await self._do_it(self._it.__next__)
  | File "/opt/app-root/lib/python3.5/site-packages/async_generator/_impl.py", line 290, in _do_it
  | return await ANextIter(self._it, start_fn, *args)
  | File "/opt/app-root/lib/python3.5/site-packages/async_generator/_impl.py", line 197, in __next__
  | return self._invoke(first_fn, *first_args)
  | File "/opt/app-root/lib/python3.5/site-packages/async_generator/_impl.py", line 209, in _invoke
  | result = fn(*args)
  | File "/opt/app-root/lib/python3.5/site-packages/jupyterhub/utils.py", line 497, in iterate_until
  | continue
  | File "/opt/app-root/lib/python3.5/site-packages/async_generator/_util.py", line 14, in __aexit__
  | await self._aiter.aclose()
  | AttributeError: 'coroutine' object has no attribute 'aclose'

```

**Share what version of JupyterHub you are using**

0.9-dev. Current master from repo.


Discussion:

Resolving patch:
diff --git a/jupyterhub/utils.py b/jupyterhub/utils.py
--- a/jupyterhub/utils.py
+++ b/jupyterhub/utils.py
@@ -19,7 +19,7 @@
 import uuid
 import warnings
 
-from async_generator import aclosing, async_generator, yield_
+from async_generator import aclosing, asynccontextmanager, async_generator, yield_
 from tornado import gen, ioloop, web
 from tornado.platform.asyncio import to_asyncio_future
 from tornado.httpclient import AsyncHTTPClient, HTTPError
@@ -452,6 +452,21 @@ def maybe_future(obj):
         return to_asyncio_future(gen.maybe_future(obj))
 
 
+@asynccontextmanager
+@async_generator
+async def not_aclosing(coro):
+    """An empty context manager for Python < 3.5.2
+    which lacks the `aclose` method on async iterators
+    """
+    await yield_(await coro)
+
+
+if sys.version_info < (3, 5, 2):
+    # Python 3.5.1 is missing the aclose method on async iterators,
+    # so we can't close them
+    aclosing = not_aclosing
+
+
 @async_generator
 async def iterate_until(deadline_future, generator):
     """An async generator that yields items from a generator


Test patch: