Merge pull request #29 from nvllsvm/tornado5

Add support for Tornado >= 5
This commit is contained in:
Andrew Rabert 2018-11-27 17:27:23 -05:00 committed by GitHub
commit a6305bedd8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 79 additions and 149 deletions

View file

@ -1,17 +1,15 @@
language: python
dist: xenial
python:
- 2.7
- 3.4
- 3.5
- 3.6
- 3.7-dev
before_install:
- pip install nose coverage codecov
- pip install -r requires/development.txt
- 3.7
install:
- pip install -e .
- pip install -r requires/development.txt codecov
script:
- nosetests --with-coverage
- python setup.py build_sphinx
- flake8
after_success:
- codecov
sudo: false
@ -22,6 +20,6 @@ deploy:
password:
secure: ARvFw5CHqQZqPOkJXxQSe7EAEbX1yt7FiBTtzz8Gd6XndbY10HVCSWhGYeldm9LevvQc9p77pBEvsl+bXGQbJ3NW/r/U5PADaFdmi4bxmXN8yc+dFKzn72MpZfL+kCV2T/HutuOY6dQa4okTkKVV+sqwPLKPhL69zH/PxQg8qe4=
on:
python: 3.4
python: 3.7
tags: true
all_branches: true

View file

@ -54,7 +54,7 @@ class instead of writing a ``make_app`` function:
handlers = [
# insert your handlers
]
super(Application, self).__init__(handlers, *args, **kwargs)
super().__init__(handlers, *args, **kwargs)
if __name__ == '__main__':
sprockets.http.run(Application)

View file

@ -43,7 +43,7 @@ when the application starts.
handlers = [
# insert your handlers here
]
super(Application, self).__init__(handlers, *args, **kwargs)
super().__init__(handlers, *args, **kwargs)
self.ready_to_serve = locks.Event()
self.ready_to_serve.clear()
self.on_start_callbacks.append(self._connect_to_database)
@ -72,7 +72,7 @@ the event:
class StatusHandler(web.RequestHandler):
@gen.coroutine
def prepare(self):
maybe_future = super(StatusHandler, self).prepare()
maybe_future = super().prepare()
if concurrent.is_future(maybe_future):
yield maybe_future
if not self._finished and not self.application.ready_to_serve.is_set():
@ -144,7 +144,7 @@ written the following instead:
class MyHandler(web.RequestHandler):
def initialize(self):
super(MyHandler, self).initialize()
super().initialize()
self.logger = logging.getLogger('MyHandler')
def get(self):

View file

