Removing support for python 2.7 (#2)

OpenTelemetry doesn't support Python 2.7. Removing it from the tests and removing contrib packages that don't support Python 3. In this change:

- removing pylons contrib
- removing mysqldb contrib
- updating minimum versions of flask (>=1.0), gevent (>=1.1)

Signed-off-by: Alex Boten <aboten@lightstep.com>
This commit is contained in:
alrex
2020-03-23 09:43:03 -07:00
committed by GitHub
parent f13c716af6
commit ed6dd7f257
33 changed files with 63 additions and 1062 deletions

View File

@ -202,17 +202,6 @@ jobs:
- *persist_to_workspace_step - *persist_to_workspace_step
- *save_cache_step - *save_cache_step
pylons:
docker:
- *test_runner
resource_class: *resource_class
steps:
- checkout
- *restore_cache_step
- run: scripts/run-tox-scenario '^pylons_contrib-'
- *persist_to_workspace_step
- *save_cache_step
aiohttp: aiohttp:
docker: docker:
- *test_runner - *test_runner
@ -318,7 +307,7 @@ jobs:
steps: steps:
- checkout - checkout
- *restore_cache_step - *restore_cache_step
- run: tox -e 'falcon_contrib{,_autopatch}-{py27,py34,py35,py36}-falcon{10,11,12,13,14}' --result-json /tmp/falcon.results - run: tox -e 'falcon_contrib{,_autopatch}-{py34,py35,py36}-falcon{10,11,12,13,14}' --result-json /tmp/falcon.results
- *persist_to_workspace_step - *persist_to_workspace_step
- *save_cache_step - *save_cache_step
@ -720,7 +709,7 @@ jobs:
- run: - run:
command: | command: |
mkdir -p /tmp/test-reports mkdir -p /tmp/test-reports
tox -e 'benchmarks-{py27,py34,py35,py36,py37}' --result-json /tmp/benchmarks.results -- --benchmark-storage=file:///tmp/test-reports/ --benchmark-autosave tox -e 'benchmarks-{py34,py35,py36,py37}' --result-json /tmp/benchmarks.results -- --benchmark-storage=file:///tmp/test-reports/ --benchmark-autosave
- store_test_results: - store_test_results:
path: /tmp/test-reports path: /tmp/test-reports
- store_artifacts: - store_artifacts:
@ -985,10 +974,6 @@ workflows:
requires: requires:
- flake8 - flake8
- black - black
- pylons:
requires:
- flake8
- black
- pymemcache: - pymemcache:
requires: requires:
- flake8 - flake8
@ -1097,7 +1082,6 @@ workflows:
- opentracer - opentracer
- psycopg - psycopg
- pylibmc - pylibmc
- pylons
- pymemcache - pymemcache
- pymongo - pymongo
- pymysql - pymysql

View File

@ -11,7 +11,7 @@ import sys
import pytest import pytest
PY_DIR_PATTERN = re.compile(r"^py[23][0-9]$") PY_DIR_PATTERN = re.compile(r"^py3[0-9]$")
# Determine if the folder should be ignored # Determine if the folder should be ignored
@ -27,7 +27,6 @@ def pytest_ignore_collect(path, config):
Example:: Example::
File: tests/contrib/vertica/py35/test.py File: tests/contrib/vertica/py35/test.py
Python 2.7: Skip
Python 3.4: Skip Python 3.4: Skip
Python 3.5: Collect Python 3.5: Collect
Python 3.6: Collect Python 3.6: Collect

View File

@ -43,7 +43,6 @@ EXTRA_PATCHED_MODULES = {
"django": True, "django": True,
"falcon": True, "falcon": True,
"flask": True, "flask": True,
"pylons": True,
"pyramid": True, "pyramid": True,
} }

View File

@ -31,7 +31,7 @@ Available environment variables:
DATADOG_TRACE_AGENT_PORT=8126: override the port that the default tracer will submit to (default: 8126) DATADOG_TRACE_AGENT_PORT=8126: override the port that the default tracer will submit to (default: 8126)
DATADOG_SERVICE_NAME : override the service name to be used for this program (no default) DATADOG_SERVICE_NAME : override the service name to be used for this program (no default)
This value is passed through when setting up middleware for web framework integrations. This value is passed through when setting up middleware for web framework integrations.
(e.g. pylons, flask, django) (e.g. flask, django)
For tracing without a web integration, prefer setting the service name in code. For tracing without a web integration, prefer setting the service name in code.
DATADOG_PRIORITY_SAMPLING=true|false : (default: false): enables Priority Sampling. DATADOG_PRIORITY_SAMPLING=true|false : (default: false): enables Priority Sampling.
""" # noqa: E501 """ # noqa: E501

View File

@ -156,13 +156,6 @@ def patch():
for signal in signals: for signal in signals:
module = 'flask' module = 'flask'
# v0.9 missed importing `appcontext_tearing_down` in `flask/__init__.py`
# https://github.com/pallets/flask/blob/0.9/flask/__init__.py#L35-L37
# https://github.com/pallets/flask/blob/0.9/flask/signals.py#L52
# DEV: Version 0.9 doesn't have a patch version
if flask_version <= (0, 9) and signal == 'appcontext_tearing_down':
module = 'flask.signals'
# DEV: Patch `receivers_for` instead of `connect` to ensure we don't mess with `disconnect` # DEV: Patch `receivers_for` instead of `connect` to ensure we don't mess with `disconnect`
_w(module, '{}.receivers_for'.format(signal), traced_signal_receivers_for(signal)) _w(module, '{}.receivers_for'.format(signal), traced_signal_receivers_for(signal))
@ -241,13 +234,6 @@ def unpatch():
# Handle 'flask.request_started.receivers_for' # Handle 'flask.request_started.receivers_for'
obj = flask obj = flask
# v0.9.0 missed importing `appcontext_tearing_down` in `flask/__init__.py`
# https://github.com/pallets/flask/blob/0.9/flask/__init__.py#L35-L37
# https://github.com/pallets/flask/blob/0.9/flask/signals.py#L52
# DEV: Version 0.9 doesn't have a patch version
if flask_version <= (0, 9) and prop == 'appcontext_tearing_down.receivers_for':
obj = flask.signals
if '.' in prop: if '.' in prop:
attr, _, prop = prop.partition('.') attr, _, prop = prop.partition('.')
obj = getattr(obj, attr, object()) obj = getattr(obj, attr, object())

View File

@ -1,32 +0,0 @@
"""
The pylons trace middleware will track request timings. To
install the middleware, prepare your WSGI application and do
the following::
from pylons.wsgiapp import PylonsApp
from ddtrace import tracer
from ddtrace.contrib.pylons import PylonsTraceMiddleware
app = PylonsApp(...)
traced_app = PylonsTraceMiddleware(app, tracer, service='my-pylons-app')
Then you can define your routes and views as usual.
"""
from ...utils.importlib import require_modules
required_modules = ['pylons.wsgiapp']
with require_modules(required_modules) as missing_modules:
if not missing_modules:
from .middleware import PylonsTraceMiddleware
from .patch import patch, unpatch
__all__ = [
'patch',
'unpatch',
'PylonsTraceMiddleware',
]

View File

@ -1,8 +0,0 @@
try:
from pylons.templating import render_mako # noqa
# Pylons > 0.9.7
legacy_pylons = False
except ImportError:
# Pylons <= 0.9.7
legacy_pylons = True

View File

@ -1 +0,0 @@
CONFIG_MIDDLEWARE = '__datadog_middleware'

View File

