Skip to content

Commit ba54153

Browse files
committed
ref: Unify required imports mechanism for integrations
Follow up to #3548 for better testing and standardisation on required imports/modules for integrations.
1 parent 1c64ff7 commit ba54153

File tree

3 files changed

+43
-17
lines changed

3 files changed

+43
-17
lines changed

sentry_sdk/integrations/__init__.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
from abc import ABC, abstractmethod
2+
from collections.abc import Iterable
3+
from functools import wraps
24
from threading import Lock
35

46
from sentry_sdk.utils import logger
@@ -7,6 +9,7 @@
79

810
if TYPE_CHECKING:
911
from collections.abc import Sequence
12+
from typing import Any
1013
from typing import Callable
1114
from typing import Dict
1215
from typing import Iterator
@@ -203,6 +206,23 @@ class DidNotEnable(Exception): # noqa: N818
203206
"""
204207

205208

209+
def import_to_did_not_enable(f):
210+
@wraps(f)
211+
def wrapper(self, *args, **kwargs):
212+
from importlib import import_module
213+
214+
try:
215+
for import_name in self.required_imports:
216+
globals()[import_name] = import_module(import_name)
217+
# "f" is retrieved from the class in __init_subclass__, before being
218+
# bound, so "self" is forwarded explicitly
219+
return f(self, *args, **kwargs)
220+
except ImportError as err:
221+
raise DidNotEnable(err.msg)
222+
223+
return wrapper
224+
225+
206226
class Integration(ABC):
207227
"""Baseclass for all integrations.
208228
@@ -213,9 +233,21 @@ class Integration(ABC):
213233
install = None
214234
"""Legacy method, do not implement."""
215235

216-
identifier = None # type: str
236+
identifier = "" # type: str
217237
"""String unique ID of integration type"""
218238

239+
required_imports = () # type: Iterable[str]
240+
241+
# The magic below is borrowed from https://stackoverflow.com/a/72666537/90297
242+
def __init_subclass__(cls, *args, **kw):
243+
# type: (Any, ...) -> None
244+
super().__init_subclass__(*args, **kw)
245+
246+
# ^no need to juggle with binding the captured method:
247+
# it will work just as any other method in the class, and
248+
# `self` will be filled in by the Python runtime itself.
249+
setattr(cls, "setup_once", import_to_did_not_enable(getattr(cls, "setup_once")))
250+
219251
@staticmethod
220252
@abstractmethod
221253
def setup_once():

sentry_sdk/integrations/aiohttp.py

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,6 @@
3737
AnnotatedValue,
3838
)
3939

40-
try:
41-
import asyncio
42-
43-
from aiohttp import __version__ as AIOHTTP_VERSION
44-
from aiohttp import ClientSession, TraceConfig
45-
from aiohttp.web import Application, HTTPException, UrlDispatcher
46-
except ImportError:
47-
raise DidNotEnable("AIOHTTP not installed")
48-
4940
from typing import TYPE_CHECKING
5041

5142
if TYPE_CHECKING:
@@ -70,6 +61,7 @@
7061
class AioHttpIntegration(Integration):
7162
identifier = "aiohttp"
7263
origin = f"auto.http.{identifier}"
64+
required_imports = ("asyncio", "aiohttp", "aiohttp.web")
7365

7466
def __init__(
7567
self,
@@ -90,6 +82,9 @@ def __init__(
9082
def setup_once():
9183
# type: () -> None
9284

85+
from aiohttp import __version__ as AIOHTTP_VERSION
86+
from aiohttp.web import Application
87+
9388
version = parse_version(AIOHTTP_VERSION)
9489

9590
if version is None:
@@ -142,7 +137,7 @@ async def sentry_app_handle(self, request, *args, **kwargs):
142137
):
143138
try:
144139
response = await old_handle(self, request)
145-
except HTTPException as e:
140+
except aiohttp.web.HTTPException as e:
146141
transaction.set_http_status(e.status_code)
147142

148143
if (
@@ -175,11 +170,11 @@ async def sentry_app_handle(self, request, *args, **kwargs):
175170

176171
Application._handle = sentry_app_handle
177172

178-
old_urldispatcher_resolve = UrlDispatcher.resolve
173+
old_urldispatcher_resolve = aiohttp.web.UrlDispatcher.resolve
179174

180175
@wraps(old_urldispatcher_resolve)
181176
async def sentry_urldispatcher_resolve(self, request):
182-
# type: (UrlDispatcher, Request) -> UrlMappingMatchInfo
177+
# type: (aiohttp.web.UrlDispatcher, Request) -> UrlMappingMatchInfo
183178
rv = await old_urldispatcher_resolve(self, request)
184179

185180
integration = sentry_sdk.get_client().get_integration(AioHttpIntegration)

sentry_sdk/integrations/redis/__init__.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
class RedisIntegration(Integration):
1616
identifier = "redis"
1717

18+
required_imports = ("redis",)
19+
1820
def __init__(self, max_data_size=_DEFAULT_MAX_DATA_SIZE, cache_prefixes=None):
1921
# type: (int, Optional[list[str]]) -> None
2022
self.max_data_size = max_data_size
@@ -23,10 +25,7 @@ def __init__(self, max_data_size=_DEFAULT_MAX_DATA_SIZE, cache_prefixes=None):
2325
@staticmethod
2426
def setup_once():
2527
# type: () -> None
26-
try:
27-
from redis import StrictRedis, client
28-
except ImportError:
29-
raise DidNotEnable("Redis client not installed")
28+
from redis import StrictRedis, client
3029

3130
_patch_redis(StrictRedis, client)
3231
_patch_redis_cluster()

0 commit comments

Comments
 (0)