Skip to content

ref: Unify required imports mechanism for integrations #3597

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion sentry_sdk/integrations/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from abc import ABC, abstractmethod
from collections.abc import Iterable
from functools import wraps
from threading import Lock

from sentry_sdk.utils import logger
Expand All @@ -7,6 +9,7 @@

if TYPE_CHECKING:
from collections.abc import Sequence
from typing import Any
from typing import Callable
from typing import Dict
from typing import Iterator
Expand Down Expand Up @@ -203,6 +206,23 @@ class DidNotEnable(Exception): # noqa: N818
"""


def import_to_did_not_enable(f):
@wraps(f)
def wrapper(self, *args, **kwargs):
from importlib import import_module

try:
for import_name in self.required_imports:
globals()[import_name] = import_module(import_name)
# "f" is retrieved from the class in __init_subclass__, before being
# bound, so "self" is forwarded explicitly
return f(self, *args, **kwargs)
except ImportError as err:
raise DidNotEnable(err.msg)

return wrapper


class Integration(ABC):
"""Baseclass for all integrations.

Expand All @@ -213,9 +233,21 @@ class Integration(ABC):
install = None
"""Legacy method, do not implement."""

identifier = None # type: str
identifier = "" # type: str
"""String unique ID of integration type"""

required_imports = () # type: Iterable[str]

# The magic below is borrowed from https://stackoverflow.com/a/72666537/90297
def __init_subclass__(cls, *args, **kw):
# type: (Any, ...) -> None
super().__init_subclass__(*args, **kw)

# ^no need to juggle with binding the captured method:
# it will work just as any other method in the class, and
# `self` will be filled in by the Python runtime itself.
setattr(cls, "setup_once", import_to_did_not_enable(getattr(cls, "setup_once")))

@staticmethod
@abstractmethod
def setup_once():
Expand Down
19 changes: 7 additions & 12 deletions sentry_sdk/integrations/aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,6 @@
AnnotatedValue,
)

try:
import asyncio

from aiohttp import __version__ as AIOHTTP_VERSION
from aiohttp import ClientSession, TraceConfig
from aiohttp.web import Application, HTTPException, UrlDispatcher
except ImportError:
raise DidNotEnable("AIOHTTP not installed")

from typing import TYPE_CHECKING

if TYPE_CHECKING:
Expand All @@ -70,6 +61,7 @@
class AioHttpIntegration(Integration):
identifier = "aiohttp"
origin = f"auto.http.{identifier}"
required_imports = ("asyncio", "aiohttp", "aiohttp.web")

def __init__(
self,
Expand All @@ -90,6 +82,9 @@ def __init__(
def setup_once():
# type: () -> None

from aiohttp import __version__ as AIOHTTP_VERSION
from aiohttp.web import Application

version = parse_version(AIOHTTP_VERSION)

if version is None:
Expand Down Expand Up @@ -142,7 +137,7 @@ async def sentry_app_handle(self, request, *args, **kwargs):
):
try:
response = await old_handle(self, request)
except HTTPException as e:
except aiohttp.web.HTTPException as e:
transaction.set_http_status(e.status_code)

if (
Expand Down Expand Up @@ -175,11 +170,11 @@ async def sentry_app_handle(self, request, *args, **kwargs):

Application._handle = sentry_app_handle

old_urldispatcher_resolve = UrlDispatcher.resolve
old_urldispatcher_resolve = aiohttp.web.UrlDispatcher.resolve

@wraps(old_urldispatcher_resolve)
async def sentry_urldispatcher_resolve(self, request):
# type: (UrlDispatcher, Request) -> UrlMappingMatchInfo
# type: (aiohttp.web.UrlDispatcher, Request) -> UrlMappingMatchInfo
rv = await old_urldispatcher_resolve(self, request)

integration = sentry_sdk.get_client().get_integration(AioHttpIntegration)
Expand Down
7 changes: 3 additions & 4 deletions sentry_sdk/integrations/redis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
class RedisIntegration(Integration):
identifier = "redis"

required_imports = ("redis",)

def __init__(self, max_data_size=_DEFAULT_MAX_DATA_SIZE, cache_prefixes=None):
# type: (int, Optional[list[str]]) -> None
self.max_data_size = max_data_size
Expand All @@ -23,10 +25,7 @@ def __init__(self, max_data_size=_DEFAULT_MAX_DATA_SIZE, cache_prefixes=None):
@staticmethod
def setup_once():
# type: () -> None
try:
from redis import StrictRedis, client
except ImportError:
raise DidNotEnable("Redis client not installed")
from redis import StrictRedis, client

_patch_redis(StrictRedis, client)
_patch_redis_cluster()
Expand Down
Loading