scout-apm 3.3.0__cp38-cp38-musllinux_1_2_i686.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. scout_apm/__init__.py +0 -0
  2. scout_apm/api/__init__.py +197 -0
  3. scout_apm/async_/__init__.py +1 -0
  4. scout_apm/async_/api.py +41 -0
  5. scout_apm/async_/instruments/__init__.py +0 -0
  6. scout_apm/async_/instruments/jinja2.py +13 -0
  7. scout_apm/async_/starlette.py +101 -0
  8. scout_apm/bottle.py +86 -0
  9. scout_apm/celery.py +153 -0
  10. scout_apm/compat.py +104 -0
  11. scout_apm/core/__init__.py +99 -0
  12. scout_apm/core/_objtrace.cpython-38-i386-linux-gnu.so +0 -0
  13. scout_apm/core/agent/__init__.py +0 -0
  14. scout_apm/core/agent/commands.py +250 -0
  15. scout_apm/core/agent/manager.py +319 -0
  16. scout_apm/core/agent/socket.py +211 -0
  17. scout_apm/core/backtrace.py +116 -0
  18. scout_apm/core/cli/__init__.py +0 -0
  19. scout_apm/core/cli/core_agent_manager.py +32 -0
  20. scout_apm/core/config.py +404 -0
  21. scout_apm/core/context.py +140 -0
  22. scout_apm/core/error.py +95 -0
  23. scout_apm/core/error_service.py +167 -0
  24. scout_apm/core/metadata.py +66 -0
  25. scout_apm/core/n_plus_one_tracker.py +41 -0
  26. scout_apm/core/objtrace.py +24 -0
  27. scout_apm/core/platform_detection.py +66 -0
  28. scout_apm/core/queue_time.py +99 -0
  29. scout_apm/core/sampler.py +149 -0
  30. scout_apm/core/samplers/__init__.py +0 -0
  31. scout_apm/core/samplers/cpu.py +76 -0
  32. scout_apm/core/samplers/memory.py +23 -0
  33. scout_apm/core/samplers/thread.py +41 -0
  34. scout_apm/core/stacktracer.py +30 -0
  35. scout_apm/core/threading.py +56 -0
  36. scout_apm/core/tracked_request.py +328 -0
  37. scout_apm/core/web_requests.py +167 -0
  38. scout_apm/django/__init__.py +7 -0
  39. scout_apm/django/apps.py +137 -0
  40. scout_apm/django/instruments/__init__.py +0 -0
  41. scout_apm/django/instruments/huey.py +30 -0
  42. scout_apm/django/instruments/sql.py +140 -0
  43. scout_apm/django/instruments/template.py +35 -0
  44. scout_apm/django/middleware.py +211 -0
  45. scout_apm/django/request.py +144 -0
  46. scout_apm/dramatiq.py +42 -0
  47. scout_apm/falcon.py +142 -0
  48. scout_apm/flask/__init__.py +118 -0
  49. scout_apm/flask/sqlalchemy.py +28 -0
  50. scout_apm/huey.py +54 -0
  51. scout_apm/hug.py +40 -0
  52. scout_apm/instruments/__init__.py +21 -0
  53. scout_apm/instruments/elasticsearch.py +263 -0
  54. scout_apm/instruments/jinja2.py +127 -0
  55. scout_apm/instruments/pymongo.py +105 -0
  56. scout_apm/instruments/redis.py +77 -0
  57. scout_apm/instruments/urllib3.py +80 -0
  58. scout_apm/rq.py +85 -0
  59. scout_apm/sqlalchemy.py +38 -0
  60. scout_apm-3.3.0.dist-info/LICENSE +21 -0
  61. scout_apm-3.3.0.dist-info/METADATA +82 -0
  62. scout_apm-3.3.0.dist-info/RECORD +65 -0
  63. scout_apm-3.3.0.dist-info/WHEEL +5 -0
  64. scout_apm-3.3.0.dist-info/entry_points.txt +2 -0
  65. scout_apm-3.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,137 @@
