Merge pull request #37 from dave-shawley/py39

asyncio.Task.all_tasks was removed in Python 3.9
This commit is contained in:
Andrew Rabert 2020-07-27 13:32:45 -04:00 committed by GitHub
commit bc19924904
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 74 additions and 33 deletions

View file

@ -1,9 +1,11 @@
language: python language: python
os: linux
dist: xenial dist: xenial
python: python:
- 3.5 - 3.5
- 3.6 - 3.6
- 3.7 - 3.7
- 3.8
install: install:
- pip install -r requires/development.txt codecov - pip install -r requires/development.txt codecov
script: script:
@ -12,11 +14,10 @@ script:
- flake8 - flake8
after_success: after_success:
- codecov - codecov
sudo: false
deploy: deploy:
distributions: sdist bdist_wheel distributions: sdist bdist_wheel
provider: pypi provider: pypi
user: sprockets username: sprockets
password: password:
secure: ARvFw5CHqQZqPOkJXxQSe7EAEbX1yt7FiBTtzz8Gd6XndbY10HVCSWhGYeldm9LevvQc9p77pBEvsl+bXGQbJ3NW/r/U5PADaFdmi4bxmXN8yc+dFKzn72MpZfL+kCV2T/HutuOY6dQa4okTkKVV+sqwPLKPhL69zH/PxQg8qe4= secure: ARvFw5CHqQZqPOkJXxQSe7EAEbX1yt7FiBTtzz8Gd6XndbY10HVCSWhGYeldm9LevvQc9p77pBEvsl+bXGQbJ3NW/r/U5PADaFdmi4bxmXN8yc+dFKzn72MpZfL+kCV2T/HutuOY6dQa4okTkKVV+sqwPLKPhL69zH/PxQg8qe4=
on: on:

View file

@ -3,6 +3,13 @@
Release History Release History
=============== ===============
`Next release`_
---------------
- Updated to support Python 3.9. ``asyncio.Task.all_tasks`` was removed
so I switched to ``asyncio.all_tasks`` if it exists.
- Deprecate calling ``sprockets.http.run`` with anything that isn't a
``sprockets.app.Application`` instance.
`2.1.1`_ (19 Feb 2020) `2.1.1`_ (19 Feb 2020)
---------------------- ----------------------
- :meth:`sprockets.http.app.CallbackManager.stop` no longer requires the - :meth:`sprockets.http.app.CallbackManager.stop` no longer requires the

View file

