scout-apm 3.3.0__cp38-cp38-musllinux_1_2_i686.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-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
scout_apm/__init__.py ADDED
File without changes
@@ -0,0 +1,197 @@
1
+ # coding=utf-8
2
+
3
+ import sys
4
+
5
+ import scout_apm.core
6
+ from scout_apm.compat import ContextDecorator, text
7
+ from scout_apm.core.config import ScoutConfig
8
+ from scout_apm.core.error import ErrorMonitor
9
+ from scout_apm.core.tracked_request import TrackedRequest
10
+
11
+ # The async_ module can only be shipped on Python 3.6+
12
+ try:
13
+ from scout_apm.async_.api import AsyncDecoratorMixin
14
+ except ImportError:
15
+
16
+ class AsyncDecoratorMixin(object):
17
+ pass
18
+
19
+
20
+ __all__ = [
21
+ "BackgroundTransaction",
22
+ "Config",
23
+ "Context",
24
+ "Error",
25
+ "WebTransaction",
26
+ "install",
27
+ "instrument",
28
+ ]
29
+
30
+
31
+ class Context(object):
32
+ @classmethod
33
+ def add(self, key, value):
34
+ """Adds context to the currently executing request.
35
+
36
+ :key: Any String identifying the request context.
37
+ Example: "user_ip", "plan", "alert_count"
38
+ :value: Any json-serializable type.
39
+ Example: "1.1.1.1", "free", 100
40
+ :returns: nothing.
41
+ """
42
+ TrackedRequest.instance().tag(key, value)
43
+
44
+
45
+ class Config(ScoutConfig):
46
+ pass
47
+
48
+
49
+ install = scout_apm.core.install
50
+
51
+
52
+ def ignore_transaction():
53
+ TrackedRequest.instance().tag("ignore_transaction", True)
54
+
55
+
56
+ class instrument(AsyncDecoratorMixin, ContextDecorator):
57
+ def __init__(self, operation, kind="Custom", tags=None):
58
+ self.operation = text(kind) + "/" + text(operation)
59
+ if tags is None:
60
+ self.tags = {}
61
+ else:
62
+ self.tags = tags
63
+
64
+ def __enter__(self):
65
+ tracked_request = TrackedRequest.instance()
66
+ self.span = tracked_request.start_span(operation=self.operation)
67
+ for key, value in self.tags.items():
68
+ self.tag(key, value)
69
+ return self
70
+
71
+ def __exit__(self, *exc):
72
+ tracked_request = TrackedRequest.instance()
73
+ tracked_request.stop_span()
74
+ return False
75
+
76
+ def tag(self, key, value):
77
+ if self.span is not None:
78
+ self.span.tag(key, value)
79
+
80
+
81
+ class Transaction(AsyncDecoratorMixin, ContextDecorator):
82
+ """
83
+ This Class is not meant to be used directly.
84
+ Use one of the subclasses
85
+ (WebTransaction or BackgroundTransaction)
86
+ """
87
+
88
+ def __init__(self, name, tags=None):
89
+ self.name = text(name)
90
+ if tags is None:
91
+ self.tags = {}
92
+ else:
93
+ self.tags = tags
94
+
95
+ @classmethod
96
+ def start(cls, kind, name, tags=None):
97
+ operation = text(kind) + "/" + text(name)
98
+
99
+ tracked_request = TrackedRequest.instance()
100
+ tracked_request.operation = operation
101
+ tracked_request.is_real_request = True
102
+ span = tracked_request.start_span(
103
+ operation=operation, should_capture_backtrace=False
104
+ )
105
+ if tags is not None:
106
+ for key, value in tags.items():
107
+ tracked_request.tag(key, value)
108
+ return span
109
+
110
+ @classmethod
111
+ def stop(cls):
112
+ tracked_request = TrackedRequest.instance()
113
+ tracked_request.stop_span()
114
+ return True
115
+
116
+ # __enter__ must be defined by child classes.
117
+
118
+ # *exc is any exception raised. Ignore that
119
+ def __exit__(self, *exc):
120
+ WebTransaction.stop()
121
+ return False
122
+
123
+ def tag(self, key, value):
124
+ if self.span is not None:
125
+ self.span.tag(key, value)
126
+
127
+
128
+ class WebTransaction(Transaction):
129
+ @classmethod
130
+ def start(cls, name, tags=None):
131
+ super(WebTransaction, cls).start("Controller", text(name), tags)
132
+
133
+ def __enter__(self):
134
+ super(WebTransaction, self).start("Controller", self.name, self.tags)
135
+
136
+
137
+ class BackgroundTransaction(Transaction):
138
+ @classmethod
139
+ def start(cls, name, tags=None):
140
+ super(BackgroundTransaction, cls).start("Job", text(name), tags)
141
+
142
+ def __enter__(self):
143
+ super(BackgroundTransaction, self).start("Job", self.name, self.tags)
144
+
145
+
146
+ def rename_transaction(name):
147
+ if name is not None:
148
+ tracked_request = TrackedRequest.instance()
149
+ tracked_request.tag("transaction.name", name)
150
+
151
+
152
+ class Error(object):
153
+ @classmethod
154
+ def capture(
155
+ cls,
156
+ exception,
157
+ request_path=None,
158
+ request_params=None,
159
+ session=None,
160
+ custom_controller=None,
161
+ custom_params=None,
162
+ ):
163
+ """
164
+ Capture the exception manually.
165
+
166
+ Utilizes sys.exc_info to gather the traceback. This has the side
167
+ effect that if another exception is raised before calling
168
+ ``Error.capture``, the traceback will match the most recently
169
+ raised exception.
170
+
171
+ Includes any context added for the TrackedRequest.
172
+
173
+ :exception: Any exception.
174
+ :request_path: Any String identifying the relative path of the request.
175
+ Example: "/hello-world/"
176
+ :request_params: Any json-serializable dict representing the
177
+ querystring parameters.
178
+ Example: {"page": 1}
179
+ :session: Any json-serializable dict representing the
180
+ request session.
181
+ Example: {"step": 0}
182
+ :custom_controller: Any String identifying the controller or job.
183
+ Example: "send_email"
184
+ :custom_params: Any json-serializable dict.
185
+ Example: {"to": "scout@test.com", "from": "no-reply@test.com"}
186
+ :returns: nothing.
187
+ """
188
+ if isinstance(exception, Exception):
189
+ exc_info = (exception.__class__, exception, sys.exc_info()[2])
190
+ ErrorMonitor.send(
191
+ exc_info,
192
+ request_path=request_path,
193
+ request_params=request_params,
194
+ session=session,
195
+ custom_controller=custom_controller,
196
+ custom_params=custom_params,
197
+ )
@@ -0,0 +1 @@
1
+ # coding=utf-8
@@ -0,0 +1,41 @@
1
+ # coding=utf-8
2
+
3
+ from functools import wraps
4
+
5
+
6
+ class AsyncDecoratorMixin(object):
7
+ """Provide the ability to decorate both sync and async functions."""
8
+
9
+ is_async = False
10
+
11
+ @classmethod
12
+ def async_(cls, operation, tags=None, **kwargs):
13
+ """
14
+ Instrument an async function via a decorator.
15
+
16
+ This will return an awaitable which must be awaited.
17
+ Using this on a synchronous function will raise a
18
+ RuntimeError.
19
+
20
+ ``
21
+ @instrument.async_("Foo")
22
+ async def foo():
23
+ ...
24
+ ``
25
+ """
26
+ instance = cls(operation, tags=tags, **kwargs)
27
+ instance.is_async = True
28
+ return instance
29
+
30
+ def __call__(self, func):
31
+ if self.is_async:
32
+ # Until https://bugs.python.org/issue37398 has a resolution,
33
+ # manually wrap the async function
34
+ @wraps(func)
35
+ async def decorated(*args, **kwds):
36
+ with self._recreate_cm():
37
+ return await func(*args, **kwds)
38
+
39
+ return decorated
40
+ else:
41
+ return super().__call__(func)
File without changes
@@ -0,0 +1,13 @@
1
+ # coding=utf-8
2
+
3
+ import wrapt
4
+
5
+ from scout_apm.core.tracked_request import TrackedRequest
6
+
7
+
8
+ @wrapt.decorator
9
+ async def wrapped_render_async(wrapped, instance, args, kwargs):
10
+ tracked_request = TrackedRequest.instance()
11
+ with tracked_request.span(operation="Template/Render") as span:
12
+ span.tag("name", instance.name)
13
+ return await wrapped(*args, **kwargs)
@@ -0,0 +1,101 @@
1
+ # coding=utf-8
2
+
3
+ import wrapt
4
+ from starlette.background import BackgroundTask
5
+
6
+ import scout_apm.core
7
+ from scout_apm.core.tracked_request import TrackedRequest
8
+ from scout_apm.core.web_requests import asgi_track_request_data
9
+
10
+
11
+ class ScoutMiddleware:
12
+ def __init__(self, app):
13
+ self.app = app
14
+ installed = scout_apm.core.install()
15
+ self._do_nothing = not installed
16
+ if installed:
17
+ install_background_instrumentation()
18
+
19
+ async def __call__(self, scope, receive, send):
20
+ if self._do_nothing or scope["type"] != "http":
21
+ return await self.app(scope, receive, send)
22
+
23
+ tracked_request = TrackedRequest.instance()
24
+ # Assume the request is real until we determine it's not. This is useful
25
+ # when the asyncio instrumentation is determining if a new Task should
26
+ # reuse the existing tracked request.
27
+ tracked_request.is_real_request = True
28
+ # Can't name controller until post-routing - see final clause
29
+ controller_span = tracked_request.start_span(operation="Controller/Unknown")
30
+
31
+ asgi_track_request_data(scope, tracked_request)
32
+
33
+ def grab_extra_data():
34
+ if "endpoint" in scope:
35
+ # Rename top span
36
+ endpoint = scope["endpoint"]
37
+ if not hasattr(endpoint, "__qualname__"):
38
+ endpoint = endpoint.__class__
39
+ controller_span.operation = "Controller/{}.{}".format(
40
+ endpoint.__module__,
41
+ endpoint.__qualname__,
42
+ )
43
+ tracked_request.operation = controller_span.operation
44
+ else:
45
+ # Mark the request as not real
46
+ tracked_request.is_real_request = False
47
+
48
+ # From AuthenticationMiddleware - bypass request.user because it
49
+ # throws AssertionError if 'user' is not in Scope, and we need a
50
+ # try/except already
51
+ try:
52
+ username = scope["user"].display_name
53
+ except (KeyError, AttributeError):
54
+ pass
55
+ else:
56
+ tracked_request.tag("username", username)
57
+
58
+ async def wrapped_send(data):
59
+ type_ = data.get("type", None)
60
+ if type_ == "http.response.start" and 500 <= data.get("status", 200) <= 599:
61
+ tracked_request.tag("error", "true")
62
+ elif type_ == "http.response.body" and not data.get("more_body", False):
63
+ # Finish HTTP span when body finishes sending, not later (e.g.
64
+ # after background tasks)
65
+ grab_extra_data()
66
+ tracked_request.stop_span()
67
+ return await send(data)
68
+
69
+ try:
70
+ await self.app(scope, receive, wrapped_send)
71
+ except Exception as exc:
72
+ tracked_request.tag("error", "true")
73
+ raise exc
74
+ finally:
75
+ if tracked_request.end_time is None:
76
+ grab_extra_data()
77
+ tracked_request.stop_span()
78
+
79
+
80
+ background_instrumentation_installed = False
81
+
82
+
83
+ def install_background_instrumentation():
84
+ global background_instrumentation_installed
85
+ if background_instrumentation_installed:
86
+ return
87
+ background_instrumentation_installed = True
88
+
89
+ @wrapt.decorator
90
+ async def wrapped_background_call(wrapped, instance, args, kwargs):
91
+ tracked_request = TrackedRequest.instance()
92
+ tracked_request.is_real_request = True
93
+
94
+ operation = "Job/{}.{}".format(
95
+ instance.func.__module__, instance.func.__qualname__
96
+ )
97
+ tracked_request.operation = operation
98
+ with tracked_request.span(operation=operation):
99
+ return await wrapped(*args, **kwargs)
100
+
101
+ BackgroundTask.__call__ = wrapped_background_call(BackgroundTask.__call__)
scout_apm/bottle.py ADDED
@@ -0,0 +1,86 @@
1
+ # coding=utf-8
2
+
3
+ import wrapt
4
+ from bottle import request, response
5
+
6
+ import scout_apm.core
7
+ from scout_apm.core.config import scout_config
8
+ from scout_apm.core.queue_time import track_request_queue_time
9
+ from scout_apm.core.tracked_request import TrackedRequest
10
+ from scout_apm.core.web_requests import create_filtered_path, ignore_path
11
+
12
+
13
+ class ScoutPlugin(object):
14
+ def __init__(self):
15
+ self.name = "scout"
16
+ self.api = 2
17
+
18
+ def set_config_from_bottle(self, app):
19
+ bottle_configs = {}
20
+ prefix = "scout."
21
+ prefix_len = len(prefix)
22
+ for key, value in app.config.items():
23
+ if key.startswith(prefix) and len(key) > prefix_len:
24
+ scout_key = key[prefix_len:]
25
+ bottle_configs[scout_key] = value
26
+ scout_config.set(**bottle_configs)
27
+
28
+ def setup(self, app):
29
+ self.set_config_from_bottle(app)
30
+ installed = scout_apm.core.install()
31
+ self._do_nothing = not installed
32
+
33
+ def apply(self, callback, context):
34
+ if self._do_nothing:
35
+ return callback
36
+ return wrap_callback(callback)
37
+
38
+
39
+ @wrapt.decorator
40
+ def wrap_callback(wrapped, instance, args, kwargs):
41
+ tracked_request = TrackedRequest.instance()
42
+ tracked_request.is_real_request = True
43
+
44
+ path = request.path
45
+ # allitems() is an undocumented bottle internal
46
+ tracked_request.tag("path", create_filtered_path(path, request.query.allitems()))
47
+ if ignore_path(path):
48
+ tracked_request.tag("ignore_transaction", True)
49
+
50
+ if request.route.name is not None:
51
+ controller_name = request.route.name
52
+ else:
53
+ controller_name = request.route.rule
54
+ if controller_name == "/":
55
+ controller_name = "/home"
56
+ if not controller_name.startswith("/"):
57
+ controller_name = "/" + controller_name
58
+
59
+ if scout_config.value("collect_remote_ip"):
60
+ # Determine a remote IP to associate with the request. The
61
+ # value is spoofable by the requester so this is not suitable
62
+ # to use in any security sensitive context.
63
+ user_ip = (
64
+ request.headers.get("x-forwarded-for", "").split(",")[0]
65
+ or request.headers.get("client-ip", "").split(",")[0]
66
+ or request.environ.get("REMOTE_ADDR")
67
+ )
68
+ tracked_request.tag("user_ip", user_ip)
69
+
70
+ queue_time = request.headers.get("x-queue-start", "") or request.headers.get(
71
+ "x-request-start", ""
72
+ )
73
+ track_request_queue_time(queue_time, tracked_request)
74
+ operation = "Controller{}".format(controller_name)
75
+
76
+ with tracked_request.span(operation=operation):
77
+ tracked_request.operation = operation
78
+ try:
79
+ value = wrapped(*args, **kwargs)
80
+ except Exception:
81
+ tracked_request.tag("error", "true")
82
+ raise
83
+ else:
84
+ if 500 <= response.status_code <= 599:
85
+ tracked_request.tag("error", "true")
86
+ return value
scout_apm/celery.py ADDED
@@ -0,0 +1,153 @@
1
+ # coding=utf-8
2
+
3
+ import datetime as dt
4
+ import logging
5
+
6
+ from celery.signals import before_task_publish, task_failure, task_postrun, task_prerun
7
+
8
+ from scout_apm.core.queue_time import track_job_queue_time
9
+
10
+ try:
11
+ import django
12
+ from django.views.debug import SafeExceptionReporterFilter
13
+
14
+ def get_safe_settings():
15
+ return SafeExceptionReporterFilter().get_safe_settings()
16
+
17
+ except ImportError:
18
+ # Django not installed
19
+ get_safe_settings = None
20
+
21
+ import scout_apm.core
22
+ from scout_apm.compat import datetime_to_timestamp
23
+ from scout_apm.core.config import scout_config
24
+ from scout_apm.core.error import ErrorMonitor
25
+ from scout_apm.core.tracked_request import TrackedRequest
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ def before_task_publish_callback(headers=None, properties=None, **kwargs):
31
+ if "scout_task_start" not in headers:
32
+ headers["scout_task_start"] = datetime_to_timestamp(
33
+ dt.datetime.now(dt.timezone.utc)
34
+ )
35
+
36
+
37
+ def task_prerun_callback(task=None, **kwargs):
38
+ tracked_request = TrackedRequest.instance()
39
+ tracked_request.is_real_request = True
40
+
41
+ start_time_header = getattr(task.request, "scout_task_start", None)
42
+ track_job_queue_time(start_time_header, tracked_request)
43
+
44
+ task_id = getattr(task.request, "id", None)
45
+ if task_id:
46
+ tracked_request.tag("task_id", task_id)
47
+ parent_task_id = getattr(task.request, "parent_id", None)
48
+ if parent_task_id:
49
+ tracked_request.tag("parent_task_id", parent_task_id)
50
+
51
+ delivery_info = getattr(task.request, "delivery_info", None)
52
+ if delivery_info:
53
+ tracked_request.tag("is_eager", delivery_info.get("is_eager", False))
54
+ tracked_request.tag("exchange", delivery_info.get("exchange", "unknown"))
55
+ tracked_request.tag("priority", delivery_info.get("priority", "unknown"))
56
+ tracked_request.tag("routing_key", delivery_info.get("routing_key", "unknown"))
57
+ tracked_request.tag("queue", delivery_info.get("queue", "unknown"))
58
+
59
+ operation = "Job/" + task.name
60
+ tracked_request.start_span(operation=operation)
61
+ tracked_request.operation = operation
62
+
63
+
64
+ def task_postrun_callback(task=None, **kwargs):
65
+ tracked_request = TrackedRequest.instance()
66
+ tracked_request.stop_span()
67
+
68
+
69
+ def task_failure_callback(
70
+ sender,
71
+ task_id=None,
72
+ exception=None,
73
+ args=None,
74
+ kwargs=None,
75
+ traceback=None,
76
+ einfo=None,
77
+ **remaining,
78
+ ):
79
+ tracked_request = TrackedRequest.instance()
80
+ tracked_request.tag("error", "true")
81
+
82
+ custom_controller = sender.name
83
+ custom_params = {
84
+ "celery": {
85
+ "task_id": task_id,
86
+ "args": args,
87
+ "kwargs": kwargs,
88
+ }
89
+ }
90
+
91
+ # Look up the django settings if populated.
92
+ environment = None
93
+ if get_safe_settings:
94
+ try:
95
+ environment = get_safe_settings()
96
+ except django.core.exceptions.ImproperlyConfigured as exc:
97
+ # Django not setup correctly
98
+ logger.debug(
99
+ "Celery integration does not have django configured properly: %r", exc
100
+ )
101
+ pass
102
+ except Exception as exc:
103
+ logger.debug(
104
+ "Celery task_failure callback exception: %r", exc, exc_info=exc
105
+ )
106
+ pass
107
+
108
+ # Celery occassionally will send the traceback as a string rather
109
+ # than a Stack trace object as the docs indicate. In that case,
110
+ # fall back to the billiard ExceptionInfo instance
111
+ traceback = traceback if traceback and not isinstance(traceback, str) else einfo.tb
112
+ exc_info = (exception.__class__, exception, traceback)
113
+ ErrorMonitor.send(
114
+ exc_info,
115
+ environment=environment,
116
+ custom_params=custom_params,
117
+ custom_controller=custom_controller,
118
+ )
119
+
120
+
121
+ def install(app=None):
122
+ if app is not None:
123
+ copy_configuration(app)
124
+
125
+ installed = scout_apm.core.install()
126
+ if not installed:
127
+ return
128
+
129
+ before_task_publish.connect(before_task_publish_callback)
130
+ task_prerun.connect(task_prerun_callback)
131
+ task_failure.connect(task_failure_callback)
132
+ task_postrun.connect(task_postrun_callback)
133
+
134
+
135
+ def copy_configuration(app):
136
+ prefix = "scout_"
137
+ prefix_len = len(prefix)
138
+
139
+ to_set = {}
140
+ for key, value in app.conf.items():
141
+ key_lower = key.lower()
142
+ if key_lower.startswith(prefix) and len(key_lower) > prefix_len:
143
+ scout_key = key_lower[prefix_len:]
144
+ to_set[scout_key] = value
145
+
146
+ scout_config.set(**to_set)
147
+
148
+
149
+ def uninstall():
150
+ before_task_publish.disconnect(before_task_publish_callback)
151
+ task_prerun.disconnect(task_prerun_callback)
152
+ task_postrun.disconnect(task_postrun_callback)
153
+ task_failure.disconnect(task_failure_callback)
scout_apm/compat.py ADDED
@@ -0,0 +1,104 @@
1
+ # coding=utf-8
2
+
3
+ import datetime as dt
4
+ import gzip
5
+ import inspect
6
+ import queue
7
+ from contextlib import ContextDecorator
8
+ from functools import wraps
9
+ from html import escape
10
+ from urllib.parse import parse_qsl, urlencode, urljoin
11
+
12
+ import certifi
13
+ import urllib3
14
+
15
+
16
+ def iteritems(dictionary):
17
+ return dictionary.items()
18
+
19
+
20
+ # datetime_to_timestamp converts a naive UTC datetime to a unix timestamp
21
+ def datetime_to_timestamp(datetime_obj):
22
+ return datetime_obj.replace(tzinfo=dt.timezone.utc).timestamp()
23
+
24
+
25
+ def text(value, encoding="utf-8", errors="strict"):
26
+ """
27
+ Convert a value to str on Python 3 and unicode on Python 2.
28
+ """
29
+ if isinstance(value, str):
30
+ return value
31
+ elif isinstance(value, bytes):
32
+ return str(value, encoding, errors)
33
+ else:
34
+ return str(value)
35
+
36
+
37
+ def get_pos_args(func):
38
+ return inspect.getfullargspec(func).args
39
+
40
+
41
+ def unwrap_decorators(func):
42
+ unwrapped = func
43
+ while True:
44
+ # N.B. only some decorators set __wrapped__ on Python 2.7
45
+ try:
46
+ unwrapped = unwrapped.__wrapped__
47
+ except AttributeError:
48
+ break
49
+ return unwrapped
50
+
51
+
52
+ def kwargs_only(func):
53
+ """
54
+ Source: https://pypi.org/project/kwargs-only/
55
+ Make a function only accept keyword arguments.
56
+ This can be dropped in Python 3 in lieu of:
57
+ def foo(*, bar=default):
58
+ Source: https://pypi.org/project/kwargs-only/
59
+ """
60
+ if hasattr(inspect, "signature"): # pragma: no cover
61
+ # Python 3
62
+ signature = inspect.signature(func)
63
+ arg_names = list(signature.parameters.keys())
64
+ else: # pragma: no cover
65
+ # Python 2
66
+ signature = inspect.getargspec(func)
67
+ arg_names = signature.args
68
+
69
+ if len(arg_names) > 0 and arg_names[0] in ("self", "cls"):
70
+ allowable_args = 1
71
+ else:
72
+ allowable_args = 0
73
+
74
+ @wraps(func)
75
+ def wrapper(*args, **kwargs):
76
+ if len(args) > allowable_args:
77
+ raise TypeError(
78
+ "{} should only be called with keyword args".format(func.__name__)
79
+ )
80
+ return func(*args, **kwargs)
81
+
82
+ return wrapper
83
+
84
+
85
+ def urllib3_cert_pool_manager(**kwargs):
86
+ return urllib3.PoolManager(cert_reqs="CERT_REQUIRED", ca_certs=certifi.where())
87
+
88
+
89
+ def gzip_compress(data):
90
+ return gzip.compress(data)
91
+
92
+
93
+ __all__ = [
94
+ "ContextDecorator",
95
+ "datetime_to_timestamp",
96
+ "escape",
97
+ "gzip_compress",
98
+ "kwargs_only",
99
+ "parse_qsl",
100
+ "queue",
101
+ "text",
102
+ "urlencode",
103
+ "urljoin",
104
+ ]