Skip to content

Commit 57848df

Browse files
committed
chore: change how integrations are initialized
1 parent 9b40380 commit 57848df

File tree

14 files changed

+150
-243
lines changed

14 files changed

+150
-243
lines changed

docs/flask.md

Lines changed: 0 additions & 62 deletions
This file was deleted.

sentry_sdk/client.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from .transport import Transport
66
from .consts import DEFAULT_OPTIONS, SDK_INFO
77
from .stripping import strip_event, flatten_metadata
8+
from .integrations import get_integration
89

910

1011
NO_DSN = object()
@@ -30,12 +31,10 @@ def __init__(self, dsn=None, *args, **kwargs):
3031
self._transport = Transport(dsn)
3132
self._transport.start()
3233

33-
for integration in options.get("integrations", None) or ():
34-
assert integration.identifier, "identifier of integration must be set"
35-
assert isinstance(
36-
integration.identifier, str
37-
), "identifier of integration must be a string"
38-
integration.install(self)
34+
for install_fn in options.get("integrations", None) or ():
35+
if isinstance(install_fn, str):
36+
install_fn = get_integration(install_fn)
37+
install_fn(self)
3938

4039
@property
4140
def dsn(self):

sentry_sdk/hub.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,9 @@ def pop_scope_unsafe(self):
165165
"""Pops a scope layer from the stack. Try to use the context manager
166166
`push_scope()` instead."""
167167
self._pending_processors = []
168-
return self._stack.pop()
168+
rv = self._stack.pop()
169+
assert self._stack
170+
return rv
169171

170172
@contextmanager
171173
def configure_scope(self):

sentry_sdk/integrations/__init__.py

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,40 @@
1-
class Integration(object):
2-
"""
3-
A interface with an install hook for integrations to patch themselves
4-
into their environment. Integrations don't really *need* to use this hook
5-
if the idiomatic control flow of some framework mandates otherwise.
6-
"""
7-
8-
identifier = None
9-
10-
def install(self, client):
11-
pass
12-
13-
@classmethod
14-
def parse_environment(cls, environ):
15-
client_options = {}
16-
integration_options = {}
17-
for key, value in environ.items():
18-
if not key.startswith("SENTRY_"):
19-
continue
20-
key = key[len("SENTRY_") :].replace("-", "_").lower()
21-
identifier_prefix = "%s_" % cls.identifier
22-
if key.startswith(identifier_prefix):
23-
integration_options[key[len(identifier_prefix) :]] = value
24-
else:
25-
client_options[key] = value
26-
27-
return client_options, integration_options
1+
_locked = False
2+
_integrations = {}
3+
4+
5+
def register_integration(identifier, integration=None):
6+
def inner(integration):
7+
if _locked:
8+
raise ValueError("A client has already been initialized. "
9+
"Registration of integrations no longer possible")
10+
if identifier in _integrations:
11+
raise ValueError("Integration with that name already exists.")
12+
_integrations[identifier] = integration
13+
14+
if integration is not None:
15+
return inner(integration)
16+
return inner
17+
18+
19+
def get_integration(identifier):
20+
global _locked
21+
_locked = True
22+
return _integrations[identifier]
23+
24+
25+
@register_integration('django')
26+
def _django_integration(*a, **kw):
27+
from .django import install
28+
return install(*a, **kw)
29+
30+
31+
@register_integration('flask')
32+
def _flask_integration(*a, **kw):
33+
from .flask import install
34+
return install(*a, **kw)
35+
36+
37+
@register_integration('celery')
38+
def _celery_integration(*a, **kw):
39+
from .celery import install
40+
return install(*a, **kw)

sentry_sdk/integrations/celery.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,27 @@
11
from __future__ import absolute_import
22

3+
from threading import Lock
4+
35
from celery.signals import task_failure, task_prerun, task_postrun
46

57
from sentry_sdk import get_current_hub, configure_scope, capture_exception
68

79

8-
def install():
9-
task_prerun.connect(_handle_task_prerun, weak=False)
10-
task_postrun.connect(_handle_task_postrun, weak=False)
11-
task_failure.connect(_process_failure_signal, weak=False)
10+
_installer_lock = Lock()
11+
_installed = False
12+
13+
14+
def install(client):
15+
global _installed
16+
with _installer_lock:
17+
if _installed:
18+
return
19+
20+
task_prerun.connect(_handle_task_prerun, weak=False)
21+
task_postrun.connect(_handle_task_postrun, weak=False)
22+
task_failure.connect(_process_failure_signal, weak=False)
23+
24+
_installed = True
1225

