Skip to content

Commit 1271f7d

Browse files
committed
Add support for Tornado 6
This PR adds support for Tornado 6 by conditionally using different scope manager, context manager and tracing implementation depending on the version of Tornado and Python being used. It does not require existing users to change anything other than upgrade to the latest version of this package. This package used to use the TornadoScopeManager shipped by opentracing-python. The scope manager used `tornado.stack_context` which was deprecated in Tornado 5 and removed in Tornado 6. Tornado now recommends using contextvars package introduced in Python3.7. opentracing-python already provides a ContextVarsScopeManager that builds on top of the contextvars package. It also implements AsyncioScopeManager which builds on top of asyncio and falls back on thread local storage to implement context propagation. We fallback on this for Python 3.6 and older when using Tornado 6 and newer. The package also had seen some decay and some tests were not passing. This PR updates the test suite and unit tests to get them working again. Changes this PR introduces: - Default to ContextVarsScopeManager instead of TornadoScopeManager. Fallback on TornadoScopeManager or AsyncioScopeManager based on the Tornado and Python version. - Added tox support to enable easier testing across Python and Tornado versions. - Updated travis config to work with tox environments. Now each travis build will run tests on every supported python version in parallel. Each parallel test will run all tests for all versions of tornado serially. - The PR add some code that uses the new async/await syntax. Such code is invalid for older versions of python. To make it works for all versions, we conditionally import modules depending on the Python interpreter version. - To preserve backward compatibility and to keep using common code for all tornado versions, we've added some noop implementations that are not to be used with newer versions of tornado. - `tornado.gen.coroutine` was deprecated in favour of async/await but we still support it where we can. There is a bug in Tornado 6 that prevents us from support the deprecated feature on Python3.7 with ContextVarsScopeManager. (tornadoweb/tornado#2716) - Python3.4 also does not pass the tests for `tornado.gen.coroutine` but it is not a regression caused by this PR. Testing on master results in the same behavior. For now, I've added skip markers to these tests on Python3.4. If needed, we can look into supporting these in future in a separate PR.
1 parent 2c87f42 commit 1271f7d

24 files changed

