Repository: DataDog/dd-trace-py

Issue:
SyntaxError running dd-trace-py on Python 3
I received this syntax error running a program with `dd-trace-py` enabled on Python 3:

```
  File "/mnt/jenkins/workspace/generic-python3-phabricator-pipeline/src/.eggs/ddtrace-0.9.2-py3.5.egg/ddtrace/contrib/pylons/middleware.py", line 58
    raise typ, val, tb
             ^
SyntaxError: invalid syntax
```

Looking at the code in that file, [it's clearly not Python 3-compatible](https://github.com/DataDog/dd-trace-py/blob/75b727569ac5a7aa4115af6b54ba763f29dd571e/ddtrace/contrib/pylons/middleware.py#L81). Not sure much more needs to be said. This code needs to be fixed to be Python 3 compatible.


Discussion:
Hello @nickwilliams-eventbrite ! We're investigating the issue, I'll reach you back once we have a proper test for that code path. Thanks for reporting!
Also, do you have the `SyntaxError` only when using our middleware in your Pylons app? In that case, are you using the official https://pypi.org/project/Pylons/ package?

I may miss something, but does it work properly with Python 3? As far as I can see it's reported as `Python :: 2 :: Only`. Also, if I run the test suite that emulates a real application, I hit this exception:
```
======================================================================
ERROR: Failure: SyntaxError (invalid syntax (core.py, line 108))
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/emanuele/workspaces/datadog/dd-trace-py/.tox/py35-pylons10/lib/python3.5/site-packages/nose/case.py", line 134, in run
    self.runTest(result)
  File "/Users/emanuele/workspaces/datadog/dd-trace-py/.tox/py35-pylons10/lib/python3.5/site-packages/nose/case.py", line 152, in runTest
    test(result)
  File "/Users/emanuele/.pyenv/versions/3.5.2/lib/python3.5/unittest/case.py", line 648, in __call__
    return self.run(*args, **kwds)
  File "/Users/emanuele/.pyenv/versions/3.5.2/lib/python3.5/unittest/case.py", line 608, in run
    self._feedErrorsToResult(result, outcome.errors)
  File "/Users/emanuele/.pyenv/versions/3.5.2/lib/python3.5/unittest/case.py", line 538, in _feedErrorsToResult
    result.addError(test, exc_info)
  File "/Users/emanuele/workspaces/datadog/dd-trace-py/.tox/py35-pylons10/lib/python3.5/site-packages/nose/proxy.py", line 132, in addError
    self.result.addError(self.test, self._prepareErr(err))
  File "/Users/emanuele/workspaces/datadog/dd-trace-py/.tox/py35-pylons10/lib/python3.5/site-packages/nose/result.py", line 61, in addError
    exc_info = self._exc_info_to_string(err, test)
  File "/Users/emanuele/workspaces/datadog/dd-trace-py/.tox/py35-pylons10/lib/python3.5/site-packages/nose/result.py", line 187, in _exc_info_to_string
    return _TextTestResult._exc_info_to_string(self, err, test)
  File "/Users/emanuele/.pyenv/versions/3.5.2/lib/python3.5/unittest/result.py", line 186, in _exc_info_to_string
    exctype, value, tb, limit=length, capture_locals=self.tb_locals)
  File "/Users/emanuele/.pyenv/versions/3.5.2/lib/python3.5/traceback.py", line 481, in __init__
    self.filename = exc_value.filename
AttributeError: 'SyntaxError' object has no attribute 'filename'
```

Where the involved line is [core.py#L108](https://github.com/Pylons/pylons/blob/069471fd347442ee93027a015a30fc64cf409b54/pylons/controllers/core.py#L108) that is not Python 3 compatible.

Probably you're using another library or fork? Not strictly related to the issue you're reporting, just want to be sure I'm getting entirely your use case.

Thanks a lot!
We're not using Pylons or any fork of Pylons or a Pylon-related library or app. I've no idea how our code resulted in execution reaching this line. I can't even reliably duplicate it. The `SyntaxError` happens only when a unit test on a certain, internal project fails for some reason, but not every time that test fails. When the `SyntaxError` happens, it masks the cause of the test failure.

I see now that Pylons doesn't support Python 3; however, every line of code in `dd-trace-py` should, in my opinion, support Python 3, especially given that we are quickly approaching the EOL for Python 2.
@nickwilliams-eventbrite sure thing! was only curious about the stack. I will schedule the work to make it compatible. Maybe we can have couple of iterations to be sure everything works on your stack. Sounds good?

Resolving patch:
diff --git a/ddtrace/compat.py b/ddtrace/compat.py
--- a/ddtrace/compat.py
+++ b/ddtrace/compat.py
@@ -79,6 +79,25 @@ def to_unicode(s):
     msgpack_type = bytes
     numeric_types = (int, float)
 
+if PY2:
+    # avoids Python 3 `SyntaxError`
+    # this block will be replaced with the `six` library
+    from .utils.reraise import _reraise as reraise
+else:
+    def reraise(tp, value, tb=None):
+        """Python 3 re-raise function. This function is internal and
+        will be replaced entirely with the `six` library.
+        """
+        try:
+            if value is None:
+                value = tp()
+            if value.__traceback__ is not tb:
+                raise value.with_traceback(tb)
+            raise value
+        finally:
+            value = None
+            tb = None
+
 
 __all__ = [
     'httplib',
@@ -89,4 +108,5 @@ def to_unicode(s):
     'StringIO',
     'urlencode',
     'parse',
+    'reraise',
 ]
diff --git a/ddtrace/contrib/pylons/middleware.py b/ddtrace/contrib/pylons/middleware.py
--- a/ddtrace/contrib/pylons/middleware.py
+++ b/ddtrace/contrib/pylons/middleware.py
@@ -7,6 +7,7 @@
 from .renderer import trace_rendering
 from .constants import CONFIG_MIDDLEWARE
 
+from ...compat import reraise
 from ...ext import http, AppTypes
 from ...propagation.http import HTTPPropagator
 
@@ -78,7 +79,7 @@ def _start_response(status, *args, **kwargs):
                 span.error = 1
 
                 # re-raise the original exception with its original traceback
-                raise typ, val, tb
+                reraise(typ, val, tb=tb)
             except SystemExit:
                 span.set_tag(http.STATUS_CODE, 500)
                 span.error = 1
diff --git a/ddtrace/utils/reraise.py b/ddtrace/utils/reraise.py
new file mode 100644
--- /dev/null
+++ b/ddtrace/utils/reraise.py
@@ -0,0 +1,5 @@
+def _reraise(tp, value, tb=None):
+    """Python 2 re-raise function. This function is internal and
+    will be replaced entirely with the `six` library.
+    """
+    raise tp, value, tb


Test patch:
diff --git a/tests/test_compat.py b/tests/test_compat.py
--- a/tests/test_compat.py
+++ b/tests/test_compat.py
@@ -1,11 +1,12 @@
 # -*- coding: utf-8 -*-
 # Define source file encoding to support raw unicode characters in Python 2
+import sys
 
 # Third party
-from nose.tools import eq_
+from nose.tools import eq_, assert_raises
 
 # Project
-from ddtrace.compat import to_unicode, PY2
+from ddtrace.compat import to_unicode, PY2, reraise
 
 
 # Use different test suites for each Python version, this allows us to test the expected
@@ -92,3 +93,26 @@ def test_to_unicode_non_string(self):
             eq_(to_unicode(True), 'True')
             eq_(to_unicode(None), 'None')
             eq_(to_unicode(dict(key='value')), '{\'key\': \'value\'}')
+
+
+class TestPy2Py3Compat(object):
+    """Common tests to ensure functions are both Python 2 and
+    Python 3 compatible.
+    """
+    def test_reraise(self):
+        # ensure the `raise` function is Python 2/3 compatible
+        with assert_raises(Exception) as ex:
+            try:
+                raise Exception('Ouch!')
+            except Exception as e:
+                # original exception we want to re-raise
+                (typ, val, tb) = sys.exc_info()
+                try:
+                    # this exception doesn't allow a re-raise, and we need
+                    # to use the previous one collected via `exc_info()`
+                    raise Exception('Obfuscate!')
+                except Exception:
+                    pass
+                # this call must be Python 2 and 3 compatible
+                raise reraise(typ, val, tb)
+        eq_(ex.exception.args[0], 'Ouch!')