We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Improve tastypie support
Currently some [Django Tastypie](https://django-tastypie.readthedocs.io/en/latest/) views are named simply `tastypie.resources` in their operation name. We can probably find better names, and maybe support some Tastypie specific tags!

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/scout_apm/compat.py`
Content:
```
1 # coding=utf-8
2 from __future__ import absolute_import, division, print_function, unicode_literals
3 
4 import datetime as dt
5 import inspect
6 import sys
7 from functools import wraps
8 
9 string_type = str if sys.version_info[0] >= 3 else basestring  # noqa: F821
10 text_type = str if sys.version_info[0] >= 3 else unicode  # noqa: F821
11 
12 # Python 2 (and very early 3.x) didn't have ContextDecorator, so define it for ourselves
13 if sys.version_info < (3, 2):
14     import functools
15 
16     class ContextDecorator(object):
17         def __call__(self, f):
18             @functools.wraps(f)
19             def decorated(*args, **kwds):
20                 with self:
21                     return f(*args, **kwds)
22 
23             return decorated
24 
25 
26 else:
27     from contextlib import ContextDecorator
28 
29 try:
30     # Python 3.x
31     import queue
32 except ImportError:
33     # Python 2.x
34     import Queue as queue
35 
36 # datetime_to_timestamp converts a naive UTC datetime to a unix timestamp
37 if sys.version_info >= (3, 3):
38 
39     def datetime_to_timestamp(datetime_obj):
40         return datetime_obj.replace(tzinfo=dt.timezone.utc).timestamp()
41 
42 
43 else:
44     _EPOCH = dt.datetime(1970, 1, 1)
45 
46     def datetime_to_timestamp(datetime_obj):
47         return (datetime_obj - _EPOCH).total_seconds()
48 
49 
50 def text(value, encoding="utf-8", errors="strict"):
51     """
52     Convert a value to str on Python 3 and unicode on Python 2.
53     """
54     if isinstance(value, text_type):
55         return value
56     elif isinstance(value, bytes):
57         return text_type(value, encoding, errors)
58     else:
59         return text_type(value)
60 
61 
62 try:
63     from urllib.parse import urlencode
64 except ImportError:
65     from urllib import urlencode
66 
67 
68 def kwargs_only(func):
69     """
70     Make a function only accept keyword arguments.
71     This can be dropped in Python 3 in lieu of:
72         def foo(*, bar=default):
73     Source: https://pypi.org/project/kwargs-only/
74     """
75     if hasattr(inspect, "signature"):  # pragma: no cover
76         # Python 3
77         signature = inspect.signature(func)
78         first_arg_name = list(signature.parameters.keys())[0]
79     else:  # pragma: no cover
80         # Python 2
81         signature = inspect.getargspec(func)
82         first_arg_name = signature.args[0]
83 
84     if first_arg_name in ("self", "cls"):
85         allowable_args = 1
86     else:
87         allowable_args = 0
88 
89     @wraps(func)
90     def wrapper(*args, **kwargs):
91         if len(args) > allowable_args:
92             raise TypeError(
93                 "{} should only be called with keyword args".format(func.__name__)
94             )
95         return func(*args, **kwargs)
96 
97     return wrapper
98 
99 
100 __all__ = [
101     "ContextDecorator",
102     "datetime_to_timestamp",
103     "kwargs_only",
104     "queue",
105     "string_type",
106     "text",
107     "text_type",
108     "urlencode",
109 ]
110
```

Path: `src/scout_apm/django/middleware.py`
Content:
```
1 # coding=utf-8
2 from __future__ import absolute_import, division, print_function, unicode_literals
3 
4 import django
5 from django.conf import settings
6 
7 from scout_apm.core.config import scout_config
8 from scout_apm.core.tracked_request import TrackedRequest
9 from scout_apm.core.web_requests import (
10     create_filtered_path,
11     ignore_path,
12     track_amazon_request_queue_time,
13     track_request_queue_time,
14 )
15 
16 if django.VERSION >= (2, 0):
17     from django.urls import get_urlconf
18 else:
19     from django.core.urlresolvers import get_urlconf
20 
21 
22 def get_operation_name(request):
23     view_func = request.resolver_match.func
24     view_name = request.resolver_match._func_path
25 
26     if hasattr(view_func, "model_admin"):
27         # Seems to comes from Django admin (attribute only set on Django 1.9+)
28         admin_class = view_func.model_admin.__class__
29         view_name = (
30             admin_class.__module__
31             + "."
32             + admin_class.__name__
33             + "."
34             + view_func.__name__
35         )
36 
37     return "Controller/" + view_name
38 
39 
40 def track_request_view_data(request, tracked_request):
41     path = request.path
42     tracked_request.tag(
43         "path",
44         create_filtered_path(
45             path, [(k, v) for k, vs in request.GET.lists() for v in vs]
46         ),
47     )
48     if ignore_path(path):
49         tracked_request.tag("ignore_transaction", True)
50 
51     try:
52         # Determine a remote IP to associate with the request. The value is
53         # spoofable by the requester so this is not suitable to use in any
54         # security sensitive context.
55         user_ip = (
56             request.META.get("HTTP_X_FORWARDED_FOR", "").split(",")[0]
57             or request.META.get("HTTP_CLIENT_IP", "").split(",")[0]
58             or request.META.get("REMOTE_ADDR", None)
59         )
60         tracked_request.tag("user_ip", user_ip)
61     except Exception:
62         pass
63 
64     user = getattr(request, "user", None)
65     if user is not None:
66         try:
67             tracked_request.tag("username", user.get_username())
68         except Exception:
69             pass
70 
71     tracked_request.tag("urlconf", get_urlconf(settings.ROOT_URLCONF))
72 
73 
74 class MiddlewareTimingMiddleware(object):
75     """
76     Insert as early into the Middleware stack as possible (outermost layers),
77     so that other middlewares called after can be timed.
78     """
79 
80     def __init__(self, get_response):
81         self.get_response = get_response
82 
83     def __call__(self, request):
84         if not scout_config.value("monitor"):
85             return self.get_response(request)
86 
87         tracked_request = TrackedRequest.instance()
88 
89         tracked_request.start_span(
90             operation="Middleware", should_capture_backtrace=False
91         )
92         queue_time = request.META.get("HTTP_X_QUEUE_START") or request.META.get(
93             "HTTP_X_REQUEST_START", ""
94         )
95         queue_time_tracked = track_request_queue_time(queue_time, tracked_request)
96         if not queue_time_tracked:
97             track_amazon_request_queue_time(
98                 request.META.get("HTTP_X_AMZN_TRACE_ID", ""), tracked_request
99             )
100 
101         try:
102             return self.get_response(request)
103         finally:
104             tracked_request.stop_span()
105 
106 
107 class ViewTimingMiddleware(object):
108     """
109     Insert as deep into the middleware stack as possible, ideally wrapping no
110     other middleware. Designed to time the View itself
111     """
112 
113     def __init__(self, get_response):
114         self.get_response = get_response
115 
116     def __call__(self, request):
117         """
118         Wrap a single incoming request with start and stop calls.
119         This will start timing, but relies on the process_view callback to
120         capture more details about what view was really called, and other
121         similar info.
122 
123         If process_view isn't called, then the request will not
124         be recorded.  This can happen if a middleware further along the stack
125         doesn't call onward, and instead returns a response directly.
126         """
127         if not scout_config.value("monitor"):
128             return self.get_response(request)
129 
130         tracked_request = TrackedRequest.instance()
131 
132         # This operation name won't be recorded unless changed later in
133         # process_view
134         tracked_request.start_span(operation="Unknown", should_capture_backtrace=False)
135         try:
136             return self.get_response(request)
137         finally:
138             tracked_request.stop_span()
139 
140     def process_view(self, request, view_func, view_args, view_kwargs):
141         """
142         Capture details about the view_func that is about to execute
143         """
144         if not scout_config.value("monitor"):
145             return
146         tracked_request = TrackedRequest.instance()
147         tracked_request.mark_real_request()
148 
149         track_request_view_data(request, tracked_request)
150 
151         span = tracked_request.current_span()
152         if span is not None:
153             span.operation = get_operation_name(request)
154 
155     def process_exception(self, request, exception):
156         """
157         Mark this request as having errored out
158 
159         Does not modify or catch or otherwise change the exception thrown
160         """
161         if not scout_config.value("monitor"):
162             return
163         TrackedRequest.instance().tag("error", "true")
164 
165 
166 class OldStyleMiddlewareTimingMiddleware(object):
167     """
168     Insert as early into the Middleware stack as possible (outermost layers),
169     so that other middlewares called after can be timed.
170     """
171 
172     def process_request(self, request):
173         if not scout_config.value("monitor"):
174             return
175         tracked_request = TrackedRequest.instance()
176         request._scout_tracked_request = tracked_request
177 
178         queue_time = request.META.get("HTTP_X_QUEUE_START") or request.META.get(
179             "HTTP_X_REQUEST_START", ""
180         )
181         queue_time_tracked = track_request_queue_time(queue_time, tracked_request)
182         if not queue_time_tracked:
183             track_amazon_request_queue_time(
184                 request.META.get("HTTP_X_AMZN_TRACE_ID", ""), tracked_request
185             )
186 
187         tracked_request.start_span(
188             operation="Middleware", should_capture_backtrace=False
189         )
190 
191     def process_response(self, request, response):
192         # Only stop span if there's a request, but presume we are balanced,
193         # i.e. that custom instrumentation within the application is not
194         # causing errors
195         tracked_request = getattr(request, "_scout_tracked_request", None)
196         if tracked_request is not None:
197             tracked_request.stop_span()
198         return response
199 
200 
201 class OldStyleViewMiddleware(object):
202     def process_view(self, request, view_func, view_func_args, view_func_kwargs):
203         tracked_request = getattr(request, "_scout_tracked_request", None)
204         if tracked_request is None:
205             # Looks like OldStyleMiddlewareTimingMiddleware didn't run, so
206             # don't do anything
207             return
208 
209         tracked_request.mark_real_request()
210 
211         track_request_view_data(request, tracked_request)
212 
213         span = tracked_request.start_span(
214             operation=get_operation_name(request), should_capture_backtrace=False
215         )
216         # Save the span into the request, so we can check
217         # if we're matched up when stopping
218         request._scout_view_span = span
219 
220     def process_response(self, request, response):
221         tracked_request = getattr(request, "_scout_tracked_request", None)
222         if tracked_request is None:
223             # Looks like OldStyleMiddlewareTimingMiddleware didn't run, so
224             # don't do anything
225             return response
226 
227         # Only stop span if we started, but presume we are balanced, i.e. that
228         # custom instrumentation within the application is not causing errors
229         span = getattr(request, "_scout_view_span", None)
230         if span is not None:
231             tracked_request.stop_span()
232         return response
233 
234     def process_exception(self, request, exception):
235         tracked_request = getattr(request, "_scout_tracked_request", None)
236         if tracked_request is None:
237             # Looks like OldStyleMiddlewareTimingMiddleware didn't run, so
238             # don't do anything
239             return
240 
241         tracked_request.tag("error", "true")
242
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/scout_apm/compat.py b/src/scout_apm/compat.py
--- a/src/scout_apm/compat.py
+++ b/src/scout_apm/compat.py
@@ -8,6 +8,7 @@
 
 string_type = str if sys.version_info[0] >= 3 else basestring  # noqa: F821
 text_type = str if sys.version_info[0] >= 3 else unicode  # noqa: F821
+string_types = tuple({string_type, text_type})
 
 # Python 2 (and very early 3.x) didn't have ContextDecorator, so define it for ourselves
 if sys.version_info < (3, 2):
diff --git a/src/scout_apm/django/middleware.py b/src/scout_apm/django/middleware.py
--- a/src/scout_apm/django/middleware.py
+++ b/src/scout_apm/django/middleware.py
@@ -1,9 +1,12 @@
 # coding=utf-8
 from __future__ import absolute_import, division, print_function, unicode_literals
 
+import sys
+
 import django
 from django.conf import settings
 
+from scout_apm.compat import string_types
 from scout_apm.core.config import scout_config
 from scout_apm.core.tracked_request import TrackedRequest
 from scout_apm.core.web_requests import (
@@ -34,9 +37,52 @@
             + view_func.__name__
         )
 
+    # Seems to be a Tastypie Resource. Need to resort to some stack inspection
+    # to find a better name since its decorators don't wrap very well
+    if view_name == "tastypie.resources.wrapper":
+        tastypie_name = _get_tastypie_operation_name(request, view_func)
+        if tastypie_name is not None:
+            return tastypie_name
+
     return "Controller/" + view_name
 
 
+def _get_tastypie_operation_name(request, view_func):
+    try:
+        from tastypie.resources import Resource
+    except ImportError:
+        return None
+
+    if sys.version_info[0] == 2:  # pragma: no cover
+        try:
+            wrapper = view_func.__closure__[0].cell_contents
+        except (AttributeError, IndexError):
+            return None
+    elif sys.version_info[0] == 3:
+        try:
+            wrapper = view_func.__wrapped__
+        except AttributeError:
+            return None
+
+    if not hasattr(wrapper, "__closure__") or len(wrapper.__closure__) != 2:
+        return None
+
+    instance = wrapper.__closure__[0].cell_contents
+    if not isinstance(instance, Resource):  # pragma: no cover
+        return None
+
+    method_name = wrapper.__closure__[1].cell_contents
+    if not isinstance(method_name, string_types):  # pragma: no cover
+        return None
+
+    if method_name.startswith("dispatch_"):  # pragma: no cover
+        method_name = request.method.lower() + method_name.split("dispatch", 1)[1]
+
+    return "Controller/{}.{}.{}".format(
+        instance.__module__, instance.__class__.__name__, method_name
+    )
+
+
 def track_request_view_data(request, tracked_request):
     path = request.path
     tracked_request.tag(

{"golden_diff": "diff --git a/src/scout_apm/compat.py b/src/scout_apm/compat.py\n--- a/src/scout_apm/compat.py\n+++ b/src/scout_apm/compat.py\n@@ -8,6 +8,7 @@\n \n string_type = str if sys.version_info[0] >= 3 else basestring  # noqa: F821\n text_type = str if sys.version_info[0] >= 3 else unicode  # noqa: F821\n+string_types = tuple({string_type, text_type})\n \n # Python 2 (and very early 3.x) didn't have ContextDecorator, so define it for ourselves\n if sys.version_info < (3, 2):\ndiff --git a/src/scout_apm/django/middleware.py b/src/scout_apm/django/middleware.py\n--- a/src/scout_apm/django/middleware.py\n+++ b/src/scout_apm/django/middleware.py\n@@ -1,9 +1,12 @@\n # coding=utf-8\n from __future__ import absolute_import, division, print_function, unicode_literals\n \n+import sys\n+\n import django\n from django.conf import settings\n \n+from scout_apm.compat import string_types\n from scout_apm.core.config import scout_config\n from scout_apm.core.tracked_request import TrackedRequest\n from scout_apm.core.web_requests import (\n@@ -34,9 +37,52 @@\n             + view_func.__name__\n         )\n \n+    # Seems to be a Tastypie Resource. Need to resort to some stack inspection\n+    # to find a better name since its decorators don't wrap very well\n+    if view_name == \"tastypie.resources.wrapper\":\n+        tastypie_name = _get_tastypie_operation_name(request, view_func)\n+        if tastypie_name is not None:\n+            return tastypie_name\n+\n     return \"Controller/\" + view_name\n \n \n+def _get_tastypie_operation_name(request, view_func):\n+    try:\n+        from tastypie.resources import Resource\n+    except ImportError:\n+        return None\n+\n+    if sys.version_info[0] == 2:  # pragma: no cover\n+        try:\n+            wrapper = view_func.__closure__[0].cell_contents\n+        except (AttributeError, IndexError):\n+            return None\n+    elif sys.version_info[0] == 3:\n+        try:\n+            wrapper = view_func.__wrapped__\n+        except AttributeError:\n+            return None\n+\n+    if not hasattr(wrapper, \"__closure__\") or len(wrapper.__closure__) != 2:\n+        return None\n+\n+    instance = wrapper.__closure__[0].cell_contents\n+    if not isinstance(instance, Resource):  # pragma: no cover\n+        return None\n+\n+    method_name = wrapper.__closure__[1].cell_contents\n+    if not isinstance(method_name, string_types):  # pragma: no cover\n+        return None\n+\n+    if method_name.startswith(\"dispatch_\"):  # pragma: no cover\n+        method_name = request.method.lower() + method_name.split(\"dispatch\", 1)[1]\n+\n+    return \"Controller/{}.{}.{}\".format(\n+        instance.__module__, instance.__class__.__name__, method_name\n+    )\n+\n+\n def track_request_view_data(request, tracked_request):\n     path = request.path\n     tracked_request.tag(\n", "issue": "Improve tastypie support\nCurrently some [Django Tastypie](https://django-tastypie.readthedocs.io/en/latest/) views are named simply `tastypie.resources` in their operation name. We can probably find better names, and maybe support some Tastypie specific tags!\n", "before_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport datetime as dt\nimport inspect\nimport sys\nfrom functools import wraps\n\nstring_type = str if sys.version_info[0] >= 3 else basestring  # noqa: F821\ntext_type = str if sys.version_info[0] >= 3 else unicode  # noqa: F821\n\n# Python 2 (and very early 3.x) didn't have ContextDecorator, so define it for ourselves\nif sys.version_info < (3, 2):\n    import functools\n\n    class ContextDecorator(object):\n        def __call__(self, f):\n            @functools.wraps(f)\n            def decorated(*args, **kwds):\n                with self:\n                    return f(*args, **kwds)\n\n            return decorated\n\n\nelse:\n    from contextlib import ContextDecorator\n\ntry:\n    # Python 3.x\n    import queue\nexcept ImportError:\n    # Python 2.x\n    import Queue as queue\n\n# datetime_to_timestamp converts a naive UTC datetime to a unix timestamp\nif sys.version_info >= (3, 3):\n\n    def datetime_to_timestamp(datetime_obj):\n        return datetime_obj.replace(tzinfo=dt.timezone.utc).timestamp()\n\n\nelse:\n    _EPOCH = dt.datetime(1970, 1, 1)\n\n    def datetime_to_timestamp(datetime_obj):\n        return (datetime_obj - _EPOCH).total_seconds()\n\n\ndef text(value, encoding=\"utf-8\", errors=\"strict\"):\n    \"\"\"\n    Convert a value to str on Python 3 and unicode on Python 2.\n    \"\"\"\n    if isinstance(value, text_type):\n        return value\n    elif isinstance(value, bytes):\n        return text_type(value, encoding, errors)\n    else:\n        return text_type(value)\n\n\ntry:\n    from urllib.parse import urlencode\nexcept ImportError:\n    from urllib import urlencode\n\n\ndef kwargs_only(func):\n    \"\"\"\n    Make a function only accept keyword arguments.\n    This can be dropped in Python 3 in lieu of:\n        def foo(*, bar=default):\n    Source: https://pypi.org/project/kwargs-only/\n    \"\"\"\n    if hasattr(inspect, \"signature\"):  # pragma: no cover\n        # Python 3\n        signature = inspect.signature(func)\n        first_arg_name = list(signature.parameters.keys())[0]\n    else:  # pragma: no cover\n        # Python 2\n        signature = inspect.getargspec(func)\n        first_arg_name = signature.args[0]\n\n    if first_arg_name in (\"self\", \"cls\"):\n        allowable_args = 1\n    else:\n        allowable_args = 0\n\n    @wraps(func)\n    def wrapper(*args, **kwargs):\n        if len(args) > allowable_args:\n            raise TypeError(\n                \"{} should only be called with keyword args\".format(func.__name__)\n            )\n        return func(*args, **kwargs)\n\n    return wrapper\n\n\n__all__ = [\n    \"ContextDecorator\",\n    \"datetime_to_timestamp\",\n    \"kwargs_only\",\n    \"queue\",\n    \"string_type\",\n    \"text\",\n    \"text_type\",\n    \"urlencode\",\n]\n", "path": "src/scout_apm/compat.py"}, {"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport django\nfrom django.conf import settings\n\nfrom scout_apm.core.config import scout_config\nfrom scout_apm.core.tracked_request import TrackedRequest\nfrom scout_apm.core.web_requests import (\n    create_filtered_path,\n    ignore_path,\n    track_amazon_request_queue_time,\n    track_request_queue_time,\n)\n\nif django.VERSION >= (2, 0):\n    from django.urls import get_urlconf\nelse:\n    from django.core.urlresolvers import get_urlconf\n\n\ndef get_operation_name(request):\n    view_func = request.resolver_match.func\n    view_name = request.resolver_match._func_path\n\n    if hasattr(view_func, \"model_admin\"):\n        # Seems to comes from Django admin (attribute only set on Django 1.9+)\n        admin_class = view_func.model_admin.__class__\n        view_name = (\n            admin_class.__module__\n            + \".\"\n            + admin_class.__name__\n            + \".\"\n            + view_func.__name__\n        )\n\n    return \"Controller/\" + view_name\n\n\ndef track_request_view_data(request, tracked_request):\n    path = request.path\n    tracked_request.tag(\n        \"path\",\n        create_filtered_path(\n            path, [(k, v) for k, vs in request.GET.lists() for v in vs]\n        ),\n    )\n    if ignore_path(path):\n        tracked_request.tag(\"ignore_transaction\", True)\n\n    try:\n        # Determine a remote IP to associate with the request. The value is\n        # spoofable by the requester so this is not suitable to use in any\n        # security sensitive context.\n        user_ip = (\n            request.META.get(\"HTTP_X_FORWARDED_FOR\", \"\").split(\",\")[0]\n            or request.META.get(\"HTTP_CLIENT_IP\", \"\").split(\",\")[0]\n            or request.META.get(\"REMOTE_ADDR\", None)\n        )\n        tracked_request.tag(\"user_ip\", user_ip)\n    except Exception:\n        pass\n\n    user = getattr(request, \"user\", None)\n    if user is not None:\n        try:\n            tracked_request.tag(\"username\", user.get_username())\n        except Exception:\n            pass\n\n    tracked_request.tag(\"urlconf\", get_urlconf(settings.ROOT_URLCONF))\n\n\nclass MiddlewareTimingMiddleware(object):\n    \"\"\"\n    Insert as early into the Middleware stack as possible (outermost layers),\n    so that other middlewares called after can be timed.\n    \"\"\"\n\n    def __init__(self, get_response):\n        self.get_response = get_response\n\n    def __call__(self, request):\n        if not scout_config.value(\"monitor\"):\n            return self.get_response(request)\n\n        tracked_request = TrackedRequest.instance()\n\n        tracked_request.start_span(\n            operation=\"Middleware\", should_capture_backtrace=False\n        )\n        queue_time = request.META.get(\"HTTP_X_QUEUE_START\") or request.META.get(\n            \"HTTP_X_REQUEST_START\", \"\"\n        )\n        queue_time_tracked = track_request_queue_time(queue_time, tracked_request)\n        if not queue_time_tracked:\n            track_amazon_request_queue_time(\n                request.META.get(\"HTTP_X_AMZN_TRACE_ID\", \"\"), tracked_request\n            )\n\n        try:\n            return self.get_response(request)\n        finally:\n            tracked_request.stop_span()\n\n\nclass ViewTimingMiddleware(object):\n    \"\"\"\n    Insert as deep into the middleware stack as possible, ideally wrapping no\n    other middleware. Designed to time the View itself\n    \"\"\"\n\n    def __init__(self, get_response):\n        self.get_response = get_response\n\n    def __call__(self, request):\n        \"\"\"\n        Wrap a single incoming request with start and stop calls.\n        This will start timing, but relies on the process_view callback to\n        capture more details about what view was really called, and other\n        similar info.\n\n        If process_view isn't called, then the request will not\n        be recorded.  This can happen if a middleware further along the stack\n        doesn't call onward, and instead returns a response directly.\n        \"\"\"\n        if not scout_config.value(\"monitor\"):\n            return self.get_response(request)\n\n        tracked_request = TrackedRequest.instance()\n\n        # This operation name won't be recorded unless changed later in\n        # process_view\n        tracked_request.start_span(operation=\"Unknown\", should_capture_backtrace=False)\n        try:\n            return self.get_response(request)\n        finally:\n            tracked_request.stop_span()\n\n    def process_view(self, request, view_func, view_args, view_kwargs):\n        \"\"\"\n        Capture details about the view_func that is about to execute\n        \"\"\"\n        if not scout_config.value(\"monitor\"):\n            return\n        tracked_request = TrackedRequest.instance()\n        tracked_request.mark_real_request()\n\n        track_request_view_data(request, tracked_request)\n\n        span = tracked_request.current_span()\n        if span is not None:\n            span.operation = get_operation_name(request)\n\n    def process_exception(self, request, exception):\n        \"\"\"\n        Mark this request as having errored out\n\n        Does not modify or catch or otherwise change the exception thrown\n        \"\"\"\n        if not scout_config.value(\"monitor\"):\n            return\n        TrackedRequest.instance().tag(\"error\", \"true\")\n\n\nclass OldStyleMiddlewareTimingMiddleware(object):\n    \"\"\"\n    Insert as early into the Middleware stack as possible (outermost layers),\n    so that other middlewares called after can be timed.\n    \"\"\"\n\n    def process_request(self, request):\n        if not scout_config.value(\"monitor\"):\n            return\n        tracked_request = TrackedRequest.instance()\n        request._scout_tracked_request = tracked_request\n\n        queue_time = request.META.get(\"HTTP_X_QUEUE_START\") or request.META.get(\n            \"HTTP_X_REQUEST_START\", \"\"\n        )\n        queue_time_tracked = track_request_queue_time(queue_time, tracked_request)\n        if not queue_time_tracked:\n            track_amazon_request_queue_time(\n                request.META.get(\"HTTP_X_AMZN_TRACE_ID\", \"\"), tracked_request\n            )\n\n        tracked_request.start_span(\n            operation=\"Middleware\", should_capture_backtrace=False\n        )\n\n    def process_response(self, request, response):\n        # Only stop span if there's a request, but presume we are balanced,\n        # i.e. that custom instrumentation within the application is not\n        # causing errors\n        tracked_request = getattr(request, \"_scout_tracked_request\", None)\n        if tracked_request is not None:\n            tracked_request.stop_span()\n        return response\n\n\nclass OldStyleViewMiddleware(object):\n    def process_view(self, request, view_func, view_func_args, view_func_kwargs):\n        tracked_request = getattr(request, \"_scout_tracked_request\", None)\n        if tracked_request is None:\n            # Looks like OldStyleMiddlewareTimingMiddleware didn't run, so\n            # don't do anything\n            return\n\n        tracked_request.mark_real_request()\n\n        track_request_view_data(request, tracked_request)\n\n        span = tracked_request.start_span(\n            operation=get_operation_name(request), should_capture_backtrace=False\n        )\n        # Save the span into the request, so we can check\n        # if we're matched up when stopping\n        request._scout_view_span = span\n\n    def process_response(self, request, response):\n        tracked_request = getattr(request, \"_scout_tracked_request\", None)\n        if tracked_request is None:\n            # Looks like OldStyleMiddlewareTimingMiddleware didn't run, so\n            # don't do anything\n            return response\n\n        # Only stop span if we started, but presume we are balanced, i.e. that\n        # custom instrumentation within the application is not causing errors\n        span = getattr(request, \"_scout_view_span\", None)\n        if span is not None:\n            tracked_request.stop_span()\n        return response\n\n    def process_exception(self, request, exception):\n        tracked_request = getattr(request, \"_scout_tracked_request\", None)\n        if tracked_request is None:\n            # Looks like OldStyleMiddlewareTimingMiddleware didn't run, so\n            # don't do anything\n            return\n\n        tracked_request.tag(\"error\", \"true\")\n", "path": "src/scout_apm/django/middleware.py"}], "after_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport datetime as dt\nimport inspect\nimport sys\nfrom functools import wraps\n\nstring_type = str if sys.version_info[0] >= 3 else basestring  # noqa: F821\ntext_type = str if sys.version_info[0] >= 3 else unicode  # noqa: F821\nstring_types = tuple({string_type, text_type})\n\n# Python 2 (and very early 3.x) didn't have ContextDecorator, so define it for ourselves\nif sys.version_info < (3, 2):\n    import functools\n\n    class ContextDecorator(object):\n        def __call__(self, f):\n            @functools.wraps(f)\n            def decorated(*args, **kwds):\n                with self:\n                    return f(*args, **kwds)\n\n            return decorated\n\n\nelse:\n    from contextlib import ContextDecorator\n\ntry:\n    # Python 3.x\n    import queue\nexcept ImportError:\n    # Python 2.x\n    import Queue as queue\n\n# datetime_to_timestamp converts a naive UTC datetime to a unix timestamp\nif sys.version_info >= (3, 3):\n\n    def datetime_to_timestamp(datetime_obj):\n        return datetime_obj.replace(tzinfo=dt.timezone.utc).timestamp()\n\n\nelse:\n    _EPOCH = dt.datetime(1970, 1, 1)\n\n    def datetime_to_timestamp(datetime_obj):\n        return (datetime_obj - _EPOCH).total_seconds()\n\n\ndef text(value, encoding=\"utf-8\", errors=\"strict\"):\n    \"\"\"\n    Convert a value to str on Python 3 and unicode on Python 2.\n    \"\"\"\n    if isinstance(value, text_type):\n        return value\n    elif isinstance(value, bytes):\n        return text_type(value, encoding, errors)\n    else:\n        return text_type(value)\n\n\ntry:\n    from urllib.parse import urlencode\nexcept ImportError:\n    from urllib import urlencode\n\n\ndef kwargs_only(func):\n    \"\"\"\n    Make a function only accept keyword arguments.\n    This can be dropped in Python 3 in lieu of:\n        def foo(*, bar=default):\n    Source: https://pypi.org/project/kwargs-only/\n    \"\"\"\n    if hasattr(inspect, \"signature\"):  # pragma: no cover\n        # Python 3\n        signature = inspect.signature(func)\n        first_arg_name = list(signature.parameters.keys())[0]\n    else:  # pragma: no cover\n        # Python 2\n        signature = inspect.getargspec(func)\n        first_arg_name = signature.args[0]\n\n    if first_arg_name in (\"self\", \"cls\"):\n        allowable_args = 1\n    else:\n        allowable_args = 0\n\n    @wraps(func)\n    def wrapper(*args, **kwargs):\n        if len(args) > allowable_args:\n            raise TypeError(\n                \"{} should only be called with keyword args\".format(func.__name__)\n            )\n        return func(*args, **kwargs)\n\n    return wrapper\n\n\n__all__ = [\n    \"ContextDecorator\",\n    \"datetime_to_timestamp\",\n    \"kwargs_only\",\n    \"queue\",\n    \"string_type\",\n    \"text\",\n    \"text_type\",\n    \"urlencode\",\n]\n", "path": "src/scout_apm/compat.py"}, {"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport sys\n\nimport django\nfrom django.conf import settings\n\nfrom scout_apm.compat import string_types\nfrom scout_apm.core.config import scout_config\nfrom scout_apm.core.tracked_request import TrackedRequest\nfrom scout_apm.core.web_requests import (\n    create_filtered_path,\n    ignore_path,\n    track_amazon_request_queue_time,\n    track_request_queue_time,\n)\n\nif django.VERSION >= (2, 0):\n    from django.urls import get_urlconf\nelse:\n    from django.core.urlresolvers import get_urlconf\n\n\ndef get_operation_name(request):\n    view_func = request.resolver_match.func\n    view_name = request.resolver_match._func_path\n\n    if hasattr(view_func, \"model_admin\"):\n        # Seems to comes from Django admin (attribute only set on Django 1.9+)\n        admin_class = view_func.model_admin.__class__\n        view_name = (\n            admin_class.__module__\n            + \".\"\n            + admin_class.__name__\n            + \".\"\n            + view_func.__name__\n        )\n\n    # Seems to be a Tastypie Resource. Need to resort to some stack inspection\n    # to find a better name since its decorators don't wrap very well\n    if view_name == \"tastypie.resources.wrapper\":\n        tastypie_name = _get_tastypie_operation_name(request, view_func)\n        if tastypie_name is not None:\n            return tastypie_name\n\n    return \"Controller/\" + view_name\n\n\ndef _get_tastypie_operation_name(request, view_func):\n    try:\n        from tastypie.resources import Resource\n    except ImportError:\n        return None\n\n    if sys.version_info[0] == 2:  # pragma: no cover\n        try:\n            wrapper = view_func.__closure__[0].cell_contents\n        except (AttributeError, IndexError):\n            return None\n    elif sys.version_info[0] == 3:\n        try:\n            wrapper = view_func.__wrapped__\n        except AttributeError:\n            return None\n\n    if not hasattr(wrapper, \"__closure__\") or len(wrapper.__closure__) != 2:\n        return None\n\n    instance = wrapper.__closure__[0].cell_contents\n    if not isinstance(instance, Resource):  # pragma: no cover\n        return None\n\n    method_name = wrapper.__closure__[1].cell_contents\n    if not isinstance(method_name, string_types):  # pragma: no cover\n        return None\n\n    if method_name.startswith(\"dispatch_\"):  # pragma: no cover\n        method_name = request.method.lower() + method_name.split(\"dispatch\", 1)[1]\n\n    return \"Controller/{}.{}.{}\".format(\n        instance.__module__, instance.__class__.__name__, method_name\n    )\n\n\ndef track_request_view_data(request, tracked_request):\n    path = request.path\n    tracked_request.tag(\n        \"path\",\n        create_filtered_path(\n            path, [(k, v) for k, vs in request.GET.lists() for v in vs]\n        ),\n    )\n    if ignore_path(path):\n        tracked_request.tag(\"ignore_transaction\", True)\n\n    try:\n        # Determine a remote IP to associate with the request. The value is\n        # spoofable by the requester so this is not suitable to use in any\n        # security sensitive context.\n        user_ip = (\n            request.META.get(\"HTTP_X_FORWARDED_FOR\", \"\").split(\",\")[0]\n            or request.META.get(\"HTTP_CLIENT_IP\", \"\").split(\",\")[0]\n            or request.META.get(\"REMOTE_ADDR\", None)\n        )\n        tracked_request.tag(\"user_ip\", user_ip)\n    except Exception:\n        pass\n\n    user = getattr(request, \"user\", None)\n    if user is not None:\n        try:\n            tracked_request.tag(\"username\", user.get_username())\n        except Exception:\n            pass\n\n    tracked_request.tag(\"urlconf\", get_urlconf(settings.ROOT_URLCONF))\n\n\nclass MiddlewareTimingMiddleware(object):\n    \"\"\"\n    Insert as early into the Middleware stack as possible (outermost layers),\n    so that other middlewares called after can be timed.\n    \"\"\"\n\n    def __init__(self, get_response):\n        self.get_response = get_response\n\n    def __call__(self, request):\n        if not scout_config.value(\"monitor\"):\n            return self.get_response(request)\n\n        tracked_request = TrackedRequest.instance()\n\n        tracked_request.start_span(\n            operation=\"Middleware\", should_capture_backtrace=False\n        )\n        queue_time = request.META.get(\"HTTP_X_QUEUE_START\") or request.META.get(\n            \"HTTP_X_REQUEST_START\", \"\"\n        )\n        queue_time_tracked = track_request_queue_time(queue_time, tracked_request)\n        if not queue_time_tracked:\n            track_amazon_request_queue_time(\n                request.META.get(\"HTTP_X_AMZN_TRACE_ID\", \"\"), tracked_request\n            )\n\n        try:\n            return self.get_response(request)\n        finally:\n            tracked_request.stop_span()\n\n\nclass ViewTimingMiddleware(object):\n    \"\"\"\n    Insert as deep into the middleware stack as possible, ideally wrapping no\n    other middleware. Designed to time the View itself\n    \"\"\"\n\n    def __init__(self, get_response):\n        self.get_response = get_response\n\n    def __call__(self, request):\n        \"\"\"\n        Wrap a single incoming request with start and stop calls.\n        This will start timing, but relies on the process_view callback to\n        capture more details about what view was really called, and other\n        similar info.\n\n        If process_view isn't called, then the request will not\n        be recorded.  This can happen if a middleware further along the stack\n        doesn't call onward, and instead returns a response directly.\n        \"\"\"\n        if not scout_config.value(\"monitor\"):\n            return self.get_response(request)\n\n        tracked_request = TrackedRequest.instance()\n\n        # This operation name won't be recorded unless changed later in\n        # process_view\n        tracked_request.start_span(operation=\"Unknown\", should_capture_backtrace=False)\n        try:\n            return self.get_response(request)\n        finally:\n            tracked_request.stop_span()\n\n    def process_view(self, request, view_func, view_args, view_kwargs):\n        \"\"\"\n        Capture details about the view_func that is about to execute\n        \"\"\"\n        if not scout_config.value(\"monitor\"):\n            return\n        tracked_request = TrackedRequest.instance()\n        tracked_request.mark_real_request()\n\n        track_request_view_data(request, tracked_request)\n\n        span = tracked_request.current_span()\n        if span is not None:\n            span.operation = get_operation_name(request)\n\n    def process_exception(self, request, exception):\n        \"\"\"\n        Mark this request as having errored out\n\n        Does not modify or catch or otherwise change the exception thrown\n        \"\"\"\n        if not scout_config.value(\"monitor\"):\n            return\n        TrackedRequest.instance().tag(\"error\", \"true\")\n\n\nclass OldStyleMiddlewareTimingMiddleware(object):\n    \"\"\"\n    Insert as early into the Middleware stack as possible (outermost layers),\n    so that other middlewares called after can be timed.\n    \"\"\"\n\n    def process_request(self, request):\n        if not scout_config.value(\"monitor\"):\n            return\n        tracked_request = TrackedRequest.instance()\n        request._scout_tracked_request = tracked_request\n\n        queue_time = request.META.get(\"HTTP_X_QUEUE_START\") or request.META.get(\n            \"HTTP_X_REQUEST_START\", \"\"\n        )\n        queue_time_tracked = track_request_queue_time(queue_time, tracked_request)\n        if not queue_time_tracked:\n            track_amazon_request_queue_time(\n                request.META.get(\"HTTP_X_AMZN_TRACE_ID\", \"\"), tracked_request\n            )\n\n        tracked_request.start_span(\n            operation=\"Middleware\", should_capture_backtrace=False\n        )\n\n    def process_response(self, request, response):\n        # Only stop span if there's a request, but presume we are balanced,\n        # i.e. that custom instrumentation within the application is not\n        # causing errors\n        tracked_request = getattr(request, \"_scout_tracked_request\", None)\n        if tracked_request is not None:\n            tracked_request.stop_span()\n        return response\n\n\nclass OldStyleViewMiddleware(object):\n    def process_view(self, request, view_func, view_func_args, view_func_kwargs):\n        tracked_request = getattr(request, \"_scout_tracked_request\", None)\n        if tracked_request is None:\n            # Looks like OldStyleMiddlewareTimingMiddleware didn't run, so\n            # don't do anything\n            return\n\n        tracked_request.mark_real_request()\n\n        track_request_view_data(request, tracked_request)\n\n        span = tracked_request.start_span(\n            operation=get_operation_name(request), should_capture_backtrace=False\n        )\n        # Save the span into the request, so we can check\n        # if we're matched up when stopping\n        request._scout_view_span = span\n\n    def process_response(self, request, response):\n        tracked_request = getattr(request, \"_scout_tracked_request\", None)\n        if tracked_request is None:\n            # Looks like OldStyleMiddlewareTimingMiddleware didn't run, so\n            # don't do anything\n            return response\n\n        # Only stop span if we started, but presume we are balanced, i.e. that\n        # custom instrumentation within the application is not causing errors\n        span = getattr(request, \"_scout_view_span\", None)\n        if span is not None:\n            tracked_request.stop_span()\n        return response\n\n    def process_exception(self, request, exception):\n        tracked_request = getattr(request, \"_scout_tracked_request\", None)\n        if tracked_request is None:\n            # Looks like OldStyleMiddlewareTimingMiddleware didn't run, so\n            # don't do anything\n            return\n\n        tracked_request.tag(\"error\", \"true\")\n", "path": "src/scout_apm/django/middleware.py"}]}