@ -1,32 +1,17 @@
# -*- coding: utf-8 -*-
import alabaster
from sprockets import http
import sprockets.http
project = 'sprockets.http'
copyright = 'AWeber Communications, Inc.'
version = http.__version__
release = '.'.join(str(v) for v in http.version_info[0:2])
version = sprockets.http.__version__
release = '.'.join(str(v) for v in sprockets.http.version_info[0:2])
needs_sphinx = '1.0'
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
'sphinx.ext.viewcode',
'sphinxcontrib.autohttp.tornado',
'sphinx.ext.viewcode'
]
templates_path = []
source_suffix = '.rst'
source_encoding = 'utf-8-sig'
master_doc = 'index'
exclude_patterns = []
pygments_style = 'sphinx'
html_theme = 'alabaster'
html_theme_path = [alabaster.get_path()]
html_sidebars = {
'**': ['about.html', 'navigation.html'],
}
html_static_path = ['_static']
html_theme_options = {
'github_user': 'sprockets',
'github_repo': 'sprockets.http',

View file

@ -125,4 +125,5 @@ Release History
.. _1.4.0: https://github.com/sprockets/sprockets.http/compare/1.3.3...1.4.0
.. _1.4.1: https://github.com/sprockets/sprockets.http/compare/1.4.0...1.4.1
.. _1.4.2: https://github.com/sprockets/sprockets.http/compare/1.4.1...1.4.2
.. _Next Release: https://github.com/sprockets/sprockets.http/compare/1.4.2...master
.. _1.5.0: https://github.com/sprockets/sprockets.http/compare/1.4.2...1.5.0
.. _Next Release: https://github.com/sprockets/sprockets.http/compare/1.5.0...master

View file

@ -31,7 +31,7 @@ class Application(app.Application):
def __init__(self, **kwargs):
kwargs['debug'] = True
super(Application, self).__init__(
super().__init__(
[web.url(r'/status/(?P<status_code>\d+)', StatusHandler)],
**kwargs)

View file

@ -1,7 +1,4 @@
-rinstallation.txt
-rtesting.txt
coverage==4.4.2
flake8==3.5.0
sphinx==1.5.6
sphinxcontrib-httpdomain==1.5.0
tox==1.9.2
-e .
-r testing.txt
-r docs.txt
flake8

1
requires/docs.txt Normal file
View file

@ -0,0 +1 @@
sphinx==1.8.2

View file

@ -1 +1 @@
tornado>=3.1,<5
tornado>=5,<6

View file

@ -1,2 +1,3 @@
nose>=1.3.1,<2
mock>=1.3,<2
coverage>=4.5,<5
nose>=1.3.7,<2
tox>=3.5,<4

View file

@ -3,6 +3,8 @@ universal = 1
[build_sphinx]
all-files = 1
fresh-env = 1
warning-is-error = 1
[flake8]
exclude = env,build

View file

@ -1,32 +1,25 @@
#!/usr/bin/env python
#
import os.path
import pathlib
import setuptools
from sprockets import http
def read_requirements(filename):
def read_requirements(name):
requirements = []
try:
with open(os.path.join('requires', filename)) as req_file:
for line in req_file:
if '#' in line:
line = line[:line.index('#')]
line = line.strip()
if line.startswith('-'):
pass
requirements.append(line)
except IOError:
pass
for line in pathlib.Path('requires', name).read_text().split('\n'):
if '#' in line:
line = line[:line.index('#')]
line = line.strip()
if line.startswith('-'):
pass
requirements.append(line)
return requirements
requirements = read_requirements('installation.txt')
tests_require = read_requirements('testing.txt')
setuptools.setup(
name='sprockets.http',
version=http.__version__,
@ -34,7 +27,7 @@ setuptools.setup(
author='AWeber Communications',
author_email='api@aweber.com',
url='https://github.com/sprockets/sprockets.http',
install_requires=requirements,
install_requires=read_requirements('installation.txt'),
license='BSD',
namespace_packages=['sprockets'],
packages=setuptools.find_packages(),
@ -48,17 +41,16 @@ setuptools.setup(
'License :: OSI Approved :: BSD License',
'Natural Language :: English',
'Operating System :: OS Independent',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Software Development :: Libraries',
'Topic :: Software Development :: Libraries :: Python Modules'],
test_suite='nose.collector',
tests_require=tests_require,
tests_require=read_requirements('testing.txt'),
python_requires='>=3.5',
zip_safe=True,
)

View file

@ -1,10 +1,11 @@
import asyncio
import logging
import sys
from tornado import concurrent, web
class _ShutdownHandler(object):
class _ShutdownHandler:
"""Keeps track of the application state during shutdown."""
def __init__(self, io_loop):
@ -40,15 +41,14 @@ class _ShutdownHandler(object):
def _maybe_stop(self):
now = self.io_loop.time()
if (now < self.__deadline and
(self.io_loop._callbacks or self.io_loop._timeouts)):
if now < self.__deadline and asyncio.Task.all_tasks():
self.io_loop.add_timeout(now + 1, self._maybe_stop)
else:
self.io_loop.stop()
self.logger.info('stopped IOLoop')
class CallbackManager(object):
class CallbackManager:
"""
Application state management.
@ -76,7 +76,7 @@ class CallbackManager(object):
def __init__(self, tornado_application, *args, **kwargs):
self.runner_callbacks = kwargs.pop('runner_callbacks', {})
super(CallbackManager, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self._tornado_application = tornado_application
self.logger = logging.getLogger(self.__class__.__name__)
@ -120,6 +120,10 @@ class CallbackManager(object):
for callback in self.on_shutdown_callbacks:
try:
maybe_future = callback(self.tornado_application)
if asyncio.iscoroutine(maybe_future):
maybe_future = asyncio.create_task(maybe_future)
if concurrent.is_future(maybe_future):
shutdown.add_future(maybe_future)
running_async = True
@ -195,7 +199,7 @@ class Application(CallbackManager, web.Application):
"""
def __init__(self, *args, **kwargs):
super(Application, self).__init__(self, *args, **kwargs)
super().__init__(self, *args, **kwargs)
class _ApplicationAdapter(CallbackManager):
@ -216,7 +220,7 @@ class _ApplicationAdapter(CallbackManager):
def __init__(self, application):
self._application = application
self.settings = self._application.settings
super(_ApplicationAdapter, self).__init__(
super().__init__(
self._application,
runner_callbacks=getattr(application, 'runner_callbacks', {}))
setattr(self._application, 'runner_callbacks', self.runner_callbacks)

View file

@ -17,7 +17,7 @@ def _get_http_reason(status_code):
return httputil.responses.get(status_code, 'Unknown')
class LoggingHandler(object):
class LoggingHandler:
"""
Add ``self.logger``.
@ -32,12 +32,12 @@ class LoggingHandler(object):
"""
def initialize(self):
super(LoggingHandler, self).initialize()
super().initialize()
if not hasattr(self, 'logger'):
self.logger = logging.getLogger(self.__class__.__name__)
class ErrorLogger(LoggingHandler, object):
class ErrorLogger(LoggingHandler):
"""
Log a message in ``send_error``.
@ -58,7 +58,7 @@ class ErrorLogger(LoggingHandler, object):
else:
# Oh, and make non-standard HTTP status codes NOT explode!
kwargs['reason'] = _get_http_reason(status_code)
super(ErrorLogger, self).send_error(status_code, **kwargs)
super().send_error(status_code, **kwargs)
def write_error(self, status_code, **kwargs):
log_function = self.logger.debug
@ -71,10 +71,10 @@ class ErrorLogger(LoggingHandler, object):
log_function('%s %s failed with %s: %s', self.request.method,
self.request.uri, status_code,
kwargs.get('log_message', kwargs['reason']))
super(ErrorLogger, self).write_error(status_code, **kwargs)
super().write_error(status_code, **kwargs)
class ErrorWriter(object):
class ErrorWriter:
"""
Write error bodies out consistently.

View file

@ -12,12 +12,11 @@ import signal
import sys
from tornado import httpserver, ioloop
import tornado
import sprockets.http.app
class Runner(object):
class Runner:
"""
HTTP service runner.
@ -91,12 +90,7 @@ class Runner(object):
self.server.listen(port_number)
else:
self.logger.info('starting processes on port %d', port_number)
if tornado.version_info >= (4, 4):
self.server.bind(port_number, reuse_port=True)
else:
self.logger.warning('port reuse disabled, please upgrade to'
'at least Tornado 4.4')
self.server.bind(port_number)
self.server.bind(port_number, reuse_port=True)
self.server.start(number_of_procs)
def stop_server(self):

View file

@ -1,3 +1,4 @@
from unittest import mock
import contextlib
import distutils.dist
import distutils.errors
@ -7,15 +8,7 @@ import json
import time
import unittest
try:
from unittest import mock
open_name = 'builtins.open'
except ImportError:
import mock
open_name = '__builtin__.open'
from tornado import concurrent, httpserver, httputil, ioloop, testing, web
import tornado
import sprockets.http.mixins
import sprockets.http.runner
@ -25,7 +18,7 @@ import examples
class RecordingHandler(logging.Handler):
def __init__(self):
super(RecordingHandler, self).__init__()
super().__init__()
self.emitted = []
def emit(self, record):
@ -44,11 +37,11 @@ class RaisingHandler(sprockets.http.mixins.ErrorLogger,
class MockHelper(unittest.TestCase):
def setUp(self):
super(MockHelper, self).setUp()
super().setUp()
self._mocks = []
def tearDown(self):
super(MockHelper, self).tearDown()
super().tearDown()
for mocker in self._mocks:
mocker.stop()
del self._mocks[:]
@ -76,13 +69,13 @@ def override_environment_variable(name, value):
class ErrorLoggerTests(testing.AsyncHTTPTestCase):
def setUp(self):
super(ErrorLoggerTests, self).setUp()
super().setUp()
self.recorder = RecordingHandler()
root_logger = logging.getLogger()
root_logger.addHandler(self.recorder)
def tearDown(self):
super(ErrorLoggerTests, self).tearDown()
super().tearDown()
logging.getLogger().removeHandler(self.recorder)
def get_app(self):
@ -136,7 +129,7 @@ class ErrorWriterTests(testing.AsyncHTTPTestCase):
def setUp(self):
self._application = None
super(ErrorWriterTests, self).setUp()
super().setUp()
@property
def application(self):
@ -226,7 +219,7 @@ class ErrorWriterTests(testing.AsyncHTTPTestCase):
class RunTests(MockHelper, unittest.TestCase):
def setUp(self):
super(RunTests, self).setUp()
super().setUp()
self.runner_cls = self.start_mock('sprockets.http.runner.Runner')
self.get_logging_config = self.start_mock(
'sprockets.http._get_logging_config')
@ -300,7 +293,7 @@ class RunTests(MockHelper, unittest.TestCase):
class CallbackTests(MockHelper, unittest.TestCase):
def setUp(self):
super(CallbackTests, self).setUp()
super().setUp()
self.shutdown_callback = mock.Mock()
self.before_run_callback = mock.Mock()
self.application = self.make_application()
@ -368,7 +361,7 @@ class CallbackTests(MockHelper, unittest.TestCase):
class RunnerTests(MockHelper, unittest.TestCase):
def setUp(self):
super(RunnerTests, self).setUp()
super().setUp()
self.application = mock.Mock()
self.application.settings = {
'xheaders': True,
@ -410,8 +403,6 @@ class RunnerTests(MockHelper, unittest.TestCase):
self.http_server.start.assert_called_once_with(0)
@unittest.skipUnless(tornado.version_info >= (4, 4),
'port reuse requries newer tornado')
def test_that_production_enables_reuse_port(self):
runner = sprockets.http.runner.Runner(self.application)
runner.run(8000)
@ -448,32 +439,6 @@ class RunnerTests(MockHelper, unittest.TestCase):
self.io_loop.add_callback_from_signal.assert_called_once_with(
runner._shutdown)
def test_that_shutdown_waits_for_callbacks(self):
def add_timeout(_, callback):
self.io_loop._callbacks.pop()
callback()
self.io_loop.add_timeout = mock.Mock(side_effect=add_timeout)
self.io_loop._callbacks = [mock.Mock(), mock.Mock()]
runner = sprockets.http.runner.Runner(self.application)
runner.run(8000)
runner._shutdown()
self.io_loop.stop.assert_called_once_with()
self.assertEqual(self.io_loop.add_timeout.call_count, 2)
def test_that_shutdown_waits_for_timeouts(self):
def add_timeout(_, callback):
self.io_loop._timeouts.pop()
callback()
self.io_loop.add_timeout = mock.Mock(side_effect=add_timeout)
self.io_loop._timeouts = [mock.Mock(), mock.Mock()]
runner = sprockets.http.runner.Runner(self.application)
runner.run(8000)
runner._shutdown()
self.io_loop.stop.assert_called_once_with()
self.assertEqual(self.io_loop.add_timeout.call_count, 2)
def test_that_shutdown_stops_after_timelimit(self):
def add_timeout(_, callback):
time.sleep(0.1)
@ -533,7 +498,7 @@ class AsyncRunTests(unittest.TestCase):
class RunCommandTests(MockHelper, unittest.TestCase):
def setUp(self):
super(RunCommandTests, self).setUp()
super().setUp()
self.distribution = mock.Mock(spec=distutils.dist.Distribution,
verbose=3)
@ -551,7 +516,7 @@ class RunCommandTests(MockHelper, unittest.TestCase):
'# commented line',
'SHOULD_BE=',
]))
self.start_mock(open_name, open_mock)
self.start_mock('builtins.open', open_mock)
command = sprockets.http.runner.RunCommand(self.distribution)
command.dry_run = True
@ -577,7 +542,7 @@ class RunCommandTests(MockHelper, unittest.TestCase):
os_module.path.exists.return_value = True
open_mock = mock.mock_open(read_data='PORT=2')
self.start_mock(open_name, open_mock)
self.start_mock('builtins.open', open_mock)
command = sprockets.http.runner.RunCommand(self.distribution)
command.dry_run = True

20
tox.ini
View file

@ -1,5 +1,5 @@
[tox]
envlist = py27,py34,py35,pypy,pypy3,tornado42,tornado44,tornado45
envlist = py35,py36,py37,tornado,tornado50
indexserver =
default = https://pypi.python.org/simple
toxworkdir = build/tox
@ -13,22 +13,12 @@ commands =
deps =
-rrequires/testing.txt
[testenv:tornado42]
[testenv:tornado]
commands =
{envbindir}/pip install tornado>=4.2,<4.3
{envbindir}/pip install tornado
{[testenv]commands}
[testenv:tornado43]
[testenv:tornado50]
commands =
{envbindir}/pip install tornado>=4.3,<4.4
{[testenv]commands}
[testenv:tornado44]
commands =
{envbindir}/pip install tornado>=4.4,<4.5
{[testenv]commands}
[testenv:tornado45]
commands =
{envbindir}/pip install tornado>=4.5,<4.6
{envbindir}/pip install tornado=5.0
{[testenv]commands}