@ -1,110 +0,0 @@
import sys
from webob import Request
from pylons import config
from .renderer import trace_rendering
from .constants import CONFIG_MIDDLEWARE
from ...compat import reraise
from ...constants import ANALYTICS_SAMPLE_RATE_KEY
from ...ext import SpanTypes, http
from ...internal.logger import get_logger
from ...propagation.http import HTTPPropagator
from ...settings import config as ddconfig
log = get_logger(__name__)
class PylonsTraceMiddleware(object):
def __init__(self, app, tracer, service='pylons', distributed_tracing=True):
self.app = app
self._service = service
self._distributed_tracing = distributed_tracing
self._tracer = tracer
# register middleware reference
config[CONFIG_MIDDLEWARE] = self
# add template tracing
trace_rendering()
def __call__(self, environ, start_response):
if self._distributed_tracing:
# retrieve distributed tracing headers
request = Request(environ)
propagator = HTTPPropagator()
context = propagator.extract(request.headers)
# only need to active the new context if something was propagated
if context.trace_id:
self._tracer.context_provider.activate(context)
with self._tracer.trace('pylons.request', service=self._service, span_type=SpanTypes.WEB) as span:
# Set the service in tracer.trace() as priority sampling requires it to be
# set as early as possible when different services share one single agent.
# set analytics sample rate with global config enabled
span.set_tag(
ANALYTICS_SAMPLE_RATE_KEY,
ddconfig.pylons.get_analytics_sample_rate(use_global_config=True)
)
if not span.sampled:
return self.app(environ, start_response)
# tentative on status code, otherwise will be caught by except below
def _start_response(status, *args, **kwargs):
""" a patched response callback which will pluck some metadata. """
http_code = int(status.split()[0])
span.set_tag(http.STATUS_CODE, http_code)
if http_code >= 500:
span.error = 1
return start_response(status, *args, **kwargs)
try:
return self.app(environ, _start_response)
except Exception as e:
# store current exceptions info so we can re-raise it later
(typ, val, tb) = sys.exc_info()
# e.code can either be a string or an int
code = getattr(e, 'code', 500)
try:
code = int(code)
if not 100 <= code < 600:
code = 500
except Exception:
code = 500
span.set_tag(http.STATUS_CODE, code)
span.error = 1
# re-raise the original exception with its original traceback
reraise(typ, val, tb=tb)
except SystemExit:
span.set_tag(http.STATUS_CODE, 500)
span.error = 1
raise
finally:
controller = environ.get('pylons.routes_dict', {}).get('controller')
action = environ.get('pylons.routes_dict', {}).get('action')
# There are cases where users re-route requests and manually
# set resources. If this is so, don't do anything, otherwise
# set the resource to the controller / action that handled it.
if span.resource == span.name:
span.resource = '%s.%s' % (controller, action)
span.set_tags({
http.METHOD: environ.get('REQUEST_METHOD'),
http.URL: '%s://%s:%s%s' % (environ.get('wsgi.url_scheme'),
environ.get('SERVER_NAME'),
environ.get('SERVER_PORT'),
environ.get('PATH_INFO')),
'pylons.user': environ.get('REMOTE_USER', ''),
'pylons.route.controller': controller,
'pylons.route.action': action,
})
if ddconfig.pylons.trace_query_string:
span.set_tag(http.QUERY_STRING, environ.get('QUERY_STRING'))

View File

@ -1,41 +0,0 @@
import os
from ddtrace.vendor import wrapt
import pylons.wsgiapp
from ddtrace import tracer, Pin
from .middleware import PylonsTraceMiddleware
from ...utils.formats import asbool, get_env
from ...utils.wrappers import unwrap as _u
def patch():
"""Instrument Pylons applications"""
if getattr(pylons.wsgiapp, '_datadog_patch', False):
return
setattr(pylons.wsgiapp, '_datadog_patch', True)
wrapt.wrap_function_wrapper('pylons.wsgiapp', 'PylonsApp.__init__', traced_init)
def unpatch():
"""Disable Pylons tracing"""
if not getattr(pylons.wsgiapp, '__datadog_patch', False):
return
setattr(pylons.wsgiapp, '__datadog_patch', False)
_u(pylons.wsgiapp.PylonsApp, '__init__')
def traced_init(wrapped, instance, args, kwargs):
wrapped(*args, **kwargs)
# set tracing options and create the TraceMiddleware
service = os.environ.get('DATADOG_SERVICE_NAME', 'pylons')
distributed_tracing = asbool(get_env('pylons', 'distributed_tracing', True))
Pin(service=service, tracer=tracer).onto(instance)
traced_app = PylonsTraceMiddleware(instance, tracer, service=service, distributed_tracing=distributed_tracing)
# re-order the middleware stack so that the first middleware is ours
traced_app.app = instance.app
instance.app = traced_app

View File

@ -1,36 +0,0 @@
import pylons
from pylons import config
from ddtrace.vendor.wrapt import wrap_function_wrapper as _w
from .compat import legacy_pylons
from .constants import CONFIG_MIDDLEWARE
def trace_rendering():
"""Patch all Pylons renderers. It supports multiple versions
of Pylons and multiple renderers.
"""
# patch only once
if getattr(pylons.templating, '__datadog_patch', False):
return
setattr(pylons.templating, '__datadog_patch', True)
if legacy_pylons:
# Pylons <= 0.9.7
_w('pylons.templating', 'render', _traced_renderer)
else:
# Pylons > 0.9.7
_w('pylons.templating', 'render_mako', _traced_renderer)
_w('pylons.templating', 'render_mako_def', _traced_renderer)
_w('pylons.templating', 'render_genshi', _traced_renderer)
_w('pylons.templating', 'render_jinja2', _traced_renderer)
def _traced_renderer(wrapped, instance, args, kwargs):
"""Traced renderer"""
tracer = config[CONFIG_MIDDLEWARE]._tracer
with tracer.trace('pylons.render') as span:
span.set_tag('template.name', args[0])
return wrapped(*args, **kwargs)

View File