@ -46,6 +46,7 @@ setuptools.setup(
'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: CPython',
'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP',
'Topic :: Software Development :: Libraries', 'Topic :: Software Development :: Libraries',

View file

@ -1,6 +1,7 @@
import asyncio import asyncio
import logging import logging
import sys import sys
import warnings
from tornado import concurrent, web from tornado import concurrent, web
@ -41,13 +42,19 @@ class _ShutdownHandler:
self._maybe_stop() self._maybe_stop()
def _maybe_stop(self): def _maybe_stop(self):
all_tasks = self._all_tasks()
now = self.io_loop.time() now = self.io_loop.time()
if now < self.__deadline and asyncio.Task.all_tasks(): if now < self.__deadline and all_tasks:
self.io_loop.add_timeout(now + self.wait_timeout, self._maybe_stop) self.io_loop.add_timeout(now + self.wait_timeout, self._maybe_stop)
else: else:
self.io_loop.stop() self.io_loop.stop()
self.logger.info('stopped IOLoop') self.logger.info('stopped IOLoop')
def _all_tasks(self):
if hasattr(asyncio, 'all_tasks'):
return asyncio.all_tasks(self.io_loop.asyncio_loop)
return asyncio.Task.all_tasks(self.io_loop.asyncio_loop)
class CallbackManager: class CallbackManager:
""" """
@ -254,6 +261,11 @@ def wrap_application(application, before_run, on_start, shutdown):
shutdown = [] if shutdown is None else shutdown shutdown = [] if shutdown is None else shutdown
if not isinstance(application, Application): if not isinstance(application, Application):
warnings.warn(
'sprockets.http.run is only going to accept '
'sprockets.app.Application instances in 3.0, '
'was called with {}'.format(type(application).__name__),
category=DeprecationWarning)
application = _ApplicationAdapter(application) application = _ApplicationAdapter(application)
application.before_run_callbacks.extend(before_run) application.before_run_callbacks.extend(before_run)

View file

@ -1,5 +1,4 @@
from unittest import mock from unittest import mock
import asyncio
import contextlib import contextlib
import distutils.dist import distutils.dist
import distutils.errors import distutils.errors
@ -470,6 +469,21 @@ class RunnerTests(MockHelper, unittest.TestCase):
self.io_loop.stop.assert_called_once_with() self.io_loop.stop.assert_called_once_with()
self.assertNotEqual(self.io_loop._timeouts, []) self.assertNotEqual(self.io_loop._timeouts, [])
def test_that_calling_with_non_sprockets_application_is_deprecated(self):
with warnings.catch_warnings(record=True) as captured:
warnings.filterwarnings(action='always', module='sprockets')
sprockets.http.runner.Runner(web.Application())
for warning in captured:
if 'sprockets.app.Application' in str(warning.message):
break
else:
self.fail('expected deprecation warning from runnr.Runner')
with warnings.catch_warnings(record=True) as captured:
warnings.filterwarnings(action='always', module='sprockets')
sprockets.http.runner.Runner(sprockets.http.app.Application())
self.assertEqual(len(captured), 0)
class AsyncRunTests(unittest.TestCase): class AsyncRunTests(unittest.TestCase):
@ -724,49 +738,55 @@ class ShutdownHandlerTests(unittest.TestCase):
self.assertIn('Injected Failure', cm.output[0]) self.assertIn('Injected Failure', cm.output[0])
def test_that_maybe_stop_retries_until_tasks_are_complete(self): def test_that_maybe_stop_retries_until_tasks_are_complete(self):
async def f():
pass
fake_loop = unittest.mock.Mock() fake_loop = unittest.mock.Mock()
fake_loop.time.return_value = 10 fake_loop.time.return_value = 10
loop = asyncio.get_event_loop() wait_timeout = 1.0
tasks = [loop.create_task(f()) for _ in range(5)] handler = sprockets.http.app._ShutdownHandler(
fake_loop, 5.0, wait_timeout)
handler = sprockets.http.app._ShutdownHandler(fake_loop, 5.0, 0.0) handler._all_tasks = unittest.mock.Mock()
handler.on_shutdown_ready() # sets __deadline to 15 handler._all_tasks.return_value = ['does-not-matter']
# on_shutdown_ready should schedule the callback since there
# are outstanding tasks
handler.on_shutdown_ready()
fake_loop.add_timeout.assert_called_once_with(
fake_loop.time.return_value + wait_timeout,
handler._maybe_stop)
fake_loop.add_timeout.reset_mock() fake_loop.add_timeout.reset_mock()
while tasks: # the callback should re-schedule since there are still
task = tasks.pop() # outstanding tasks
handler._maybe_stop() handler._maybe_stop()
fake_loop.add_timeout.assert_called_once_with( fake_loop.add_timeout.assert_called_once_with(
unittest.mock.ANY, handler._maybe_stop) fake_loop.time.return_value + wait_timeout,
handler._maybe_stop)
fake_loop.add_timeout.reset_mock() fake_loop.add_timeout.reset_mock()
loop.run_until_complete(task)
del task
# when all of the tasks are finished, the loop is stopped
handler._all_tasks.return_value = []
handler._maybe_stop() handler._maybe_stop()
fake_loop.stop.assert_called_once_with() fake_loop.stop.assert_called_once_with()
def test_that_maybe_stop_terminates_when_deadline_reached(self): def test_that_maybe_stop_terminates_when_deadline_reached(self):
fake_loop = unittest.mock.Mock() fake_loop = unittest.mock.Mock()
fake_loop.time.return_value = 10
loop = asyncio.get_event_loop() shutdown_limit = 10
loop.create_task(asyncio.sleep(10)) ticks = range(0, shutdown_limit)
handler = sprockets.http.app._ShutdownHandler(
fake_loop, shutdown_limit, 1.0)
handler = sprockets.http.app._ShutdownHandler(fake_loop, 5.0, 0.0) handler._all_tasks = unittest.mock.Mock()
handler.on_shutdown_ready() # sets __deadline to 15 handler._all_tasks.return_value = ['does-not-matter']
fake_loop.add_timeout.reset_mock()
while fake_loop.time.return_value < 15: fake_loop.time.return_value = 0.0
handler.on_shutdown_ready() # sets deadline to 0 + shutdown_limit
for time_value in ticks: # tick down
fake_loop.time.return_value = float(time_value)
handler._maybe_stop() handler._maybe_stop()
fake_loop.add_timeout.assert_called_once_with( fake_loop.stop.assert_not_called()
unittest.mock.ANY, handler._maybe_stop)
fake_loop.add_timeout.reset_mock()
fake_loop.time.return_value += 1
fake_loop.time.return_value = float(shutdown_limit)
handler._maybe_stop() handler._maybe_stop()
fake_loop.stop.assert_called_once_with() fake_loop.stop.assert_called_once_with()
self.assertEqual(len(asyncio.Task.all_tasks(loop)), 1)

View file

@ -1,5 +1,5 @@
[tox] [tox]
envlist = py35,py36,py37,tornado,tornado50 envlist = py35,py36,py37,py38,py39,tornado,tornado50
indexserver = indexserver =
default = https://pypi.python.org/simple default = https://pypi.python.org/simple
toxworkdir = build/tox toxworkdir = build/tox
@ -20,5 +20,5 @@ commands =
[testenv:tornado50] [testenv:tornado50]
commands = commands =
{envbindir}/pip install tornado=5.0 {envbindir}/pip install tornado==5.0
{[testenv]commands} {[testenv]commands}