+754
-262
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1+
.coverage
2+
.python-version
3+
.tox
14
*.pyc
5+
.vscode
26
dist
37
bin
48
eggs
59
lib
610
*.egg-info
711
build
812
env/
13+
venv/
14+
.pytest_cache/*

.travis.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
language: python
22
python:
33
- "2.7"
4+
- "3.4"
5+
- "3.5"
6+
- "3.6"
7+
- "3.7"
8+
- "3.8"
49

510
sudo: required
611

712
install:
13+
- pip install tox tox-travis
814
- make bootstrap
915

1016
script:
11-
- make test lint
17+
- make test

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,12 @@ clean-test:
3838
lint:
3939
flake8 $(project) tests
4040

41-
test:
41+
test-local:
4242
py.test -s --cov-report term-missing:skip-covered --cov=$(project)
4343

44+
test:
45+
tox
46+
4447
build:
4548
python setup.py build
4649

setup.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,17 @@
1515
platforms='any',
1616
install_requires=[
1717
'tornado',
18-
'opentracing>=2.0,<2.1',
18+
'opentracing>=2.0,<=2.3',
1919
'wrapt',
2020
],
2121
extras_require={
2222
'tests': [
23-
'flake8<3', # see https://github.com/zheller/flake8-quotes/issues/29
23+
'flake8<4',
2424
'flake8-quotes',
25-
'pytest>=2.7,<3',
25+
'pytest>=4.6.9',
2626
'pytest-cov',
27+
'mock',
28+
'tox',
2729
],
2830
},
2931
classifiers=[

tests/_handlers_async_py35.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import asyncio
2+
3+
import tornado.web
4+
5+
from .tracing import tracing
6+
7+
8+
class AsyncScopeHandler(tornado.web.RequestHandler):
9+
async def do_something(self):
10+
tracing = self.settings.get('opentracing_tracing')
11+
with tracing.tracer.start_active_span('Child'):
12+
tracing.tracer.active_span.set_tag('start', 0)
13+
await asyncio.sleep(0)
14+
tracing.tracer.active_span.set_tag('end', 1)
15+
16+
async def get(self):
17+
tracing = self.settings.get('opentracing_tracing')
18+
span = tracing.get_span(self.request)
19+
assert span is not None
20+
assert tracing.tracer.active_span is span
21+
22+
await self.do_something()
23+
24+
assert tracing.tracer.active_span is span
25+
self.write('{}')
26+
27+
28+
class DecoratedAsyncHandler(tornado.web.RequestHandler):
29+
@tracing.trace('protocol', 'doesntexist')
30+
async def get(self):
31+
await asyncio.sleep(0)
32+
self.set_status(201)
33+
self.write('{}')
34+
35+
36+
class DecoratedAsyncErrorHandler(tornado.web.RequestHandler):
37+
@tracing.trace()
38+
async def get(self):
39+
await asyncio.sleep(0)
40+
raise ValueError('invalid value')
41+
42+
43+
class DecoratedAsyncScopeHandler(tornado.web.RequestHandler):
44+
async def do_something(self):
45+
with tracing.tracer.start_active_span('Child'):
46+
tracing.tracer.active_span.set_tag('start', 0)
47+
await asyncio.sleep(0)
48+
tracing.tracer.active_span.set_tag('end', 1)
49+
50+
@tracing.trace()
51+
async def get(self):
52+
span = tracing.get_span(self.request)
53+
assert span is not None
54+
assert tracing.tracer.active_span is span
55+
56+
await self.do_something()
57+
58+
assert tracing.tracer.active_span is span
59+
self.set_status(201)
60+
self.write('{}')

tests/_test_case_base.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import tornado.testing
2+
3+
4+
class BaseAsyncHTTPTestCase(tornado.testing.AsyncHTTPTestCase):
5+
6+
def http_fetch(self, url, *args, **kwargs):
7+
self.http_client.fetch(url, self.stop, *args, **kwargs)
8+
return self.wait()

tests/_test_case_gen.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import tornado.testing
2+
from tornado.httpclient import HTTPError
3+
from tornado import version_info as tornado_version
4+
5+
from ._test_case_base import BaseAsyncHTTPTestCase
6+
7+
8+
use_wait_stop = tornado_version < (5, 0, 0)
9+
10+
if use_wait_stop:
11+
def gen_test(func):
12+
return func
13+
else:
14+
gen_test = tornado.testing.gen_test
15+
16+
17+
class AsyncHTTPTestCase(BaseAsyncHTTPTestCase):
18+
19+
@gen_test
20+
def _http_fetch_gen(self, url, *args, **kwargs):
21+
try:
22+
response = yield self.http_client.fetch(url, *args, **kwargs)
23+
except HTTPError as exc:
24+
response = exc.response
25+
return response
26+
27+
def http_fetch(self, url, *args, **kwargs):
28+
fetch = self._http_fetch_gen
29+
if use_wait_stop:
30+
fetch = super().http_fetch
31+
return fetch(url, *args, **kwargs)

tests/handlers_async.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import sys
2+
3+
import tornado.web
4+
5+
6+
class noopHandler(tornado.web.RequestHandler):
7+
def get(self):
8+
pass
9+
10+
11+
if sys.version_info > (3, 5):
12+
from ._handlers_async_py35 import (
13+
AsyncScopeHandler,
14+
DecoratedAsyncHandler,
15+
DecoratedAsyncScopeHandler,
16+
DecoratedAsyncErrorHandler
17+
)
18+
else:
19+
AsyncScopeHandler = noopHandler
20+
DecoratedAsyncHandler = noopHandler
21+
DecoratedAsyncScopeHandler = noopHandler
22+
DecoratedAsyncErrorHandler = noopHandler

tests/helpers.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import sys
2+
3+
import pytest
4+
from tornado import version_info as tornado_version
5+
6+
7+
def skip_generator_contextvars_on_tornado6(func):
8+
return pytest.mark.skipif(
9+
tornado_version >= (6, 0, 0),
10+
reason=(
11+
'tornado6 has a bug (#2716) that '
12+
'prevents contextvars from working.'
13+
)
14+
)(func)
15+
16+
17+
def skip_generator_contextvars_on_py34(func):
18+
return pytest.mark.skipif(
19+
sys.version_info.major == 3 and sys.version_info.minor == 4,
20+
reason=('does not work on 3.4 with tornado context stack currently.')
21+
)(func)
22+
23+
24+
def skip_no_async_await(func):
25+
return pytest.mark.skipif(
26+
sys.version_info < (3, 5) or tornado_version < (5, 0),
27+
reason=(
28+
'async/await is not supported on python older than 3.5 '
29+
'and tornado older than 5.0.'
30+
)
31+
)(func)

tests/test_case.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import sys
2+
3+
4+
if sys.version_info >= (3, 3):
5+
from ._test_case_gen import AsyncHTTPTestCase # noqa
6+
else:
7+
from ._test_case_base import BaseAsyncHTTPTestCase as AsyncHTTPTestCase # noqa

0 commit comments

Comments
 (0)