Skip to content
This repository was archived by the owner on Sep 23, 2025. It is now read-only.

Commit 4724975

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 4724975

23 files changed

+780
-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

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ In order to implement tracing in your system (for all the requests), add the fol
2525

2626
.. code-block:: python
2727
28-
from opentracing.scope_managers.tornado import TornadoScopeManager
28+
from opentracing_tornado.scope_managers import TornadoScopeManager
2929
import tornado_opentracing
3030
3131
# Create your opentracing tracer using TornadoScopeManager for active Span handling.

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.4',
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/helpers/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import sys
2+
3+
import tornado_opentracing
4+
from opentracing.mocktracer import MockTracer
5+
from tornado_opentracing.scope_managers import ScopeManager
6+
7+
8+
if sys.version_info >= (3, 3):
9+
from ._test_case_gen import AsyncHTTPTestCase # noqa
10+
else:
11+
from ._test_case import AsyncHTTPTestCase # noqa
12+
13+
14+
tracing = tornado_opentracing.TornadoTracing(MockTracer(ScopeManager()))

tests/helpers/_test_case.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 AsyncHTTPTestCase(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/helpers/_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 import AsyncHTTPTestCase as BaseTestCase
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(BaseTestCase):
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/helpers/handlers.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_await import (
13+
AsyncScopeHandler,
14+
DecoratedAsyncHandler,
15+
DecoratedAsyncScopeHandler,
16+
DecoratedAsyncErrorHandler
17+
)
18+
else:
19+
AsyncScopeHandler = noopHandler
20+
DecoratedAsyncHandler = noopHandler
21+
DecoratedAsyncScopeHandler = noopHandler
22+
DecoratedAsyncErrorHandler = noopHandler
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 . 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('{}')

0 commit comments

Comments
 (0)