1326

1427
def _process_failure_signal(sender, task_id, einfo, **kw):

sentry_sdk/integrations/django/__init__.py

Lines changed: 28 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
from sentry_sdk import get_current_hub, configure_scope, capture_exception, init
1414
from .._wsgi import RequestExtractor
15-
from .. import Integration
1615

1716

1817
try:
@@ -93,74 +92,54 @@ def _got_request_exception(request=None, **kwargs):
9392
capture_exception()
9493

9594

96-
MIDDLEWARE_NAME = "sentry_sdk.integrations.django.SentryMiddleware"
97-
98-
CONFLICTING_MIDDLEWARE = (
99-
"raven.contrib.django.middleware.SentryMiddleware",
100-
"raven.contrib.django.middleware.SentryLogMiddleware",
101-
) + (MIDDLEWARE_NAME,)
95+
MIDDLEWARE_NAME = __name__ + ".SentryMiddleware"
10296

10397
_installer_lock = Lock()
10498
_installed = False
10599

106100

107-
def _install():
101+
def install(client):
108102
global _installed
109103
with _installer_lock:
110104
if _installed:
111105
return
112106

113-
client_options, integration_options = DjangoIntegration.parse_environment(
114-
dict((key, getattr(settings, key)) for key in dir(settings))
115-
)
116-
117-
client_options.setdefault("integrations", []).append(
118-
DjangoIntegration(**integration_options)
119-
)
120-
121-
init(**client_options)
107+
_install_impl()
122108
_installed = True
123109

124110

125111
def _install_impl():
126-
# default settings.MIDDLEWARE is None
127-
if getattr(settings, "MIDDLEWARE", None):
128-
middleware_attr = "MIDDLEWARE"
129-
else:
130-
middleware_attr = "MIDDLEWARE_CLASSES"
112+
old_getattr = settings.__getattr__
131113

132-
# make sure to get an empty tuple when attr is None
133-
middleware = list(getattr(settings, middleware_attr, ()) or ())
134-
conflicts = set(CONFLICTING_MIDDLEWARE).intersection(set(middleware))
135-
if conflicts:
136-
raise RuntimeError("Other sentry-middleware already registered: %s" % conflicts)
137-
138-
setattr(settings, middleware_attr, [MIDDLEWARE_NAME] + middleware)
139-
140-
signals.request_finished.connect(_request_finished)
141-
signals.got_request_exception.connect(_got_request_exception)
114+
# When `sentry_sdk.init` is called, the settings object may or may not be
115+
# initialized. So we need to lazily merge the array values.
142116

117+
def sentry_patched_getattr(self, key):
118+
if key in ("MIDDLEWARE", "MIDDLEWARE_CLASSES"):
119+
try:
120+
rv = old_getattr(key)
121+
except AttributeError:
122+
rv = []
143123

144-
class DjangoIntegration(Integration):
145-
identifier = "django"
146124

147-
def install(self, client):
148-
_install_impl()
125+
rv = type(rv)([MIDDLEWARE_NAME]) + rv
126+
elif key == "INSTALLED_APPS":
127+
try:
128+
rv = old_getattr(key)
129+
except AttributeError:
130+
rv = []
149131

132+
rv = type(rv)([__name__]) + rv
133+
else:
134+
rv = old_getattr(key)
150135

151-
try:
152-
# Django >= 1.7
153-
from django.apps import AppConfig
154-
except ImportError:
155-
_install()
156-
else:
136+
# fix cache set by old_getattr
137+
if key in self.__dict__:
138+
self.__dict__[key] = rv
139+
return rv
157140

158-
class SentryConfig(AppConfig):
159-
name = "sentry_sdk.integrations.django"
160-
label = "sentry_sdk_integrations_django"
161-
verbose_name = "Sentry"
162141

163-
def ready(self):
164-
_install()
142+
type(settings).__getattr__ = sentry_patched_getattr
165143

166-
default_app_config = "sentry_sdk.integrations.django.SentryConfig"
144+
signals.request_finished.connect(_request_finished)
145+
signals.got_request_exception.connect(_got_request_exception)

0 commit comments

Comments
 (0)