mirror of
https://github.com/sprockets/sprockets.http.git
synced 2024-11-14 11:19:26 +00:00
Merge pull request #29 from nvllsvm/tornado5
Add support for Tornado >= 5
This commit is contained in:
commit
a6305bedd8
17 changed files with 79 additions and 149 deletions
14
.travis.yml
14
.travis.yml
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
23
docs/conf.py
23
docs/conf.py
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
1
requires/docs.txt
Normal file
|
@ -0,0 +1 @@
|
|||
sphinx==1.8.2
|
|
@ -1 +1 @@
|
|||
tornado>=3.1,<5
|
||||
tornado>=5,<6
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -3,6 +3,8 @@ universal = 1
|
|||
|
||||
[build_sphinx]
|
||||
all-files = 1
|
||||
fresh-env = 1
|
||||
warning-is-error = 1
|
||||
|
||||
[flake8]
|
||||
exclude = env,build
|
||||
|
|
36
setup.py
36
setup.py
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
61
tests.py
61
tests.py
|
@ -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
20
tox.ini
|
@ -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}
|
||||
|
|
Loading…
Reference in a new issue