1
+ # coding=utf-8
2
+
3
+ import sys
4
+
5
+ import django
6
+ from django.apps import AppConfig
7
+ from django.conf import settings
8
+ from django.core.signals import got_request_exception
9
+ from django.test.signals import setting_changed
10
+
11
+ if django.VERSION < (3, 1):
12
+ from django.views.debug import get_safe_settings
13
+ else:
14
+ from django.views.debug import SafeExceptionReporterFilter
15
+
16
+ def get_safe_settings():
17
+ return SafeExceptionReporterFilter().get_safe_settings()
18
+
19
+
20
+ import scout_apm.core
21
+ from scout_apm.core.config import scout_config
22
+ from scout_apm.core.error import ErrorMonitor
23
+ from scout_apm.django.instruments.huey import ensure_huey_instrumented
24
+ from scout_apm.django.instruments.sql import ensure_sql_instrumented
25
+ from scout_apm.django.instruments.template import ensure_templates_instrumented
26
+ from scout_apm.django.request import get_request_components
27
+
28
+
29
+ class ScoutApmDjangoConfig(AppConfig):
30
+ name = "scout_apm"
31
+ verbose_name = "Scout Apm (Django)"
32
+
33
+ def ready(self):
34
+ self.update_scout_config_from_django_settings()
35
+ setting_changed.connect(self.on_setting_changed)
36
+
37
+ # Finish installing the agent. If the agent isn't installed for any
38
+ # reason, return without installing instruments
39
+ installed = scout_apm.core.install()
40
+ if not installed:
41
+ return
42
+
43
+ if scout_config.value("errors_enabled"):
44
+ got_request_exception.connect(self.on_got_request_exception)
45
+
46
+ self.install_middleware()
47
+
48
+ # Setup Instruments
49
+ ensure_huey_instrumented()
50
+ ensure_sql_instrumented()
51
+ ensure_templates_instrumented()
52
+
53
+ def update_scout_config_from_django_settings(self, **kwargs):
54
+ for name in dir(settings):
55
+ self.on_setting_changed(name)
56
+
57
+ def on_got_request_exception(self, request, **kwargs):
58
+ """
59
+ Process this exception with the error monitoring solution.
60
+ """
61
+ ErrorMonitor.send(
62
+ sys.exc_info(),
63
+ request_components=get_request_components(request),
64
+ request_path=request.path,
65
+ request_params=dict(request.GET.lists()),
66
+ session=dict(request.session.items())
67
+ if hasattr(request, "session")
68
+ else None,
69
+ environment=get_safe_settings(),
70
+ )
71
+
72
+ def on_setting_changed(self, setting, **kwargs):
73
+ cast = None
74
+ if setting == "BASE_DIR":
75
+ scout_name = "application_root"
76
+ cast = str
77
+ elif setting.startswith("SCOUT_"):
78
+ scout_name = setting.replace("SCOUT_", "").lower()
79
+ else:
80
+ return
81
+
82
+ try:
83
+ value = getattr(settings, setting)
84
+ except AttributeError:
85
+ # It was removed
86
+ scout_config.unset(scout_name)
87
+ else:
88
+ if cast is not None:
89
+ value = cast(value)
90
+ scout_config.set(**{scout_name: value})
91
+
92
+ def install_middleware(self):
93
+ """
94
+ Attempts to insert the ScoutApm middleware as the first middleware
95
+ (first on incoming requests, last on outgoing responses).
96
+ """
97
+ from django.conf import settings
98
+
99
+ # If MIDDLEWARE is set, update that, with handling of tuple vs array forms
100
+ if getattr(settings, "MIDDLEWARE", None) is not None:
101
+ timing_middleware = "scout_apm.django.middleware.MiddlewareTimingMiddleware"
102
+ view_middleware = "scout_apm.django.middleware.ViewTimingMiddleware"
103
+
104
+ if isinstance(settings.MIDDLEWARE, tuple):
105
+ if timing_middleware not in settings.MIDDLEWARE:
106
+ settings.MIDDLEWARE = (timing_middleware,) + settings.MIDDLEWARE
107
+ if view_middleware not in settings.MIDDLEWARE:
108
+ settings.MIDDLEWARE = settings.MIDDLEWARE + (view_middleware,)
109
+ else:
110
+ if timing_middleware not in settings.MIDDLEWARE:
111
+ settings.MIDDLEWARE.insert(0, timing_middleware)
112
+ if view_middleware not in settings.MIDDLEWARE:
113
+ settings.MIDDLEWARE.append(view_middleware)
114
+
115
+ # Otherwise, we're doing old style middleware, do the same thing with
116
+ # the same handling of tuple vs array forms
117
+ else:
118
+ timing_middleware = (
119
+ "scout_apm.django.middleware.OldStyleMiddlewareTimingMiddleware"
120
+ )
121
+ view_middleware = "scout_apm.django.middleware.OldStyleViewMiddleware"
122
+
123
+ if isinstance(settings.MIDDLEWARE_CLASSES, tuple):
124
+ if timing_middleware not in settings.MIDDLEWARE_CLASSES:
125
+ settings.MIDDLEWARE_CLASSES = (
126
+ timing_middleware,
127
+ ) + settings.MIDDLEWARE_CLASSES
128
+
129
+ if view_middleware not in settings.MIDDLEWARE_CLASSES:
130
+ settings.MIDDLEWARE_CLASSES = settings.MIDDLEWARE_CLASSES + (
131
+ view_middleware,
132
+ )
133
+ else:
134
+ if timing_middleware not in settings.MIDDLEWARE_CLASSES:
135
+ settings.MIDDLEWARE_CLASSES.insert(0, timing_middleware)
136
+ if view_middleware not in settings.MIDDLEWARE_CLASSES:
137
+ settings.MIDDLEWARE_CLASSES.append(view_middleware)
File without changes
@@ -0,0 +1,30 @@
1
+ # coding=utf-8
2
+
3
+ import logging
4
+
5
+ from django.conf import settings
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ huey_instrumented = False
10
+
11
+
12
+ def ensure_huey_instrumented():
13
+ global huey_instrumented
14
+ if huey_instrumented:
15
+ return
16
+ huey_instrumented = True
17
+
18
+ # Avoid importing if not installed
19
+ if "huey.contrib.djhuey" not in settings.INSTALLED_APPS: # pragma: no cover
20
+ return
21
+
22
+ try:
23
+ from huey.contrib.djhuey import HUEY
24
+ except ImportError: # pragma: no cover
25
+ return
26
+
27
+ from scout_apm.huey import attach_scout_handlers
28
+
29
+ attach_scout_handlers(HUEY)
30
+ logger.debug("Instrumented huey.contrib.djhuey")
@@ -0,0 +1,140 @@
1
+ # coding=utf-8
2
+
3
+ import logging
4
+
5
+ import django
6
+ import wrapt
7
+ from django.db import connections
8
+ from django.db.backends.signals import connection_created
9
+ from django.db.backends.utils import CursorWrapper
10
+
11
+ from scout_apm.core.tracked_request import TrackedRequest
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ sql_instrumented = False
16
+
17
+
18
+ def ensure_sql_instrumented():
19
+ global sql_instrumented
20
+ if sql_instrumented:
21
+ return
22
+ sql_instrumented = True
23
+
24
+ if django.VERSION >= (2, 0):
25
+ for connection in connections.all():
26
+ install_db_execute_hook(connection=connection)
27
+ connection_created.connect(install_db_execute_hook)
28
+ logger.debug("Installed DB connection created signal handler")
29
+ else:
30
+ CursorWrapper.execute = execute_wrapper(CursorWrapper.execute)
31
+ CursorWrapper.executemany = executemany_wrapper(CursorWrapper.executemany)
32
+
33
+ logger.debug("Monkey patched SQL")
34
+
35
+
36
+ def db_execute_hook(execute, sql, params, many, context):
37
+ """
38
+ Database instrumentation hook for Django 2.0+
39
+ https://docs.djangoproject.com/en/2.0/topics/db/instrumentation/
40
+ """
41
+ if many:
42
+ operation = "SQL/Many"
43
+ else:
44
+ operation = "SQL/Query"
45
+
46
+ if sql is not None:
47
+ tracked_request = TrackedRequest.instance()
48
+ span = tracked_request.start_span(operation=operation)
49
+ span.tag("db.statement", sql)
50
+
51
+ try:
52
+ return execute(sql, params, many, context)
53
+ finally:
54
+ if sql is not None:
55
+ tracked_request.stop_span()
56
+ if tracked_request.n_plus_one_tracker.should_capture_backtrace(
57
+ sql=sql,
58
+ duration=span.duration(),
59
+ count=(1 if not many else len(params)),
60
+ ):
61
+ span.capture_backtrace()
62
+
63
+
64
+ def install_db_execute_hook(connection, **kwargs):
65
+ """
66
+ Install db_execute_hook on the given database connection.
67
+
68
+ Rather than use the documented API of the `execute_wrapper()` context
69
+ manager, directly insert the hook. This is done because:
70
+ 1. Deleting the context manager closes it, so it's not possible to enter
71
+ it here and not exit it, unless we store it forever in some variable
72
+ 2. We want to be the first hook, so we can capture every query (although
73
+ potentially later hooks will change the SQL)
74
+ 3. We want to be idempotent and only install the hook once
75
+ """
76
+ if db_execute_hook not in connection.execute_wrappers:
77
+ connection.execute_wrappers.insert(0, db_execute_hook)
78
+
79
+
80
+ @wrapt.decorator
81
+ def execute_wrapper(wrapped, instance, args, kwargs):
82
+ """
83
+ CursorWrapper.execute() wrapper for Django < 2.0
84
+ """
85
+ try:
86
+ sql = _extract_sql(*args, **kwargs)
87
+ except TypeError:
88
+ sql = None
89
+
90
+ if sql is not None:
91
+ tracked_request = TrackedRequest.instance()
92
+ span = tracked_request.start_span(operation="SQL/Query")
93
+ span.tag("db.statement", sql)
94
+
95
+ try:
96
+ return wrapped(*args, **kwargs)
97
+ finally:
98
+ if sql is not None:
99
+ tracked_request.stop_span()
100
+ if tracked_request.n_plus_one_tracker.should_capture_backtrace(
101
+ sql, span.duration()
102
+ ):
103
+ span.capture_backtrace()
104
+
105
+
106
+ def _extract_sql(sql, *args, **kwargs):
107
+ return sql
108
+
109
+
110
+ @wrapt.decorator
111
+ def executemany_wrapper(wrapped, instance, args, kwargs):
112
+ """
113
+ CursorWrapper.executemany() wrapper for Django < 2.0
114
+ """
115
+ try:
116
+ sql, param_list = _extract_sql_param_list(*args, **kwargs)
117
+ except TypeError:
118
+ sql = None
119
+ param_list = None
120
+
121
+ if sql is not None:
122
+ tracked_request = TrackedRequest.instance()
123
+ span = tracked_request.start_span(operation="SQL/Many")
124
+ span.tag("db.statement", sql)
125
+
126
+ try:
127
+ return wrapped(*args, **kwargs)
128
+ finally:
129
+ if sql is not None:
130
+ tracked_request.stop_span()
131
+ if tracked_request.n_plus_one_tracker.should_capture_backtrace(
132
+ sql=sql,
133
+ duration=span.duration(),
134
+ count=len(param_list),
135
+ ):
136
+ span.capture_backtrace()
137
+
138
+
139
+ def _extract_sql_param_list(sql, param_list, *args, **kwargs):
140
+ return sql, param_list
@@ -0,0 +1,35 @@
1
+ # coding=utf-8
2
+
3
+ import logging
4
+
5
+ from django.template import Template
6
+ from django.template.loader_tags import BlockNode
7
+
8
+ from scout_apm.core.stacktracer import trace_method
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ templates_instrumented = False
13
+
14
+
15
+ def ensure_templates_instrumented():
16
+ global templates_instrumented
17
+ if templates_instrumented:
18
+ return
19
+ templates_instrumented = True
20
+
21
+ @trace_method(Template)
22
+ def __init__(self, *args, **kwargs):
23
+ name = args[2] if len(args) >= 3 else "<Unknown Template>"
24
+ return ("Template/Compile", {"name": name})
25
+
26
+ @trace_method(Template)
27
+ def render(self, *args, **kwargs):
28
+ name = self.name if self.name is not None else "<Unknown Template>"
29
+ return ("Template/Render", {"name": name})
30
+
31
+ @trace_method(BlockNode, "render")
32
+ def render_block(self, *args, **kwargs):
33
+ return ("Block/Render", {"name": self.name})
34
+
35
+ logger.debug("Monkey patched Templates")
@@ -0,0 +1,211 @@
1
+ # coding=utf-8
2
+
3
+ from django.conf import settings
4
+ from django.urls import get_urlconf
5
+
6
+ from scout_apm.core.config import scout_config
7
+ from scout_apm.core.queue_time import track_request_queue_time
8
+ from scout_apm.core.tracked_request import TrackedRequest
9
+ from scout_apm.core.web_requests import create_filtered_path, ignore_path
10
+ from scout_apm.django.request import get_controller_name
11
+
12
+
13
+ def track_request_view_data(request, tracked_request):
14
+ path = request.path
15
+ tracked_request.tag(
16
+ "path",
17
+ create_filtered_path(
18
+ path, [(k, v) for k, vs in request.GET.lists() for v in vs]
19
+ ),
20
+ )
21
+ if ignore_path(path):
22
+ tracked_request.tag("ignore_transaction", True)
23
+
24
+ if scout_config.value("collect_remote_ip"):
25
+ try:
26
+ # Determine a remote IP to associate with the request. The value is
27
+ # spoofable by the requester so this is not suitable to use in any
28
+ # security sensitive context.
29
+ user_ip = (
30
+ request.META.get("HTTP_X_FORWARDED_FOR", "").split(",")[0]
31
+ or request.META.get("HTTP_CLIENT_IP", "").split(",")[0]
32
+ or request.META.get("REMOTE_ADDR", None)
33
+ )
34
+ tracked_request.tag("user_ip", user_ip)
35
+ except Exception:
36
+ pass
37
+
38
+ # Django's request.user caches in this attribute on first access. We only
39
+ # want to track the user if the application code has touched request.user
40
+ # because touching it causes session access, which adds "Cookie" to the
41
+ # "Vary" header.
42
+ user = getattr(request, "_cached_user", None)
43
+ if user is not None:
44
+ try:
45
+ tracked_request.tag("username", user.get_username())
46
+ except Exception:
47
+ pass
48
+
49
+ tracked_request.tag("urlconf", get_urlconf(settings.ROOT_URLCONF))
50
+
51
+
52
+ class MiddlewareTimingMiddleware(object):
53
+ """
54
+ Insert as early into the Middleware stack as possible (outermost layers),
55
+ so that other middlewares called after can be timed.
56
+ """
57
+
58
+ def __init__(self, get_response):
59
+ self.get_response = get_response
60
+
61
+ def __call__(self, request):
62
+ if not scout_config.value("monitor"):
63
+ return self.get_response(request)
64
+
65
+ tracked_request = TrackedRequest.instance()
66
+
67
+ queue_time = request.META.get("HTTP_X_QUEUE_START") or request.META.get(
68
+ "HTTP_X_REQUEST_START", ""
69
+ )
70
+ track_request_queue_time(queue_time, tracked_request)
71
+
72
+ with tracked_request.span(
73
+ operation="Middleware",
74
+ should_capture_backtrace=False,
75
+ ):
76
+ return self.get_response(request)
77
+
78
+
79
+ class ViewTimingMiddleware(object):
80
+ """
81
+ Insert as deep into the middleware stack as possible, ideally wrapping no
82
+ other middleware. Designed to time the View itself
83
+ """
84
+
85
+ def __init__(self, get_response):
86
+ self.get_response = get_response
87
+
88
+ def __call__(self, request):
89
+ """
90
+ Wrap a single incoming request with start and stop calls.
91
+ This will start timing, but relies on the process_view callback to
92
+ capture more details about what view was really called, and other
93
+ similar info.
94
+
95
+ If process_view isn't called, then the request will not
96
+ be recorded. This can happen if a middleware further along the stack
97
+ doesn't call onward, and instead returns a response directly.
98
+ """
99
+ if not scout_config.value("monitor"):
100
+ return self.get_response(request)
101
+
102
+ tracked_request = TrackedRequest.instance()
103
+
104
+ # This operation name won't be recorded unless changed later in
105
+ # process_view
106
+ with tracked_request.span(operation="Unknown", should_capture_backtrace=False):
107
+ response = self.get_response(request)
108
+ track_request_view_data(request, tracked_request)
109
+ if 500 <= response.status_code <= 599:
110
+ tracked_request.tag("error", "true")
111
+ return response
112
+
113
+ def process_view(self, request, view_func, view_args, view_kwargs):
114
+ """
115
+ Capture details about the view_func that is about to execute
116
+ """
117
+ if not scout_config.value("monitor"):
118
+ return
119
+ tracked_request = TrackedRequest.instance()
120
+ tracked_request.is_real_request = True
121
+
122
+ span = tracked_request.current_span()
123
+ if span is not None:
124
+ span.operation = get_controller_name(request)
125
+ tracked_request.operation = span.operation
126
+
127
+ def process_exception(self, request, exception):
128
+ """
129
+ Mark this request as having errored out
130
+
131
+ Does not modify or catch or otherwise change the exception thrown
132
+ """
133
+ if not scout_config.value("monitor"):
134
+ return
135
+ TrackedRequest.instance().tag("error", "true")
136
+
137
+
138
+ class OldStyleMiddlewareTimingMiddleware(object):
139
+ """
140
+ Insert as early into the Middleware stack as possible (outermost layers),
141
+ so that other middlewares called after can be timed.
142
+ """
143
+
144
+ def process_request(self, request):
145
+ if not scout_config.value("monitor"):
146
+ return
147
+ tracked_request = TrackedRequest.instance()
148
+ request._scout_tracked_request = tracked_request
149
+
150
+ queue_time = request.META.get("HTTP_X_QUEUE_START") or request.META.get(
151
+ "HTTP_X_REQUEST_START", ""
152
+ )
153
+ track_request_queue_time(queue_time, tracked_request)
154
+
155
+ tracked_request.start_span(
156
+ operation="Middleware", should_capture_backtrace=False
157
+ )
158
+
159
+ def process_response(self, request, response):
160
+ # Only stop span if there's a request, but presume we are balanced,
161
+ # i.e. that custom instrumentation within the application is not
162
+ # causing errors
163
+ tracked_request = getattr(request, "_scout_tracked_request", None)
164
+ if tracked_request is not None:
165
+ if 500 <= response.status_code <= 599:
166
+ tracked_request.tag("error", "true")
167
+ tracked_request.stop_span()
168
+ return response
169
+
170
+
171
+ class OldStyleViewMiddleware(object):
172
+ def process_view(self, request, view_func, view_func_args, view_func_kwargs):
173
+ tracked_request = getattr(request, "_scout_tracked_request", None)
174
+ if tracked_request is None:
175
+ # Looks like OldStyleMiddlewareTimingMiddleware didn't run, so
176
+ # don't do anything
177
+ return
178
+
179
+ tracked_request.is_real_request = True
180
+
181
+ span = tracked_request.start_span(
182
+ operation=get_controller_name(request), should_capture_backtrace=False
183
+ )
184
+ # Save the span into the request, so we can check
185
+ # if we're matched up when stopping
186
+ request._scout_view_span = span
187
+
188
+ def process_response(self, request, response):
189
+ tracked_request = getattr(request, "_scout_tracked_request", None)
190
+ if tracked_request is None:
191
+ # Looks like OldStyleMiddlewareTimingMiddleware didn't run, so
192
+ # don't do anything
193
+ return response
194
+
195
+ track_request_view_data(request, tracked_request)
196
+
197
+ # Only stop span if we started, but presume we are balanced, i.e. that
198
+ # custom instrumentation within the application is not causing errors
199
+ span = getattr(request, "_scout_view_span", None)
200
+ if span is not None:
201
+ tracked_request.stop_span()
202
+ return response
203
+
204
+ def process_exception(self, request, exception):
205
+ tracked_request = getattr(request, "_scout_tracked_request", None)
206
+ if tracked_request is None:
207
+ # Looks like OldStyleMiddlewareTimingMiddleware didn't run, so
208
+ # don't do anything
209
+ return
210
+
211
+ tracked_request.tag("error", "true")
@@ -0,0 +1,144 @@
1
+ # coding=utf-8
2
+
3
+ from scout_apm.core.web_requests import RequestComponents
4
+
5
+
6
+ def get_controller_name(request):
7
+ view_func = request.resolver_match.func
8
+ view_name = request.resolver_match._func_path
9
+ if hasattr(view_func, "view_class"):
10
+ view_func = view_func.view_class
11
+ view_name = "{}.{}".format(view_func.__module__, view_func.__name__)
12
+
13
+ django_admin_components = _get_django_admin_components(view_func)
14
+ if django_admin_components:
15
+ view_name = "{}.{}.{}".format(
16
+ django_admin_components.module,
17
+ django_admin_components.controller,
18
+ django_admin_components.action,
19
+ )
20
+
21
+ django_rest_framework_components = _get_django_rest_framework_components(
22
+ request, view_func
23
+ )
24
+ if django_rest_framework_components is not None:
25
+ view_name = "{}.{}.{}".format(
26
+ django_rest_framework_components.module,
27
+ django_rest_framework_components.controller,
28
+ django_rest_framework_components.action,
29
+ )
30
+
31
+ # Seems to be a Tastypie Resource. Need to resort to some stack inspection
32
+ # to find a better name since its decorators don't wrap very well
33
+ if view_name == "tastypie.resources.wrapper":
34
+ tastypie_components = _get_tastypie_components(request, view_func)
35
+ if tastypie_components is not None:
36
+ view_name = "{}.{}.{}".format(
37
+ tastypie_components.module,
38
+ tastypie_components.controller,
39
+ tastypie_components.action,
40
+ )
41
+
42
+ return "Controller/{}".format(view_name)
43
+
44
+
45
+ def get_request_components(request):
46
+ if not request.resolver_match:
47
+ return None
48
+ view_func = request.resolver_match.func
49
+ view_name = request.resolver_match._func_path
50
+ if hasattr(view_func, "view_class"):
51
+ view_func = view_func.view_class
52
+ request_components = RequestComponents(
53
+ module=view_func.__module__,
54
+ controller=view_func.__name__,
55
+ action=request.method,
56
+ )
57
+
58
+ django_admin_components = _get_django_admin_components(view_func)
59
+ if django_admin_components:
60
+ request_components = django_admin_components
61
+
62
+ django_rest_framework_components = _get_django_rest_framework_components(
63
+ request, view_func
64
+ )
65
+ if django_rest_framework_components is not None:
66
+ request_components = django_rest_framework_components
67
+
68
+ # Seems to be a Tastypie Resource. Need to resort to some stack inspection
69
+ # to find a better name since its decorators don't wrap very well
70
+ if view_name == "tastypie.resources.wrapper":
71
+ tastypie_components = _get_tastypie_components(request, view_func)
72
+ if tastypie_components is not None:
73
+ request_components = tastypie_components
74
+ return request_components
75
+
76
+
77
+ def _get_django_admin_components(view_func):
78
+ if hasattr(view_func, "model_admin"):
79
+ # Seems to comes from Django admin (attribute only set on Django 1.9+)
80
+ admin_class = view_func.model_admin.__class__
81
+ return RequestComponents(
82
+ module=admin_class.__module__,
83
+ controller=admin_class.__name__,
84
+ action=view_func.__name__,
85
+ )
86
+ return None
87
+
88
+
89
+ def _get_django_rest_framework_components(request, view_func):
90
+ try:
91
+ from rest_framework.viewsets import ViewSetMixin
92
+ except ImportError:
93
+ return None
94
+
95
+ kls = getattr(view_func, "cls", None)
96
+ if isinstance(kls, type) and not issubclass(kls, ViewSetMixin):
97
+ return None
98
+
99
+ # Get 'actions' set in ViewSetMixin.as_view
100
+ actions = getattr(view_func, "actions", None)
101
+ if not actions or not isinstance(actions, dict):
102
+ return None
103
+
104
+ method_lower = request.method.lower()
105
+ if method_lower not in actions:
106
+ return None
107
+
108
+ return RequestComponents(
109
+ module=view_func.__module__,
110
+ controller=view_func.__name__,
111
+ action=actions[method_lower],
112
+ )
113
+
114
+
115
+ def _get_tastypie_components(request, view_func):
116
+ try:
117
+ from tastypie.resources import Resource
118
+ except ImportError:
119
+ return None
120
+
121
+ try:
122
+ wrapper = view_func.__wrapped__
123
+ except AttributeError:
124
+ return None
125
+
126
+ if not hasattr(wrapper, "__closure__") or len(wrapper.__closure__) != 2:
127
+ return None
128
+
129
+ instance = wrapper.__closure__[0].cell_contents
130
+ if not isinstance(instance, Resource): # pragma: no cover
131
+ return None
132
+
133
+ method_name = wrapper.__closure__[1].cell_contents
134
+ if not isinstance(method_name, str): # pragma: no cover
135
+ return None
136
+
137
+ if method_name.startswith("dispatch_"): # pragma: no cover
138
+ method_name = request.method.lower() + method_name.split("dispatch", 1)[1]
139
+
140
+ return RequestComponents(
141
+ module=instance.__module__,
142
+ controller=instance.__class__.__name__,
143
+ action=method_name,
144
+ )