scout-apm 3.3.0__cp310-cp310-musllinux_1_2_aarch64.whl

Sign up to get free protection for your applications and to get access to all the features.
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-310-aarch64-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 +94 -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
+ )