scout-apm 3.3.0__cp311-cp311-musllinux_1_2_i686.whl
Sign up to get free protection for your applications and to get access to all the features.
- scout_apm/__init__.py +0 -0
- scout_apm/api/__init__.py +197 -0
- scout_apm/async_/__init__.py +1 -0
- scout_apm/async_/api.py +41 -0
- scout_apm/async_/instruments/__init__.py +0 -0
- scout_apm/async_/instruments/jinja2.py +13 -0
- scout_apm/async_/starlette.py +101 -0
- scout_apm/bottle.py +86 -0
- scout_apm/celery.py +153 -0
- scout_apm/compat.py +104 -0
- scout_apm/core/__init__.py +99 -0
- scout_apm/core/_objtrace.cpython-311-i386-linux-musl.so +0 -0
- scout_apm/core/agent/__init__.py +0 -0
- scout_apm/core/agent/commands.py +250 -0
- scout_apm/core/agent/manager.py +319 -0
- scout_apm/core/agent/socket.py +211 -0
- scout_apm/core/backtrace.py +116 -0
- scout_apm/core/cli/__init__.py +0 -0
- scout_apm/core/cli/core_agent_manager.py +32 -0
- scout_apm/core/config.py +404 -0
- scout_apm/core/context.py +140 -0
- scout_apm/core/error.py +95 -0
- scout_apm/core/error_service.py +167 -0
- scout_apm/core/metadata.py +66 -0
- scout_apm/core/n_plus_one_tracker.py +41 -0
- scout_apm/core/objtrace.py +24 -0
- scout_apm/core/platform_detection.py +66 -0
- scout_apm/core/queue_time.py +99 -0
- scout_apm/core/sampler.py +149 -0
- scout_apm/core/samplers/__init__.py +0 -0
- scout_apm/core/samplers/cpu.py +76 -0
- scout_apm/core/samplers/memory.py +23 -0
- scout_apm/core/samplers/thread.py +41 -0
- scout_apm/core/stacktracer.py +30 -0
- scout_apm/core/threading.py +56 -0
- scout_apm/core/tracked_request.py +328 -0
- scout_apm/core/web_requests.py +167 -0
- scout_apm/django/__init__.py +7 -0
- scout_apm/django/apps.py +137 -0
- scout_apm/django/instruments/__init__.py +0 -0
- scout_apm/django/instruments/huey.py +30 -0
- scout_apm/django/instruments/sql.py +140 -0
- scout_apm/django/instruments/template.py +35 -0
- scout_apm/django/middleware.py +211 -0
- scout_apm/django/request.py +144 -0
- scout_apm/dramatiq.py +42 -0
- scout_apm/falcon.py +142 -0
- scout_apm/flask/__init__.py +118 -0
- scout_apm/flask/sqlalchemy.py +28 -0
- scout_apm/huey.py +54 -0
- scout_apm/hug.py +40 -0
- scout_apm/instruments/__init__.py +21 -0
- scout_apm/instruments/elasticsearch.py +263 -0
- scout_apm/instruments/jinja2.py +127 -0
- scout_apm/instruments/pymongo.py +105 -0
- scout_apm/instruments/redis.py +77 -0
- scout_apm/instruments/urllib3.py +80 -0
- scout_apm/rq.py +85 -0
- scout_apm/sqlalchemy.py +38 -0
- scout_apm-3.3.0.dist-info/LICENSE +21 -0
- scout_apm-3.3.0.dist-info/METADATA +94 -0
- scout_apm-3.3.0.dist-info/RECORD +65 -0
- scout_apm-3.3.0.dist-info/WHEEL +5 -0
- scout_apm-3.3.0.dist-info/entry_points.txt +2 -0
- scout_apm-3.3.0.dist-info/top_level.txt +1 -0
scout_apm/dramatiq.py
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# coding=utf-8
|
2
|
+
|
3
|
+
import dramatiq
|
4
|
+
|
5
|
+
import scout_apm.core
|
6
|
+
from scout_apm.core.tracked_request import TrackedRequest
|
7
|
+
|
8
|
+
|
9
|
+
class ScoutMiddleware(dramatiq.Middleware):
|
10
|
+
def __init__(self):
|
11
|
+
installed = scout_apm.core.install()
|
12
|
+
self._do_nothing = not installed
|
13
|
+
|
14
|
+
def before_process_message(self, broker, message):
|
15
|
+
if self._do_nothing:
|
16
|
+
return
|
17
|
+
tracked_request = TrackedRequest.instance()
|
18
|
+
tracked_request.tag("queue", message.queue_name)
|
19
|
+
tracked_request.tag("message_id", message.message_id)
|
20
|
+
operation = "Job/" + message.actor_name
|
21
|
+
tracked_request.start_span(operation=operation)
|
22
|
+
tracked_request.operation = operation
|
23
|
+
|
24
|
+
def after_process_message(self, broker, message, result=None, exception=None):
|
25
|
+
if self._do_nothing:
|
26
|
+
return
|
27
|
+
tracked_request = TrackedRequest.instance()
|
28
|
+
tracked_request.is_real_request = True
|
29
|
+
if exception:
|
30
|
+
tracked_request.tag("error", "true")
|
31
|
+
tracked_request.stop_span()
|
32
|
+
|
33
|
+
def after_skip_message(self, broker, message):
|
34
|
+
"""
|
35
|
+
The message was skipped by another middleware raising SkipMessage.
|
36
|
+
Stop the span and thus the request, it won't have been marked as real
|
37
|
+
so that's alright.
|
38
|
+
"""
|
39
|
+
if self._do_nothing:
|
40
|
+
return
|
41
|
+
tracked_request = TrackedRequest.instance()
|
42
|
+
tracked_request.stop_span()
|
scout_apm/falcon.py
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
# coding=utf-8
|
2
|
+
|
3
|
+
import logging
|
4
|
+
import warnings
|
5
|
+
|
6
|
+
import falcon
|
7
|
+
|
8
|
+
from scout_apm.api import install
|
9
|
+
from scout_apm.core.config import scout_config
|
10
|
+
from scout_apm.core.queue_time import track_request_queue_time
|
11
|
+
from scout_apm.core.tracked_request import TrackedRequest
|
12
|
+
from scout_apm.core.web_requests import create_filtered_path, ignore_path
|
13
|
+
|
14
|
+
logger = logging.getLogger(__name__)
|
15
|
+
|
16
|
+
# Falcon Middleware docs:
|
17
|
+
# https://falcon.readthedocs.io/en/stable/api/middleware.html
|
18
|
+
|
19
|
+
|
20
|
+
class ScoutMiddleware(object):
|
21
|
+
"""
|
22
|
+
Falcon Middleware for integration with Scout APM.
|
23
|
+
"""
|
24
|
+
|
25
|
+
def __init__(self, config, hug_http_interface=None):
|
26
|
+
self.api = None
|
27
|
+
self.hug_http_interface = hug_http_interface
|
28
|
+
installed = install(config=config)
|
29
|
+
self._do_nothing = not installed
|
30
|
+
|
31
|
+
def set_api(self, api):
|
32
|
+
if not isinstance(api, falcon.API):
|
33
|
+
raise ValueError("api should be an instance of falcon.API")
|
34
|
+
self.api = api
|
35
|
+
|
36
|
+
def process_request(self, req, resp):
|
37
|
+
if self._do_nothing:
|
38
|
+
return
|
39
|
+
if self.api is None and self.hug_http_interface is not None:
|
40
|
+
self.api = self.hug_http_interface.falcon
|
41
|
+
tracked_request = TrackedRequest.instance()
|
42
|
+
tracked_request.is_real_request = True
|
43
|
+
req.context.scout_tracked_request = tracked_request
|
44
|
+
tracked_request.start_span(
|
45
|
+
operation="Middleware", should_capture_backtrace=False
|
46
|
+
)
|
47
|
+
|
48
|
+
path = req.path
|
49
|
+
# Falcon URL parameter values are *either* single items or lists
|
50
|
+
url_params = [
|
51
|
+
(k, v)
|
52
|
+
for k, vs in req.params.items()
|
53
|
+
for v in (vs if isinstance(vs, list) else [vs])
|
54
|
+
]
|
55
|
+
tracked_request.tag("path", create_filtered_path(path, url_params))
|
56
|
+
if ignore_path(path):
|
57
|
+
tracked_request.tag("ignore_transaction", True)
|
58
|
+
|
59
|
+
if scout_config.value("collect_remote_ip"):
|
60
|
+
# Determine a remote IP to associate with the request. The value is
|
61
|
+
# spoofable by the requester so this is not suitable to use in any
|
62
|
+
# security sensitive context.
|
63
|
+
user_ip = (
|
64
|
+
req.get_header("x-forwarded-for", default="").split(",")[0]
|
65
|
+
or req.get_header("client-ip", default="").split(",")[0]
|
66
|
+
or req.remote_addr
|
67
|
+
)
|
68
|
+
tracked_request.tag("user_ip", user_ip)
|
69
|
+
|
70
|
+
queue_time = req.get_header("x-queue-start", default="") or req.get_header(
|
71
|
+
"x-request-start", default=""
|
72
|
+
)
|
73
|
+
track_request_queue_time(queue_time, tracked_request)
|
74
|
+
|
75
|
+
def process_resource(self, req, resp, resource, params):
|
76
|
+
if self._do_nothing:
|
77
|
+
return
|
78
|
+
|
79
|
+
tracked_request = getattr(req.context, "scout_tracked_request", None)
|
80
|
+
if tracked_request is None:
|
81
|
+
# Somehow we didn't start a request - this might occur in
|
82
|
+
# combination with a pretty adversarial application, so guard
|
83
|
+
# against it, although if a request was started and the context was
|
84
|
+
# lost, other problems might occur.
|
85
|
+
return
|
86
|
+
|
87
|
+
if self.api is None:
|
88
|
+
warnings.warn(
|
89
|
+
(
|
90
|
+
"{}.set_api() should be called before requests begin for"
|
91
|
+
+ " more detail."
|
92
|
+
).format(self.__class__.__name__),
|
93
|
+
RuntimeWarning,
|
94
|
+
stacklevel=2,
|
95
|
+
)
|
96
|
+
operation = "Controller/{}.{}.{}".format(
|
97
|
+
resource.__module__, resource.__class__.__name__, req.method
|
98
|
+
)
|
99
|
+
else:
|
100
|
+
# Find the current responder's name. Falcon passes middleware the
|
101
|
+
# current resource but unfortunately not the method being called, hence
|
102
|
+
# we have to go through routing again.
|
103
|
+
responder, _params, _resource, _uri_template = self.api._get_responder(req)
|
104
|
+
operation = self._name_operation(req, responder, resource)
|
105
|
+
|
106
|
+
span = tracked_request.start_span(
|
107
|
+
operation=operation, should_capture_backtrace=False
|
108
|
+
)
|
109
|
+
tracked_request.operation = operation
|
110
|
+
req.context.scout_resource_span = span
|
111
|
+
|
112
|
+
def _name_operation(self, req, responder, resource):
|
113
|
+
try:
|
114
|
+
last_part = responder.__name__
|
115
|
+
except AttributeError:
|
116
|
+
last_part = req.method
|
117
|
+
return "Controller/{}.{}.{}".format(
|
118
|
+
resource.__module__, resource.__class__.__name__, last_part
|
119
|
+
)
|
120
|
+
|
121
|
+
def process_response(self, req, resp, resource, req_succeeded):
|
122
|
+
tracked_request = getattr(req.context, "scout_tracked_request", None)
|
123
|
+
if tracked_request is None:
|
124
|
+
# Somehow we didn't start a request
|
125
|
+
return
|
126
|
+
|
127
|
+
# Falcon only stores the response status line, we have to parse it
|
128
|
+
try:
|
129
|
+
status_code = int(resp.status.split(" ")[0])
|
130
|
+
except ValueError:
|
131
|
+
# Bad status line - force it to be tagged as an error because
|
132
|
+
# client will experience it as one
|
133
|
+
status_code = 500
|
134
|
+
|
135
|
+
if not req_succeeded or 500 <= status_code <= 599:
|
136
|
+
tracked_request.tag("error", "true")
|
137
|
+
|
138
|
+
span = getattr(req.context, "scout_resource_span", None)
|
139
|
+
if span is not None:
|
140
|
+
tracked_request.stop_span()
|
141
|
+
# Stop Middleware span
|
142
|
+
tracked_request.stop_span()
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# coding=utf-8
|
2
|
+
|
3
|
+
import logging
|
4
|
+
import sys
|
5
|
+
|
6
|
+
import wrapt
|
7
|
+
from flask import current_app
|
8
|
+
from flask.globals import request, session
|
9
|
+
|
10
|
+
import scout_apm.core
|
11
|
+
from scout_apm.core.config import scout_config
|
12
|
+
from scout_apm.core.error import ErrorMonitor
|
13
|
+
from scout_apm.core.tracked_request import TrackedRequest
|
14
|
+
from scout_apm.core.web_requests import RequestComponents, werkzeug_track_request_data
|
15
|
+
|
16
|
+
logger = logging.getLogger(__name__)
|
17
|
+
|
18
|
+
|
19
|
+
class ScoutApm(object):
|
20
|
+
def __init__(self, app):
|
21
|
+
self.app = app
|
22
|
+
self._attempted_install = False
|
23
|
+
app.full_dispatch_request = self.wrapped_full_dispatch_request(
|
24
|
+
app.full_dispatch_request
|
25
|
+
)
|
26
|
+
app.preprocess_request = self.wrapped_preprocess_request(app.preprocess_request)
|
27
|
+
|
28
|
+
@wrapt.decorator
|
29
|
+
def wrapped_full_dispatch_request(self, wrapped, instance, args, kwargs):
|
30
|
+
if not self._attempted_install:
|
31
|
+
self.extract_flask_settings()
|
32
|
+
installed = scout_apm.core.install()
|
33
|
+
self._do_nothing = not installed
|
34
|
+
self._attempted_install = True
|
35
|
+
|
36
|
+
if self._do_nothing:
|
37
|
+
return wrapped(*args, **kwargs)
|
38
|
+
|
39
|
+
# Pass on routing exceptions (normally 404's)
|
40
|
+
if request.routing_exception is not None:
|
41
|
+
return wrapped(*args, **kwargs)
|
42
|
+
|
43
|
+
request_components = get_request_components(self.app, request)
|
44
|
+
operation = "Controller/{}.{}".format(
|
45
|
+
request_components.module, request_components.controller
|
46
|
+
)
|
47
|
+
|
48
|
+
tracked_request = TrackedRequest.instance()
|
49
|
+
tracked_request.is_real_request = True
|
50
|
+
tracked_request.operation = operation
|
51
|
+
request._scout_tracked_request = tracked_request
|
52
|
+
|
53
|
+
werkzeug_track_request_data(request, tracked_request)
|
54
|
+
|
55
|
+
with tracked_request.span(
|
56
|
+
operation=operation, should_capture_backtrace=False
|
57
|
+
) as span:
|
58
|
+
request._scout_view_span = span
|
59
|
+
|
60
|
+
try:
|
61
|
+
response = wrapped(*args, **kwargs)
|
62
|
+
except Exception as exc:
|
63
|
+
tracked_request.tag("error", "true")
|
64
|
+
if scout_config.value("errors_enabled"):
|
65
|
+
ErrorMonitor.send(
|
66
|
+
sys.exc_info(),
|
67
|
+
request_components=get_request_components(self.app, request),
|
68
|
+
request_path=request.path,
|
69
|
+
request_params=dict(request.args.lists()),
|
70
|
+
session=dict(session.items()),
|
71
|
+
environment=self.app.config,
|
72
|
+
)
|
73
|
+
raise exc
|
74
|
+
else:
|
75
|
+
if 500 <= response.status_code <= 599:
|
76
|
+
tracked_request.tag("error", "true")
|
77
|
+
return response
|
78
|
+
|
79
|
+
@wrapt.decorator
|
80
|
+
def wrapped_preprocess_request(self, wrapped, instance, args, kwargs):
|
81
|
+
tracked_request = getattr(request, "_scout_tracked_request", None)
|
82
|
+
if tracked_request is None:
|
83
|
+
return wrapped(*args, **kwargs)
|
84
|
+
|
85
|
+
# Unlike middleware in other frameworks, using request preprocessors is
|
86
|
+
# less common in Flask, so only add a span if there is any in use
|
87
|
+
have_before_request_funcs = (
|
88
|
+
None in instance.before_request_funcs
|
89
|
+
or request.blueprint in instance.before_request_funcs
|
90
|
+
)
|
91
|
+
if not have_before_request_funcs:
|
92
|
+
return wrapped(*args, **kwargs)
|
93
|
+
|
94
|
+
with tracked_request.span("PreprocessRequest", should_capture_backtrace=False):
|
95
|
+
return wrapped(*args, **kwargs)
|
96
|
+
|
97
|
+
def extract_flask_settings(self):
|
98
|
+
"""
|
99
|
+
Copies SCOUT_* settings in the app into Scout's config lookup
|
100
|
+
"""
|
101
|
+
configs = {}
|
102
|
+
configs["application_root"] = self.app.instance_path
|
103
|
+
for name in current_app.config:
|
104
|
+
if name.startswith("SCOUT_"):
|
105
|
+
value = current_app.config[name]
|
106
|
+
clean_name = name.replace("SCOUT_", "").lower()
|
107
|
+
configs[clean_name] = value
|
108
|
+
scout_config.set(**configs)
|
109
|
+
|
110
|
+
|
111
|
+
def get_request_components(app, request):
|
112
|
+
view_func = app.view_functions[request.endpoint]
|
113
|
+
request_components = RequestComponents(
|
114
|
+
module=view_func.__module__,
|
115
|
+
controller=view_func.__name__,
|
116
|
+
action=request.method,
|
117
|
+
)
|
118
|
+
return request_components
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding=utf-8
|
2
|
+
|
3
|
+
import wrapt
|
4
|
+
from flask_sqlalchemy import SQLAlchemy
|
5
|
+
|
6
|
+
import scout_apm.sqlalchemy
|
7
|
+
|
8
|
+
|
9
|
+
def instrument_sqlalchemy(db):
|
10
|
+
# Version 3 of flask_sqlalchemy changed how engines are created
|
11
|
+
if hasattr(db, "_make_engine"):
|
12
|
+
db._make_engine = wrapped_make_engine(db._make_engine)
|
13
|
+
else:
|
14
|
+
SQLAlchemy.get_engine = wrapped_get_engine(SQLAlchemy.get_engine)
|
15
|
+
|
16
|
+
|
17
|
+
@wrapt.decorator
|
18
|
+
def wrapped_get_engine(wrapped, instance, args, kwargs):
|
19
|
+
engine = wrapped(*args, **kwargs)
|
20
|
+
scout_apm.sqlalchemy.instrument_sqlalchemy(engine)
|
21
|
+
return engine
|
22
|
+
|
23
|
+
|
24
|
+
@wrapt.decorator
|
25
|
+
def wrapped_make_engine(wrapped, instance, args, kwargs):
|
26
|
+
engine = wrapped(*args, **kwargs)
|
27
|
+
scout_apm.sqlalchemy.instrument_sqlalchemy(engine)
|
28
|
+
return engine
|
scout_apm/huey.py
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# coding=utf-8
|
2
|
+
|
3
|
+
from huey.exceptions import RetryTask, TaskLockedException
|
4
|
+
from huey.signals import SIGNAL_CANCELED
|
5
|
+
|
6
|
+
import scout_apm.core
|
7
|
+
from scout_apm.core.tracked_request import TrackedRequest
|
8
|
+
|
9
|
+
# Because neither hooks nor signals are called in *all* cases, we need to use
|
10
|
+
# both in order to capture every case. See source:
|
11
|
+
# https://github.com/coleifer/huey/blob/e6710bd6a9f581ebc728e24f5923d26eb0047750/huey/api.py#L331 # noqa
|
12
|
+
|
13
|
+
|
14
|
+
def attach_scout(huey):
|
15
|
+
installed = scout_apm.core.install()
|
16
|
+
if installed:
|
17
|
+
attach_scout_handlers(huey)
|
18
|
+
|
19
|
+
|
20
|
+
def attach_scout_handlers(huey):
|
21
|
+
huey.pre_execute()(scout_on_pre_execute)
|
22
|
+
huey.post_execute()(scout_on_post_execute)
|
23
|
+
huey.signal(SIGNAL_CANCELED)(scout_on_cancelled)
|
24
|
+
|
25
|
+
|
26
|
+
def scout_on_pre_execute(task):
|
27
|
+
tracked_request = TrackedRequest.instance()
|
28
|
+
|
29
|
+
tracked_request.tag("task_id", task.id)
|
30
|
+
|
31
|
+
operation = "Job/{}.{}".format(task.__module__, task.__class__.__name__)
|
32
|
+
tracked_request.start_span(operation=operation)
|
33
|
+
tracked_request.operation = operation
|
34
|
+
|
35
|
+
|
36
|
+
def scout_on_post_execute(task, task_value, exception):
|
37
|
+
tracked_request = TrackedRequest.instance()
|
38
|
+
if exception is None:
|
39
|
+
tracked_request.is_real_request = True
|
40
|
+
elif isinstance(exception, TaskLockedException):
|
41
|
+
pass
|
42
|
+
elif isinstance(exception, RetryTask):
|
43
|
+
tracked_request.is_real_request = True
|
44
|
+
tracked_request.tag("retrying", True)
|
45
|
+
else:
|
46
|
+
tracked_request.is_real_request = True
|
47
|
+
tracked_request.tag("error", "true")
|
48
|
+
tracked_request.stop_span()
|
49
|
+
|
50
|
+
|
51
|
+
def scout_on_cancelled(signal, task, exc=None):
|
52
|
+
# In the case of a cancelled signal, Huey doesn't run the post_execute
|
53
|
+
# handler, so we need to tidy up
|
54
|
+
TrackedRequest.instance().stop_span()
|
scout_apm/hug.py
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# coding=utf-8
|
2
|
+
|
3
|
+
import hug
|
4
|
+
from hug.interface import HTTP
|
5
|
+
|
6
|
+
from scout_apm.falcon import ScoutMiddleware as FalconMiddleware
|
7
|
+
|
8
|
+
|
9
|
+
class ScoutMiddleware(FalconMiddleware):
|
10
|
+
"""
|
11
|
+
Hug's HTTP interface is based on Falcon. Therefore we use a subclass of our
|
12
|
+
Falcon integration with Hug specific extras.
|
13
|
+
"""
|
14
|
+
|
15
|
+
def __init__(self, config, hug_http_interface):
|
16
|
+
super(ScoutMiddleware, self).__init__(config)
|
17
|
+
self.hug_http_interface = hug_http_interface
|
18
|
+
|
19
|
+
def process_request(self, req, resp):
|
20
|
+
if not self._do_nothing and self.api is None:
|
21
|
+
self.api = self.hug_http_interface.falcon
|
22
|
+
return super(ScoutMiddleware, self).process_request(req, resp)
|
23
|
+
|
24
|
+
def _name_operation(self, req, responder, resource):
|
25
|
+
if isinstance(responder, HTTP):
|
26
|
+
# Hug doesn't use functions but its custom callable classes
|
27
|
+
return "Controller/{}.{}".format(
|
28
|
+
responder.interface._function.__module__,
|
29
|
+
responder.interface._function.__name__,
|
30
|
+
)
|
31
|
+
return super(ScoutMiddleware, self)._name_operation(req, responder, resource)
|
32
|
+
|
33
|
+
|
34
|
+
def integrate_scout(hug_module_name, config):
|
35
|
+
http_interface = hug.API(hug_module_name).http
|
36
|
+
scout_middleware = ScoutMiddleware(
|
37
|
+
config=config,
|
38
|
+
hug_http_interface=http_interface,
|
39
|
+
)
|
40
|
+
http_interface.add_middleware(scout_middleware)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# coding=utf-8
|
2
|
+
|
3
|
+
import importlib
|
4
|
+
import logging
|
5
|
+
|
6
|
+
from scout_apm.core.config import scout_config
|
7
|
+
|
8
|
+
logger = logging.getLogger(__name__)
|
9
|
+
|
10
|
+
instrument_names = ["elasticsearch", "jinja2", "pymongo", "redis", "urllib3"]
|
11
|
+
|
12
|
+
|
13
|
+
def ensure_all_installed():
|
14
|
+
disabled_instruments = scout_config.value("disabled_instruments")
|
15
|
+
for instrument_name in instrument_names:
|
16
|
+
if instrument_name in disabled_instruments:
|
17
|
+
logger.info("%s instrument is disabled. Skipping.", instrument_name)
|
18
|
+
continue
|
19
|
+
|
20
|
+
module = importlib.import_module("{}.{}".format(__name__, instrument_name))
|
21
|
+
module.ensure_installed()
|