@ -57,7 +57,6 @@ PATCH_MODULES = {
# Ignore some web framework integrations that might be configured explicitly in code # Ignore some web framework integrations that might be configured explicitly in code
'django': False, 'django': False,
'falcon': False, 'falcon': False,
'pylons': False,
'pyramid': False, 'pyramid': False,
# Standard library modules off by default # Standard library modules off by default

View File

@ -52,8 +52,6 @@ Supported web frameworks:
+-------------------+---------+ +-------------------+---------+
| :ref:`flask` | True | | :ref:`flask` | True |
+-------------------+---------+ +-------------------+---------+
| :ref:`pylons` | True |
+-------------------+---------+
| :ref:`pyramid` | True | | :ref:`pyramid` | True |
+-------------------+---------+ +-------------------+---------+
| :ref:`requests` | True | | :ref:`requests` | True |
@ -197,7 +195,6 @@ Enabling APM events for all web frameworks can be accomplished by setting the en
* :ref:`falcon` * :ref:`falcon`
* :ref:`flask` * :ref:`flask`
* :ref:`molten` * :ref:`molten`
* :ref:`pylons`
* :ref:`pyramid` * :ref:`pyramid`
* :ref:`requests` * :ref:`requests`
* :ref:`tornado` * :ref:`tornado`
@ -238,8 +235,6 @@ For most libraries, APM events can be enabled with the environment variable ``DD
+----------------------+----------------------------------------+ +----------------------+----------------------------------------+
| :ref:`pylibmc` | ``DD_PYLIBMC_ANALYTICS_ENABLED`` | | :ref:`pylibmc` | ``DD_PYLIBMC_ANALYTICS_ENABLED`` |
+----------------------+----------------------------------------+ +----------------------+----------------------------------------+
| :ref:`pylons` | ``DD_PYLONS_ANALYTICS_ENABLED`` |
+----------------------+----------------------------------------+
| :ref:`pymemcache` | ``DD_PYMEMCACHE_ANALYTICS_ENABLED`` | | :ref:`pymemcache` | ``DD_PYMEMCACHE_ANALYTICS_ENABLED`` |
+----------------------+----------------------------------------+ +----------------------+----------------------------------------+
| :ref:`pymongo` | ``DD_PYMONGO_ANALYTICS_ENABLED`` | | :ref:`pymongo` | ``DD_PYMONGO_ANALYTICS_ENABLED`` |
@ -564,7 +559,7 @@ The available environment variables for ``ddtrace-run`` are:
the tracer the tracer
* ``DATADOG_SERVICE_NAME`` (no default): override the service name to be used * ``DATADOG_SERVICE_NAME`` (no default): override the service name to be used
for this program. This value is passed through when setting up middleware for for this program. This value is passed through when setting up middleware for
web framework integrations (e.g. pylons, flask, django). For tracing without a web framework integrations (e.g. flask, django). For tracing without a
web integration, prefer setting the service name in code. web integration, prefer setting the service name in code.
* ``DATADOG_PATCH_MODULES=module:patch,module:patch...`` e.g. * ``DATADOG_PATCH_MODULES=module:patch,module:patch...`` e.g.
``boto:true,redis:false``: override the modules patched for this execution of ``boto:true,redis:false``: override the modules patched for this execution of

View File

@ -25,7 +25,7 @@ documentation`_.
Supported Libraries Supported Libraries
------------------- -------------------
We officially support Python 2.7, 3.4 and above. We officially support Python 3.4 and above.
The versions listed are the versions that we have tested, but ``ddtrace`` can The versions listed are the versions that we have tested, but ``ddtrace`` can
still be compatible with other versions of these libraries. If a version of a still be compatible with other versions of these libraries. If a version of a
@ -72,7 +72,7 @@ contacting support.
+--------------------------------------------------+---------------+----------------+ +--------------------------------------------------+---------------+----------------+
| :ref:`flask_cache` | >= 0.12 | No | | :ref:`flask_cache` | >= 0.12 | No |
+--------------------------------------------------+---------------+----------------+ +--------------------------------------------------+---------------+----------------+
| :ref:`gevent` | >= 1.0 | No | | :ref:`gevent` | >= 1.1 | No |
+--------------------------------------------------+---------------+----------------+ +--------------------------------------------------+---------------+----------------+
| :ref:`grpc` | >= 1.8.0 | Yes | | :ref:`grpc` | >= 1.8.0 | Yes |
+--------------------------------------------------+---------------+----------------+ +--------------------------------------------------+---------------+----------------+
@ -96,8 +96,6 @@ contacting support.
+--------------------------------------------------+---------------+----------------+ +--------------------------------------------------+---------------+----------------+
| :ref:`pylibmc` | >= 1.4 | Yes | | :ref:`pylibmc` | >= 1.4 | Yes |
+--------------------------------------------------+---------------+----------------+ +--------------------------------------------------+---------------+----------------+
| :ref:`pylons` | >= 0.9.6 | No |
+--------------------------------------------------+---------------+----------------+
| :ref:`pymemcache` | >= 1.3 | Yes | | :ref:`pymemcache` | >= 1.3 | Yes |
+--------------------------------------------------+---------------+----------------+ +--------------------------------------------------+---------------+----------------+
| :ref:`pymongo` | >= 3.0 | Yes | | :ref:`pymongo` | >= 3.0 | Yes |

View File

@ -59,14 +59,6 @@ Molten
.. automodule:: ddtrace.contrib.molten .. automodule:: ddtrace.contrib.molten
.. _pylons:
Pylons
^^^^^^
.. automodule:: ddtrace.contrib.pylons
.. _pyramid: .. _pyramid:
Pyramid Pyramid

View File

@ -1,6 +1,6 @@
[tool.black] [tool.black]
line-length = 120 line-length = 120
target_version = ['py27', 'py34', 'py35', 'py36', 'py37', 'py38'] target_version = ['py34', 'py35', 'py36', 'py37', 'py38']
exclude = ''' exclude = '''
( (
\.eggs \.eggs
@ -70,7 +70,6 @@ exclude = '''
| mysqldb | mysqldb
| psycopg | psycopg
| pylibmc | pylibmc
| pylons
| pymemcache | pymemcache
| pymongo | pymongo
| pymysql | pymysql
@ -140,7 +139,6 @@ exclude = '''
| patch.py | patch.py
| psycopg | psycopg
| pylibmc | pylibmc
| pylons
| pymemcache | pymemcache
| pymongo | pymongo
| pymysql | pymysql

View File

@ -88,7 +88,7 @@ documentation][visualization docs].
# enum34 is an enum backport for earlier versions of python # enum34 is an enum backport for earlier versions of python
# funcsigs backport required for vendored debtcollector # funcsigs backport required for vendored debtcollector
install_requires = ["psutil>=5.0.0", "enum34; python_version<'3.4'", "funcsigs>=1.0.0;python_version=='2.7'"] install_requires = ["psutil>=5.0.0", "enum34; python_version<'3.4'"]
# Base `setup()` kwargs without any C-extension registering # Base `setup()` kwargs without any C-extension registering
setup_kwargs = dict( setup_kwargs = dict(
@ -113,7 +113,6 @@ setup_kwargs = dict(
entry_points={"console_scripts": ["ddtrace-run = ddtrace.commands.ddtrace_run:main"]}, entry_points={"console_scripts": ["ddtrace-run = ddtrace.commands.ddtrace_run:main"]},
classifiers=[ classifiers=[
"Programming Language :: Python", "Programming Language :: Python",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.6",

View File

@ -11,12 +11,6 @@ from . import BaseFlaskTestCase
class FlaskSignalsTestCase(BaseFlaskTestCase): class FlaskSignalsTestCase(BaseFlaskTestCase):
def get_signal(self, signal_name): def get_signal(self, signal_name):
# v0.9 missed importing `appcontext_tearing_down` in `flask/__init__.py`
# https://github.com/pallets/flask/blob/0.9/flask/__init__.py#L35-L37
# https://github.com/pallets/flask/blob/0.9/flask/signals.py#L52
# DEV: Version 0.9 doesn't have a patch version
if flask_version <= (0, 9) and signal_name == 'appcontext_tearing_down':
return getattr(flask.signals, signal_name)
return getattr(flask, signal_name) return getattr(flask, signal_name)
def signal_function(self, name): def signal_function(self, name):

View File

@ -1,44 +0,0 @@
from pylons.controllers import WSGIController
from ..lib.helpers import ExceptionWithCodeMethod, get_render_fn
class BaseController(WSGIController):
def __call__(self, environ, start_response):
"""Invoke the Controller"""
# WSGIController.__call__ dispatches to the Controller method
# the request is routed to. This routing information is
# available in environ['pylons.routes_dict']
return WSGIController.__call__(self, environ, start_response)
class RootController(BaseController):
"""Controller used for most tests"""
def index(self):
return 'Hello World'
def raise_exception(self):
raise Exception('Ouch!')
def raise_wrong_code(self):
e = Exception('Ouch!')
e.code = 'wrong formatted code'
raise e
def raise_code_method(self):
raise ExceptionWithCodeMethod('Ouch!')
def raise_custom_code(self):
e = Exception('Ouch!')
e.code = '512'
raise e
def render(self):
render = get_render_fn()
return render('/template.mako')
def render_exception(self):
render = get_render_fn()
return render('/exception.mako')

View File

@ -1 +0,0 @@
# this file is required when Pylons calls the `legacy` module

View File

@ -1,29 +0,0 @@
from webhelpers import * # noqa
class ExceptionWithCodeMethod(Exception):
"""Use case where the status code is defined by
the `code()` method.
"""
def __init__(self, message):
super(ExceptionWithCodeMethod, self).__init__(message)
def code():
pass
class AppGlobals(object):
"""Object used to store application globals."""
pass
def get_render_fn():
"""Re-import the function everytime so that double-patching
is correctly tested.
"""
try:
from pylons.templating import render_mako as render
except ImportError:
from pylons.templating import render
return render

View File

@ -1,43 +0,0 @@
from webob import Request, Response
class ExceptionMiddleware(object):
"""A middleware which raises an exception."""
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
raise Exception('Middleware exception')
class ExceptionToSuccessMiddleware(object):
"""A middleware which catches any exceptions that occur in a later
middleware and returns a successful request.
"""
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
req = Request(environ)
try:
response = req.get_response(self.app)
except Exception:
response = Response()
response.status_int = 200
response.body = 'An error has been handled appropriately'
return response(environ, start_response)
class ExceptionToClientErrorMiddleware(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
req = Request(environ)
try:
response = req.get_response(self.app)
except Exception:
response = Response()
response.status_int = 404
response.body = 'An error has occured with proper client error handling'
return response(environ, start_response)

View File

@ -1,20 +0,0 @@
import os
from routes import Mapper
def create_routes():
"""Change this function if you need to add more routes
to your Pylons test app.
"""
app_dir = os.path.dirname(os.path.abspath(__file__))
controller_dir = os.path.join(app_dir, 'controllers')
routes = Mapper(directory=controller_dir)
routes.connect('/', controller='root', action='index')
routes.connect('/raise_exception', controller='root', action='raise_exception')
routes.connect('/raise_wrong_code', controller='root', action='raise_wrong_code')
routes.connect('/raise_custom_code', controller='root', action='raise_custom_code')
routes.connect('/raise_code_method', controller='root', action='raise_code_method')
routes.connect('/render', controller='root', action='render')
routes.connect('/render_exception', controller='root', action='render_exception')
return routes

View File

@ -1 +0,0 @@
${1/0}

View File

@ -1 +0,0 @@
Hello world!

View File

@ -1,41 +0,0 @@
import os
from mako.lookup import TemplateLookup
from pylons import config
from pylons.wsgiapp import PylonsApp
from routes.middleware import RoutesMiddleware
from beaker.middleware import SessionMiddleware, CacheMiddleware
from paste.registry import RegistryManager
from .router import create_routes
from .lib.helpers import AppGlobals
def make_app(global_conf, full_stack=True, **app_conf):
# load Pylons environment
root = os.path.dirname(os.path.abspath(__file__))
paths = dict(
templates=[os.path.join(root, 'templates')],
)
config.init_app(global_conf, app_conf, paths=paths)
config['pylons.package'] = 'tests.contrib.pylons.app'
config['pylons.app_globals'] = AppGlobals()
# set Pylons routes
config['routes.map'] = create_routes()
# Create the Mako TemplateLookup, with the default auto-escaping
config['pylons.app_globals'].mako_lookup = TemplateLookup(
directories=paths['templates'],
)
# define a default middleware stack
app = PylonsApp()
app = RoutesMiddleware(app, config['routes.map'])
app = SessionMiddleware(app, config)
app = CacheMiddleware(app, config)
app = RegistryManager(app)
return app

View File

@ -1,9 +0,0 @@
[DEFAULT]
debug = false
[app:main]
use = call:tests.contrib.pylons.app.web:make_app
full_stack = true
cache_dir = %(here)s/.cache
beaker.session.key = helloworld
beaker.session.secret = somesecret

View File

@ -1,429 +0,0 @@
import os
from routes import url_for
from paste import fixture
from paste.deploy import loadapp
import pytest
from ddtrace import config
from ddtrace.ext import http, errors
from ddtrace.constants import SAMPLING_PRIORITY_KEY, ANALYTICS_SAMPLE_RATE_KEY
from ddtrace.contrib.pylons import PylonsTraceMiddleware
from tests.opentracer.utils import init_tracer
from ...base import BaseTracerTestCase
from ...utils import assert_span_http_status_code
class PylonsTestCase(BaseTracerTestCase):
"""Pylons Test Controller that is used to test specific
cases defined in the Pylons controller. To test a new behavior,
add a new action in the `app.controllers.root` module.
"""
conf_dir = os.path.dirname(os.path.abspath(__file__))
def setUp(self):
super(PylonsTestCase, self).setUp()
# initialize a real traced Pylons app
wsgiapp = loadapp('config:test.ini', relative_to=PylonsTestCase.conf_dir)
self._wsgiapp = wsgiapp
app = PylonsTraceMiddleware(wsgiapp, self.tracer, service='web')
self.app = fixture.TestApp(app)
def test_controller_exception(self):
"""Ensure exceptions thrown in controllers can be handled.
No error tags should be set in the span.
"""
from .app.middleware import ExceptionToSuccessMiddleware
wsgiapp = ExceptionToSuccessMiddleware(self._wsgiapp)
app = PylonsTraceMiddleware(wsgiapp, self.tracer, service='web')
app = fixture.TestApp(app)
app.get(url_for(controller='root', action='raise_exception'))
spans = self.tracer.writer.pop()
assert spans, spans
assert len(spans) == 1
span = spans[0]
assert span.service == 'web'
assert span.resource == 'root.raise_exception'
assert span.error == 0
assert span.get_tag(http.URL) == 'http://localhost:80/raise_exception'
assert_span_http_status_code(span, 200)
assert http.QUERY_STRING not in span.meta
assert span.get_tag(errors.ERROR_MSG) is None
assert span.get_tag(errors.ERROR_TYPE) is None
assert span.get_tag(errors.ERROR_STACK) is None
assert span.span_type == 'web'
def test_mw_exc_success(self):
"""Ensure exceptions can be properly handled by other middleware.
No error should be reported in the span.
"""
from .app.middleware import ExceptionMiddleware, ExceptionToSuccessMiddleware
wsgiapp = ExceptionMiddleware(self._wsgiapp)
wsgiapp = ExceptionToSuccessMiddleware(wsgiapp)
app = PylonsTraceMiddleware(wsgiapp, self.tracer, service='web')
app = fixture.TestApp(app)
app.get(url_for(controller='root', action='index'))
spans = self.tracer.writer.pop()
assert spans, spans
assert len(spans) == 1
span = spans[0]
assert span.service == 'web'
assert span.resource == 'None.None'
assert span.error == 0
assert span.get_tag(http.URL) == 'http://localhost:80/'
assert_span_http_status_code(span, 200)
assert span.get_tag(errors.ERROR_MSG) is None
assert span.get_tag(errors.ERROR_TYPE) is None
assert span.get_tag(errors.ERROR_STACK) is None
def test_middleware_exception(self):
"""Ensure exceptions raised in middleware are properly handled.
Uncaught exceptions should result in error tagged spans.
"""
from .app.middleware import ExceptionMiddleware
wsgiapp = ExceptionMiddleware(self._wsgiapp)
app = PylonsTraceMiddleware(wsgiapp, self.tracer, service='web')
app = fixture.TestApp(app)
with pytest.raises(Exception):
app.get(url_for(controller='root', action='index'))
spans = self.tracer.writer.pop()
assert spans, spans
assert len(spans) == 1
span = spans[0]
assert span.service == 'web'
assert span.resource == 'None.None'
assert span.error == 1
assert span.get_tag(http.URL) == 'http://localhost:80/'
assert_span_http_status_code(span, 500)
assert span.get_tag(errors.ERROR_MSG) == 'Middleware exception'
assert span.get_tag(errors.ERROR_TYPE) == 'exceptions.Exception'
assert span.get_tag(errors.ERROR_STACK)
def test_exc_success(self):
from .app.middleware import ExceptionToSuccessMiddleware
wsgiapp = ExceptionToSuccessMiddleware(self._wsgiapp)
app = PylonsTraceMiddleware(wsgiapp, self.tracer, service='web')
app = fixture.TestApp(app)
app.get(url_for(controller='root', action='raise_exception'))
spans = self.tracer.writer.pop()
assert spans, spans
assert len(spans) == 1
span = spans[0]
assert span.service == 'web'
assert span.resource == 'root.raise_exception'
assert span.error == 0
assert span.get_tag(http.URL) == 'http://localhost:80/raise_exception'
assert_span_http_status_code(span, 200)
assert span.get_tag(errors.ERROR_MSG) is None
assert span.get_tag(errors.ERROR_TYPE) is None
assert span.get_tag(errors.ERROR_STACK) is None
def test_exc_client_failure(self):
from .app.middleware import ExceptionToClientErrorMiddleware
wsgiapp = ExceptionToClientErrorMiddleware(self._wsgiapp)
app = PylonsTraceMiddleware(wsgiapp, self.tracer, service='web')
app = fixture.TestApp(app)
app.get(url_for(controller='root', action='raise_exception'), status=404)
spans = self.tracer.writer.pop()
assert spans, spans
assert len(spans) == 1
span = spans[0]
assert span.service == 'web'
assert span.resource == 'root.raise_exception'
assert span.error == 0
assert span.get_tag(http.URL) == 'http://localhost:80/raise_exception'
assert_span_http_status_code(span, 404)
assert span.get_tag(errors.ERROR_MSG) is None
assert span.get_tag(errors.ERROR_TYPE) is None
assert span.get_tag(errors.ERROR_STACK) is None
def test_success_200(self, query_string=''):
if query_string:
fqs = '?' + query_string
else:
fqs = ''
res = self.app.get(url_for(controller='root', action='index') + fqs)
assert res.status == 200
spans = self.tracer.writer.pop()
assert spans, spans
assert len(spans) == 1
span = spans[0]
assert span.service == 'web'
assert span.resource == 'root.index'
assert_span_http_status_code(span, 200)
if config.pylons.trace_query_string:
assert span.meta.get(http.QUERY_STRING) == query_string
else:
assert http.QUERY_STRING not in span.meta
assert span.error == 0
def test_query_string(self):
return self.test_success_200('foo=bar')
def test_multi_query_string(self):
return self.test_success_200('foo=bar&foo=baz&x=y')
def test_query_string_trace(self):
with self.override_http_config('pylons', dict(trace_query_string=True)):
return self.test_success_200('foo=bar')
def test_multi_query_string_trace(self):
with self.override_http_config('pylons', dict(trace_query_string=True)):
return self.test_success_200('foo=bar&foo=baz&x=y')
def test_analytics_global_on_integration_default(self):
"""
When making a request
When an integration trace search is not event sample rate is not set and globally trace search is enabled
We expect the root span to have the appropriate tag
"""
with self.override_global_config(dict(analytics_enabled=True)):
res = self.app.get(url_for(controller='root', action='index'))
self.assertEqual(res.status, 200)
self.assert_structure(
dict(name='pylons.request', metrics={ANALYTICS_SAMPLE_RATE_KEY: 1.0})
)
def test_analytics_global_on_integration_on(self):
"""
When making a request
When an integration trace search is enabled and sample rate is set and globally trace search is enabled
We expect the root span to have the appropriate tag
"""
with self.override_global_config(dict(analytics_enabled=True)):
with self.override_config('pylons', dict(analytics_enabled=True, analytics_sample_rate=0.5)):
res = self.app.get(url_for(controller='root', action='index'))
self.assertEqual(res.status, 200)
self.assert_structure(
dict(name='pylons.request', metrics={ANALYTICS_SAMPLE_RATE_KEY: 0.5})
)
def test_analytics_global_off_integration_default(self):
"""
When making a request
When an integration trace search is not set and sample rate is set and globally trace search is disabled
We expect the root span to not include tag
"""
with self.override_global_config(dict(analytics_enabled=False)):
res = self.app.get(url_for(controller='root', action='index'))
self.assertEqual(res.status, 200)
root = self.get_root_span()
self.assertIsNone(root.get_metric(ANALYTICS_SAMPLE_RATE_KEY))
def test_analytics_global_off_integration_on(self):
"""
When making a request
When an integration trace search is enabled and sample rate is set and globally trace search is disabled
We expect the root span to have the appropriate tag
"""
with self.override_global_config(dict(analytics_enabled=False)):
with self.override_config('pylons', dict(analytics_enabled=True, analytics_sample_rate=0.5)):
res = self.app.get(url_for(controller='root', action='index'))
self.assertEqual(res.status, 200)
self.assert_structure(
dict(name='pylons.request', metrics={ANALYTICS_SAMPLE_RATE_KEY: 0.5})
)
def test_template_render(self):
res = self.app.get(url_for(controller='root', action='render'))
assert res.status == 200
spans = self.tracer.writer.pop()
assert spans, spans
assert len(spans) == 2
request = spans[0]
template = spans[1]
assert request.service == 'web'
assert request.resource == 'root.render'
assert_span_http_status_code(request, 200)
assert request.error == 0
assert template.service == 'web'
assert template.resource == 'pylons.render'
assert template.meta.get('template.name') == '/template.mako'
assert template.error == 0
def test_template_render_exception(self):
with pytest.raises(Exception):
self.app.get(url_for(controller='root', action='render_exception'))
spans = self.tracer.writer.pop()
assert spans, spans
assert len(spans) == 2
request = spans[0]
template = spans[1]
assert request.service == 'web'
assert request.resource == 'root.render_exception'
assert_span_http_status_code(request, 500)
assert request.error == 1
assert template.service == 'web'
assert template.resource == 'pylons.render'
assert template.meta.get('template.name') == '/exception.mako'
assert template.error == 1
assert template.get_tag('error.msg') == 'integer division or modulo by zero'
assert 'ZeroDivisionError: integer division or modulo by zero' in template.get_tag('error.stack')
def test_failure_500(self):
with pytest.raises(Exception):
self.app.get(url_for(controller='root', action='raise_exception'))
spans = self.tracer.writer.pop()
assert spans, spans
assert len(spans) == 1
span = spans[0]
assert span.service == 'web'
assert span.resource == 'root.raise_exception'
assert span.error == 1
assert_span_http_status_code(span, 500)
assert span.get_tag('error.msg') == 'Ouch!'
assert span.get_tag(http.URL) == 'http://localhost:80/raise_exception'
assert 'Exception: Ouch!' in span.get_tag('error.stack')
def test_failure_500_with_wrong_code(self):
with pytest.raises(Exception):
self.app.get(url_for(controller='root', action='raise_wrong_code'))
spans = self.tracer.writer.pop()
assert spans, spans
assert len(spans) == 1
span = spans[0]
assert span.service == 'web'
assert span.resource == 'root.raise_wrong_code'
assert span.error == 1
assert_span_http_status_code(span, 500)
assert span.meta.get(http.URL) == 'http://localhost:80/raise_wrong_code'
assert span.get_tag('error.msg') == 'Ouch!'
assert 'Exception: Ouch!' in span.get_tag('error.stack')
def test_failure_500_with_custom_code(self):
with pytest.raises(Exception):
self.app.get(url_for(controller='root', action='raise_custom_code'))
spans = self.tracer.writer.pop()
assert spans, spans
assert len(spans) == 1
span = spans[0]
assert span.service == 'web'
assert span.resource == 'root.raise_custom_code'
assert span.error == 1
assert_span_http_status_code(span, 512)
assert span.meta.get(http.URL) == 'http://localhost:80/raise_custom_code'
assert span.get_tag('error.msg') == 'Ouch!'
assert 'Exception: Ouch!' in span.get_tag('error.stack')
def test_failure_500_with_code_method(self):
with pytest.raises(Exception):
self.app.get(url_for(controller='root', action='raise_code_method'))
spans = self.tracer.writer.pop()
assert spans, spans
assert len(spans) == 1
span = spans[0]
assert span.service == 'web'
assert span.resource == 'root.raise_code_method'
assert span.error == 1
assert_span_http_status_code(span, 500)
assert span.meta.get(http.URL) == 'http://localhost:80/raise_code_method'
assert span.get_tag('error.msg') == 'Ouch!'
def test_distributed_tracing_default(self):
# ensure by default, distributed tracing is not enabled
headers = {
'x-datadog-trace-id': '100',
'x-datadog-parent-id': '42',
'x-datadog-sampling-priority': '2',
}
res = self.app.get(url_for(controller='root', action='index'), headers=headers)
assert res.status == 200
spans = self.tracer.writer.pop()
assert spans, spans
assert len(spans) == 1
span = spans[0]
assert span.trace_id == 100
assert span.parent_id == 42
assert span.get_metric(SAMPLING_PRIORITY_KEY) == 2
def test_distributed_tracing_disabled(self):
# ensure distributed tracing propagator is working
middleware = self.app.app
middleware._distributed_tracing = False
headers = {
'x-datadog-trace-id': '100',
'x-datadog-parent-id': '42',
'x-datadog-sampling-priority': '2',
}
res = self.app.get(url_for(controller='root', action='index'), headers=headers)
assert res.status == 200
spans = self.tracer.writer.pop()
assert spans, spans
assert len(spans) == 1
span = spans[0]
assert span.trace_id != 100
assert span.parent_id != 42
assert span.get_metric(SAMPLING_PRIORITY_KEY) != 2
def test_success_200_ot(self):
"""OpenTracing version of test_success_200."""
ot_tracer = init_tracer('pylons_svc', self.tracer)
with ot_tracer.start_active_span('pylons_get'):
res = self.app.get(url_for(controller='root', action='index'))
assert res.status == 200
spans = self.tracer.writer.pop()
assert spans, spans
assert len(spans) == 2
ot_span, dd_span = spans
# confirm the parenting
assert ot_span.parent_id is None
assert dd_span.parent_id == ot_span.span_id
assert ot_span.name == 'pylons_get'
assert ot_span.service == 'pylons_svc'
assert dd_span.service == 'web'
assert dd_span.resource == 'root.index'
assert_span_http_status_code(dd_span, 200)
assert dd_span.meta.get(http.URL) == 'http://localhost:80/'
assert dd_span.error == 0

204
tox.ini
View File

@ -20,12 +20,12 @@ envlist =
flake8 flake8
black black
wait wait
{py27,py34,py35,py36,py37}-tracer {py34,py35,py36,py37}-tracer
{py27,py34,py35,py36,py37}-internal {py34,py35,py36,py37}-internal
{py27,py34,py35,py36,py37}-integration {py34,py35,py36,py37}-integration
{py27,py34,py35,py36,py37}-ddtracerun {py34,py35,py36,py37}-ddtracerun
{py27,py34,py35,py36,py37}-test_utils {py34,py35,py36,py37}-test_utils
{py27,py34,py35,py36,py37}-test_logging {py34,py35,py36,py37}-test_logging
# Integrations environments # Integrations environments
aiobotocore_contrib-py34-aiobotocore{02,03,04} aiobotocore_contrib-py34-aiobotocore{02,03,04}
aiobotocore_contrib-{py35,py36}-aiobotocore{02,03,04,05,07,08,09,010} aiobotocore_contrib-{py35,py36}-aiobotocore{02,03,04,05,07,08,09,010}
@ -37,70 +37,63 @@ envlist =
aiohttp_contrib-{py35,py36,py37}-aiohttp{30,31,32,33,34,35}-aiohttp_jinja{015}-yarl10 aiohttp_contrib-{py35,py36,py37}-aiohttp{30,31,32,33,34,35}-aiohttp_jinja{015}-yarl10
aiopg_contrib-{py34,py35,py36}-aiopg{012,015} aiopg_contrib-{py34,py35,py36}-aiopg{012,015}
aiopg_contrib-py37-aiopg015 aiopg_contrib-py37-aiopg015
algoliasearch_contrib-{py27,py34,py35,py36,py37}-algoliasearch{1,2} algoliasearch_contrib-{py34,py35,py36,py37}-algoliasearch{1,2}
asyncio_contrib-{py34,py35,py36,py37} asyncio_contrib-{py34,py35,py36,py37}
# boto needs moto<1 and moto<1 does not support Python >= 3.7 # boto needs moto<1 and moto<1 does not support Python >= 3.7
boto_contrib-{py27,py34,py35,py36}-boto boto_contrib-{py34,py35,py36}-boto
botocore_contrib-{py27,py34,py35,py36,py37}-botocore botocore_contrib-{py34,py35,py36,py37}-botocore
bottle_contrib{,_autopatch}-{py27,py34,py35,py36,py37}-bottle{11,12}-webtest bottle_contrib{,_autopatch}-{py34,py35,py36,py37}-bottle{11,12}-webtest
cassandra_contrib-{py27,py34,py35,py36,py37}-cassandra{35,36,37,38,315} cassandra_contrib-{py34,py35,py36,py37}-cassandra{35,36,37,38,315}
# Non-4.x celery should be able to use the older redis lib, since it locks to an older kombu # Non-4.x celery should be able to use the older redis lib, since it locks to an older kombu
celery_contrib-{py27,py34,py35,py36}-celery{31}-redis{210} celery_contrib-{py34,py35,py36}-celery{31}-redis{210}
# 4.x celery bumps kombu to 4.4+, which requires redis 3.2 or later, this tests against # 4.x celery bumps kombu to 4.4+, which requires redis 3.2 or later, this tests against
# older redis with an older kombu, and newer kombu/newer redis. # older redis with an older kombu, and newer kombu/newer redis.
# https://github.com/celery/kombu/blob/3e60e6503a77b9b1a987cf7954659929abac9bac/Changelog#L35 # https://github.com/celery/kombu/blob/3e60e6503a77b9b1a987cf7954659929abac9bac/Changelog#L35
celery_contrib-{py27,py34,py35,py36}-celery{40,41}-{redis210-kombu43,redis320-kombu44} celery_contrib-{py34,py35,py36}-celery{40,41}-{redis210-kombu43,redis320-kombu44}
# Celery 4.2 is now limited to Kombu 4.3 # Celery 4.2 is now limited to Kombu 4.3
# https://github.com/celery/celery/commit/1571d414461f01ae55be63a03e2adaa94dbcb15d # https://github.com/celery/celery/commit/1571d414461f01ae55be63a03e2adaa94dbcb15d
celery_contrib-{py27,py34,py35,py36}-celery42-redis210-kombu43 celery_contrib-{py34,py35,py36}-celery42-redis210-kombu43
# Celery 4.3 wants Kombu >= 4.4 and Redis >= 3.2 # Celery 4.3 wants Kombu >= 4.4 and Redis >= 3.2
# Python 3.7 needs Celery 4.3 # Python 3.7 needs Celery 4.3
celery_contrib-{py27,py34,py35,py36,py37}-celery43-redis320-kombu44 celery_contrib-{py34,py35,py36,py37}-celery43-redis320-kombu44
consul_contrib-py{27,34,35,36,37}-consul{07,10,11} consul_contrib-py{34,35,36,37}-consul{07,10,11}
dbapi_contrib-{py27,py34,py35,py36} dbapi_contrib-{py34,py35,py36}
django_contrib{,_autopatch}-{py27,py34,py35,py36}-django{18,111}-djangopylibmc06-djangoredis45-pylibmc-redis{210}-memcached django_contrib{,_autopatch}-{py34,py35,py36}-django{18,111}-djangopylibmc06-djangoredis45-pylibmc-redis{210}-memcached
django_contrib{,_autopatch}-{py34,py35,py36}-django{200}-djangopylibmc06-djangoredis45-pylibmc-redis{210}-memcached django_contrib{,_autopatch}-{py34,py35,py36}-django{200}-djangopylibmc06-djangoredis45-pylibmc-redis{210}-memcached
django_drf_contrib-{py27,py34,py35,py36}-django{111}-djangorestframework{34,37,38} django_drf_contrib-{py34,py35,py36}-django{111}-djangorestframework{34,37,38}
django_drf_contrib-{py34,py35,py36}-django{200}-djangorestframework{37,38} django_drf_contrib-{py34,py35,py36}-django{200}-djangorestframework{37,38}
dogpile_contrib-{py27,py35,py36,py37}-dogpilecache{06,07,08,latest} dogpile_contrib-{py35,py36,py37}-dogpilecache{06,07,08,latest}
elasticsearch_contrib-{py27,py34,py35,py36}-elasticsearch{16,17,18,23,24,51,52,53,54,63,64} elasticsearch_contrib-{py34,py35,py36}-elasticsearch{16,17,18,23,24,51,52,53,54,63,64}
elasticsearch_contrib-{py27,py34,py35,py36}-elasticsearch1{100} elasticsearch_contrib-{py34,py35,py36}-elasticsearch1{100}
elasticsearch_contrib-{py27,py34,py35,py36}-elasticsearch2{50} elasticsearch_contrib-{py34,py35,py36}-elasticsearch2{50}
elasticsearch_contrib-{py27,py34,py35,py36}-elasticsearch5{50} elasticsearch_contrib-{py34,py35,py36}-elasticsearch5{50}
elasticsearch_contrib-{py27,py34,py35,py36}-elasticsearch6{40} elasticsearch_contrib-{py34,py35,py36}-elasticsearch6{40}
falcon_contrib{,_autopatch}-{py27,py34,py35,py36}-falcon{10,11,12,13,14} falcon_contrib{,_autopatch}-{py34,py35,py36}-falcon{10,11,12,13,14}
flask_contrib{,_autopatch}-{py27,py34,py35,py36}-flask{010,011,012,10}-blinker flask_contrib{,_autopatch}-{py34,py35,py36}-flask{010,011,012,10}-blinker
# Flask <=0.9 does not support Python 3 flask_cache_contrib{,_autopatch}-{py34,py35,py36,py37}-flask{010,011,012}-flaskcache{013}-memcached-redis{210}-blinker
flask_contrib{,_autopatch}-{py27}-flask{09}-blinker
flask_cache_contrib{,_autopatch}-{py27,py34,py35,py36,py37}-flask{010,011,012}-flaskcache{013}-memcached-redis{210}-blinker
flask_cache_contrib{,_autopatch}-{py27}-flask{010,011}-flaskcache{012}-memcached-redis{210}-blinker
futures_contrib-{py27}-futures{30,31,32}
futures_contrib-{py34,py35,py36,py37} futures_contrib-{py34,py35,py36,py37}
gevent_contrib-{py27,py34,py35,py36}-gevent{11,12,13} gevent_contrib-{py34,py35,py36}-gevent{11,12,13}
gevent_contrib-py37-gevent{13,14} gevent_contrib-py37-gevent{13,14}
# gevent 1.0 is not python 3 compatible grpc_contrib-{py34,py35,py36,py37}-grpc{112,113,114,115,116,117,118,119,120,121,122}
gevent_contrib-{py27}-gevent{10} httplib_contrib-{py34,py35,py36,py37}
grpc_contrib-{py27,py34,py35,py36,py37}-grpc{112,113,114,115,116,117,118,119,120,121,122} jinja2_contrib-{py34,py35,py36,py37}-jinja{27,28,29,210}
httplib_contrib-{py27,py34,py35,py36,py37} mako_contrib-{py34,py35,py36,py37}-mako{010,100}
jinja2_contrib-{py27,py34,py35,py36,py37}-jinja{27,28,29,210}
mako_contrib-{py27,py34,py35,py36,py37}-mako{010,100}
molten_contrib-py{36,37}-molten{070,072} molten_contrib-py{36,37}-molten{070,072}
mongoengine_contrib-{py27,py34,py35,py36,py37}-mongoengine{015,016,017,018,latest}-pymongo{latest} mongoengine_contrib-{py34,py35,py36,py37}-mongoengine{015,016,017,018,latest}-pymongo{latest}
mysql_contrib-{py27,py34,py35,py36,py37}-mysqlconnector mysql_contrib-{py34,py35,py36,py37}-mysqlconnector
mysqldb_contrib-{py27}-mysqldb{12} mysqldb_contrib-{py27}-mysqldb{12}
mysqldb_contrib-{py27,py34,py35,py36,py37}-mysqlclient{13} mysqldb_contrib-{py34,py35,py36,py37}-mysqlclient{13}
psycopg_contrib-{py27,py34,py35,py36}-psycopg2{24,25,26,27,28} psycopg_contrib-{py34,py35,py36}-psycopg2{24,25,26,27,28}
psycopg_contrib-py37-psycopg2{27,28} psycopg_contrib-py37-psycopg2{27,28}
pylibmc_contrib-{py27,py34,py35,py36,py37}-pylibmc{140,150} pylibmc_contrib-{py34,py35,py36,py37}-pylibmc{140,150}
pylons_contrib-{py27}-pylons{096,097,010,10} pymemcache_contrib{,_autopatch}-{py34,py35,py36,py37}-pymemcache{130,140}
pymemcache_contrib{,_autopatch}-{py27,py34,py35,py36,py37}-pymemcache{130,140} pymongo_contrib-{py34,py35,py36,py37}-pymongo{30,31,32,33,34,35,36,37,38,39,latest}-mongoengine{latest}
pymongo_contrib-{py27,py34,py35,py36,py37}-pymongo{30,31,32,33,34,35,36,37,38,39,latest}-mongoengine{latest} pymysql_contrib-{py34,py35,py36,py37}-pymysql{07,08,09}
pymysql_contrib-{py27,py34,py35,py36,py37}-pymysql{07,08,09} pyramid_contrib{,_autopatch}-{py34,py35,py36,py37}-pyramid{17,18,19}-webtest
pyramid_contrib{,_autopatch}-{py27,py34,py35,py36,py37}-pyramid{17,18,19}-webtest redis_contrib-{py34,py35,py36,py37}-redis{26,27,28,29,210,300}
redis_contrib-{py27,py34,py35,py36,py37}-redis{26,27,28,29,210,300} rediscluster_contrib-{py34,py35,py36,py37}-rediscluster{135,136}-redis210
rediscluster_contrib-{py27,py34,py35,py36,py37}-rediscluster{135,136}-redis210 requests_contrib{,_autopatch}-{py34,py35,py36,py37}-requests{208,209,210,211,212,213,219}
requests_contrib{,_autopatch}-{py27,py34,py35,py36,py37}-requests{208,209,210,211,212,213,219} kombu_contrib-{py34,py35,py36}-kombu{40,41,42}
kombu_contrib-{py27,py34,py35,py36}-kombu{40,41,42}
# Python 3.7 needs Kombu >= 4.2 # Python 3.7 needs Kombu >= 4.2
kombu_contrib-py37-kombu42 kombu_contrib-py37-kombu42
# python 3.6 requests + gevent regression test # python 3.6 requests + gevent regression test
@ -108,22 +101,20 @@ envlist =
# https://github.com/gevent/gevent/issues/903 # https://github.com/gevent/gevent/issues/903
requests_gevent_contrib-{py36}-requests{208,209,210,211,212,213,219}-gevent{12,13} requests_gevent_contrib-{py36}-requests{208,209,210,211,212,213,219}-gevent{12,13}
requests_gevent_contrib-py37-requests{208,209,210,211,212,213,219}-gevent13 requests_gevent_contrib-py37-requests{208,209,210,211,212,213,219}-gevent13
sqlalchemy_contrib-{py27,py34,py35,py36,py37}-sqlalchemy{10,11,12}-psycopg228-mysqlconnector sqlalchemy_contrib-{py34,py35,py36,py37}-sqlalchemy{10,11,12}-psycopg228-mysqlconnector
sqlite3_contrib-{py27,py34,py35,py36,py37}-sqlite3 sqlite3_contrib-{py34,py35,py36,py37}-sqlite3
tornado_contrib-{py27,py34,py35,py36,py37}-tornado{40,41,42,43,44,45} tornado_contrib-{py34,py35,py36,py37}-tornado{40,41,42,43,44,45}
tornado_contrib-{py37}-tornado{50,51,60} tornado_contrib-{py37}-tornado{50,51,60}
tornado_contrib-{py27}-tornado{40,41,42,43,44,45}-futures{30,31,32} vertica_contrib-{py34,py35,py36,py37}-vertica{060,070}
vertica_contrib-{py27,py34,py35,py36,py37}-vertica{060,070}
# Opentracer # Opentracer
{py27,py34,py35,py36,py37}-opentracer {py34,py35,py36,py37}-opentracer
{py34,py35,py36,py37}-opentracer_asyncio {py34,py35,py36,py37}-opentracer_asyncio
{py34,py35,py36,py37}-opentracer_tornado-tornado{40,41,42,43,44} {py34,py35,py36,py37}-opentracer_tornado-tornado{40,41,42,43,44}
{py27}-opentracer_gevent-gevent{10} {py34,py35,py36}-opentracer_gevent-gevent{11,12}
{py27,py34,py35,py36}-opentracer_gevent-gevent{11,12}
py37-opentracer_gevent-gevent{13,14} py37-opentracer_gevent-gevent{13,14}
# Unit tests: pytest based test suite that do not require any additional dependency # Unit tests: pytest based test suite that do not require any additional dependency
unit_tests-{py27,py34,py35,py36,py37} unit_tests-{py34,py35,py36,py37}
benchmarks-{py27,py34,py35,py36,py37} benchmarks-{py34,py35,py36,py37}
[testenv] [testenv]
# Always re-run `setup.py develop` to ensure the proper C-extension .so files are created # Always re-run `setup.py develop` to ensure the proper C-extension .so files are created
@ -133,7 +124,6 @@ envlist =
commands_pre={envpython} {toxinidir}/setup.py develop commands_pre={envpython} {toxinidir}/setup.py develop
usedevelop = True usedevelop = True
basepython = basepython =
py27: python2.7
py34: python3.4 py34: python3.4
py35: python3.5 py35: python3.5
py36: python3.6 py36: python3.6
@ -153,8 +143,6 @@ deps =
# https://github.com/aio-libs/aiohttp/issues/2662 # https://github.com/aio-libs/aiohttp/issues/2662
yarl: yarl==0.18.0 yarl: yarl==0.18.0
yarl10: yarl>=1.0,<1.1 yarl10: yarl>=1.0,<1.1
# backports
py27: enum34
# integrations # integrations
aiobotocore010: aiobotocore>=0.10,<0.11 aiobotocore010: aiobotocore>=0.10,<0.11
aiobotocore09: aiobotocore>=0.9,<0.10 aiobotocore09: aiobotocore>=0.9,<0.10
@ -246,7 +234,6 @@ deps =
falcon12: falcon>=1.2,<1.3 falcon12: falcon>=1.2,<1.3
falcon13: falcon>=1.3,<1.4 falcon13: falcon>=1.3,<1.4
falcon14: falcon>=1.4,<1.5 falcon14: falcon>=1.4,<1.5
flask09: flask>=0.9,<0.10
flask010: flask>=0.10,<0.11 flask010: flask>=0.10,<0.11
flask011: flask>=0.11,<0.12 flask011: flask>=0.11,<0.12
flask012: flask>=0.12,<0.13 flask012: flask>=0.12,<0.13
@ -257,7 +244,6 @@ deps =
futures30: futures>=3.0,<3.1 futures30: futures>=3.0,<3.1
futures31: futures>=3.1,<3.2 futures31: futures>=3.1,<3.2
futures32: futures>=3.2,<3.3 futures32: futures>=3.2,<3.3
gevent10: gevent>=1.0,<1.1
gevent11: gevent>=1.1,<1.2 gevent11: gevent>=1.1,<1.2
gevent12: gevent>=1.2,<1.3 gevent12: gevent>=1.2,<1.3
gevent13: gevent>=1.3,<1.4 gevent13: gevent>=1.3,<1.4
@ -301,14 +287,6 @@ deps =
mysqlconnector: mysql-connector-python!=8.0.18 mysqlconnector: mysql-connector-python!=8.0.18
mysqldb12: mysql-python>=1.2,<1.3 mysqldb12: mysql-python>=1.2,<1.3
mysqlclient13: mysqlclient>=1.3,<1.4 mysqlclient13: mysqlclient>=1.3,<1.4
# webob is required for Pylons < 1.0
pylons096: pylons>=0.9.6,<0.9.7
pylons096: webob<1.1
pylons097: pylons>=0.9.7,<0.9.8
pylons097: webob<1.1
pylons010: pylons>=0.10,<0.11
pylons010: webob<1.1
pylons10: pylons>=1.0,<1.1
pylibmc: pylibmc pylibmc: pylibmc
pylibmc140: pylibmc>=1.4.0,<1.5.0 pylibmc140: pylibmc>=1.4.0,<1.5.0
pylibmc150: pylibmc>=1.5.0,<1.6.0 pylibmc150: pylibmc>=1.5.0,<1.6.0
@ -426,7 +404,6 @@ commands =
mysqldb_contrib: pytest {posargs} tests/contrib/mysqldb mysqldb_contrib: pytest {posargs} tests/contrib/mysqldb
psycopg_contrib: pytest {posargs} tests/contrib/psycopg psycopg_contrib: pytest {posargs} tests/contrib/psycopg
pylibmc_contrib: pytest {posargs} tests/contrib/pylibmc pylibmc_contrib: pytest {posargs} tests/contrib/pylibmc
pylons_contrib: pytest {posargs} tests/contrib/pylons
pymemcache_contrib: pytest {posargs} --ignore="tests/contrib/pymemcache/autopatch" tests/contrib/pymemcache/ pymemcache_contrib: pytest {posargs} --ignore="tests/contrib/pymemcache/autopatch" tests/contrib/pymemcache/
pymemcache_contrib_autopatch: python tests/ddtrace_run.py pytest {posargs} tests/contrib/pymemcache/autopatch/ pymemcache_contrib_autopatch: python tests/ddtrace_run.py pytest {posargs} tests/contrib/pymemcache/autopatch/
pymongo_contrib: pytest {posargs} tests/contrib/pymongo pymongo_contrib: pytest {posargs} tests/contrib/pymongo
@ -487,22 +464,12 @@ basepython=python3.7
# same job will cause problem for tests that use ddtrace-run # same job will cause problem for tests that use ddtrace-run
[celery_contrib] [celery_contrib]
usedevelop = False usedevelop = False
[testenv:celery_contrib-py27-celery31-redis210]
usedevelop = {[celery_contrib]usedevelop}
[testenv:celery_contrib-py34-celery31-redis210] [testenv:celery_contrib-py34-celery31-redis210]
usedevelop = {[celery_contrib]usedevelop} usedevelop = {[celery_contrib]usedevelop}
[testenv:celery_contrib-py35-celery31-redis210] [testenv:celery_contrib-py35-celery31-redis210]
usedevelop = {[celery_contrib]usedevelop} usedevelop = {[celery_contrib]usedevelop}
[testenv:celery_contrib-py36-celery31-redis210] [testenv:celery_contrib-py36-celery31-redis210]
usedevelop = {[celery_contrib]usedevelop} usedevelop = {[celery_contrib]usedevelop}
[testenv:celery_contrib-py27-celery40-redis210-kombu43]
usedevelop = {[celery_contrib]usedevelop}
[testenv:celery_contrib-py27-celery40-redis320-kombu44]
usedevelop = {[celery_contrib]usedevelop}
[testenv:celery_contrib-py27-celery41-redis210-kombu43]
usedevelop = {[celery_contrib]usedevelop}
[testenv:celery_contrib-py27-celery41-redis320-kombu44]
usedevelop = {[celery_contrib]usedevelop}
[testenv:celery_contrib-py34-celery40-redis210-kombu43] [testenv:celery_contrib-py34-celery40-redis210-kombu43]
usedevelop = {[celery_contrib]usedevelop} usedevelop = {[celery_contrib]usedevelop}
[testenv:celery_contrib-py34-celery40-redis320-kombu44] [testenv:celery_contrib-py34-celery40-redis320-kombu44]
@ -527,16 +494,12 @@ usedevelop = {[celery_contrib]usedevelop}
usedevelop = {[celery_contrib]usedevelop} usedevelop = {[celery_contrib]usedevelop}
[testenv:celery_contrib-py36-celery41-redis320-kombu44] [testenv:celery_contrib-py36-celery41-redis320-kombu44]
usedevelop = {[celery_contrib]usedevelop} usedevelop = {[celery_contrib]usedevelop}
[testenv:celery_contrib-py27-celery42-redis210-kombu43]
usedevelop = {[celery_contrib]usedevelop}
[testenv:celery_contrib-py34-celery42-redis210-kombu43] [testenv:celery_contrib-py34-celery42-redis210-kombu43]
usedevelop = {[celery_contrib]usedevelop} usedevelop = {[celery_contrib]usedevelop}
[testenv:celery_contrib-py35-celery42-redis210-kombu43] [testenv:celery_contrib-py35-celery42-redis210-kombu43]
usedevelop = {[celery_contrib]usedevelop} usedevelop = {[celery_contrib]usedevelop}
[testenv:celery_contrib-py36-celery42-redis210-kombu43] [testenv:celery_contrib-py36-celery42-redis210-kombu43]
usedevelop = {[celery_contrib]usedevelop} usedevelop = {[celery_contrib]usedevelop}
[testenv:celery_contrib-py27-celery43-redis320-kombu44]
usedevelop = {[celery_contrib]usedevelop}
[testenv:celery_contrib-py34-celery43-redis320-kombu44] [testenv:celery_contrib-py34-celery43-redis320-kombu44]
usedevelop = {[celery_contrib]usedevelop} usedevelop = {[celery_contrib]usedevelop}
[testenv:celery_contrib-py35-celery43-redis320-kombu44] [testenv:celery_contrib-py35-celery43-redis320-kombu44]
@ -549,21 +512,6 @@ usedevelop = {[celery_contrib]usedevelop}
[falcon_autopatch] [falcon_autopatch]
setenv = setenv =
DATADOG_SERVICE_NAME=my-falcon DATADOG_SERVICE_NAME=my-falcon
[testenv:falcon_contrib_autopatch-py27-falcon10]
setenv =
{[falcon_autopatch]setenv}
[testenv:falcon_contrib_autopatch-py27-falcon11]
setenv =
{[falcon_autopatch]setenv}
[testenv:falcon_contrib_autopatch-py27-falcon12]
setenv =
{[falcon_autopatch]setenv}
[testenv:falcon_contrib_autopatch-py27-falcon13]
setenv =
{[falcon_autopatch]setenv}
[testenv:falcon_contrib_autopatch-py27-falcon14]
setenv =
{[falcon_autopatch]setenv}
[testenv:falcon_contrib_autopatch-py34-falcon10] [testenv:falcon_contrib_autopatch-py34-falcon10]
setenv = setenv =
{[falcon_autopatch]setenv} {[falcon_autopatch]setenv}
@ -630,16 +578,6 @@ setenv =
setenv = setenv =
DATADOG_SERVICE_NAME = foobar DATADOG_SERVICE_NAME = foobar
DATADOG_PYRAMID_DISTRIBUTED_TRACING = True DATADOG_PYRAMID_DISTRIBUTED_TRACING = True
[testenv:pyramid_contrib_autopatch-py27-pyramid17-webtest]
setenv =
{[pyramid_autopatch]setenv}
[testenv:pyramid_contrib_autopatch-py27-pyramid18-webtest]
setenv =
{[pyramid_autopatch]setenv}
[testenv:pyramid_contrib_autopatch-py27-pyramid19-webtest]
setenv =
{[pyramid_autopatch]setenv}
[testenv:pyramid_contrib_autopatch-py34-pyramid17-webtest] [testenv:pyramid_contrib_autopatch-py34-pyramid17-webtest]
setenv = setenv =
{[pyramid_autopatch]setenv} {[pyramid_autopatch]setenv}
@ -682,18 +620,6 @@ setenv =
setenv = setenv =
DATADOG_SERVICE_NAME = test.flask.service DATADOG_SERVICE_NAME = test.flask.service
DATADOG_PATCH_MODULES = jinja2:false DATADOG_PATCH_MODULES = jinja2:false
[testenv:flask_contrib_autopatch-py27-flask010-blinker]
setenv =
{[flask_autopatch]setenv}
[testenv:flask_contrib_autopatch-py27-flask011-blinker]
setenv =
{[flask_autopatch]setenv}
[testenv:flask_contrib_autopatch-py27-flask012-blinker]
setenv =
{[flask_autopatch]setenv}
[testenv:flask_contrib_autopatch-py27-flask10-blinker]
setenv =
{[flask_autopatch]setenv}
[testenv:flask_contrib_autopatch-py34-flask010-blinker] [testenv:flask_contrib_autopatch-py34-flask010-blinker]
setenv = setenv =
{[flask_autopatch]setenv} {[flask_autopatch]setenv}
@ -740,15 +666,6 @@ setenv =
setenv = setenv =
{[flask_autopatch]setenv} {[flask_autopatch]setenv}
[testenv:flask_contrib_autopatch-py37-flask10-blinker] [testenv:flask_contrib_autopatch-py37-flask10-blinker]
setenv =
{[flask_autopatch]setenv}
[testenv:flask_contrib_autopatch-py27-flask010-flaskcache013-memcached-redis210-blinker]
setenv =
{[flask_autopatch]setenv}
[testenv:flask_contrib_autopatch-py27-flask011-flaskcache013-memcached-redis210-blinker]
setenv =
{[flask_autopatch]setenv}
[testenv:flask_contrib_autopatch-py27-flask012-flaskcache013-memcached-redis210-blinker]
setenv = setenv =
{[flask_autopatch]setenv} {[flask_autopatch]setenv}
[testenv:flask_contrib_autopatch-py34-flask010-flaskcache013-memcached-redis210-blinker] [testenv:flask_contrib_autopatch-py34-flask010-flaskcache013-memcached-redis210-blinker]
@ -787,20 +704,10 @@ setenv =
[testenv:flask_contrib_autopatch-py37-flask012-flaskcache013-memcached-redis210-blinker] [testenv:flask_contrib_autopatch-py37-flask012-flaskcache013-memcached-redis210-blinker]
setenv = setenv =
{[flask_autopatch]setenv} {[flask_autopatch]setenv}
[testenv:flask_contrib_autopatch-py27-flask010-flaskcache012-memcached-redis210-blinker]
setenv =
{[flask_autopatch]setenv}
[testenv:flask_contrib_autopatch-py27-flask011-flaskcache012-memcached-redis210-blinker]
setenv =
{[flask_autopatch]setenv}
[bottle_autopatch] [bottle_autopatch]
setenv = setenv =
DATADOG_SERVICE_NAME = bottle-app DATADOG_SERVICE_NAME = bottle-app
[testenv:bottle_contrib_autopatch-py27-bottle11-webtest]
setenv =
{[bottle_autopatch]setenv}
[testenv:bottle_contrib_autopatch-py34-bottle11-webtest] [testenv:bottle_contrib_autopatch-py34-bottle11-webtest]
setenv = setenv =
{[bottle_autopatch]setenv} {[bottle_autopatch]setenv}
@ -811,9 +718,6 @@ setenv =
setenv = setenv =
{[bottle_autopatch]setenv} {[bottle_autopatch]setenv}
[testenv:bottle_contrib_autopatch-py37-bottle11-webtest] [testenv:bottle_contrib_autopatch-py37-bottle11-webtest]
setenv =
{[bottle_autopatch]setenv}
[testenv:bottle_contrib_autopatch-py27-bottle12-webtest]
setenv = setenv =
{[bottle_autopatch]setenv} {[bottle_autopatch]setenv}
[testenv:bottle_contrib_autopatch-py34-bottle12-webtest] [testenv:bottle_contrib_autopatch-py34-bottle12-webtest]