Skip to content
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

Centralize minimum version checking #3910

Merged
merged 7 commits into from
Jan 9, 2025
Merged
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
42 changes: 41 additions & 1 deletion sentry_sdk/integrations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ def iter_default_integrations(with_auto_enabling_integrations):
"sentry_sdk.integrations.tornado.TornadoIntegration",
]


iter_default_integrations = _generate_default_integrations_iterator(
integrations=_DEFAULT_INTEGRATIONS,
auto_enabling_integrations=_AUTO_ENABLING_INTEGRATIONS,
Expand All @@ -120,6 +119,30 @@ def iter_default_integrations(with_auto_enabling_integrations):
del _generate_default_integrations_iterator


_MIN_VERSIONS = {
"aiohttp": (3, 4),
"anthropic": (0, 16),
"ariadne": (0, 20),
"arq": (0, 23),
"asyncpg": (0, 23),
"boto3": (1, 12), # this is actually the botocore version
"bottle": (0, 12),
"celery": (4, 4, 7),
"clickhouse_driver": (0, 2, 0),
"django": (1, 8),
"falcon": (1, 4),
"flask": (0, 10),
"gql": (3, 4, 1),
"graphene": (3, 3),
"ray": (2, 7, 0),
"rq": (0, 6),
"sanic": (0, 8),
"sqlalchemy": (1, 2),
"strawberry": (0, 209, 5),
"tornado": (6, 0),
}


def setup_integrations(
integrations,
with_defaults=True,
Expand Down Expand Up @@ -195,6 +218,23 @@ def setup_integrations(
return integrations


def _check_minimum_version(integration, version, package=None):
# type: (type[Integration], Optional[tuple[int, ...]], Optional[str]) -> None
package = package or integration.identifier

if version is None:
raise DidNotEnable(f"Unparsable {package} version.")

min_version = _MIN_VERSIONS.get(integration.identifier)
if min_version is None:
return

if version < min_version:
raise DidNotEnable(
f"Integration only supports {package} {'.'.join(map(str, min_version))} or newer."
)


class DidNotEnable(Exception): # noqa: N818
"""
The integration could not be enabled due to a trivial user error like
Expand Down
8 changes: 2 additions & 6 deletions sentry_sdk/integrations/aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from sentry_sdk.consts import OP, SPANSTATUS, SPANDATA
from sentry_sdk.integrations import (
_DEFAULT_FAILED_REQUEST_STATUS_CODES,
_check_minimum_version,
Integration,
DidNotEnable,
)
Expand Down Expand Up @@ -91,12 +92,7 @@ def setup_once():
# type: () -> None

version = parse_version(AIOHTTP_VERSION)

if version is None:
raise DidNotEnable("Unparsable AIOHTTP version: {}".format(AIOHTTP_VERSION))

if version < (3, 4):
raise DidNotEnable("AIOHTTP 3.4 or newer required.")
_check_minimum_version(AioHttpIntegration, version)

if not HAS_REAL_CONTEXTVARS:
# We better have contextvars or we're going to leak state between
Expand Down
9 changes: 2 additions & 7 deletions sentry_sdk/integrations/anthropic.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import sentry_sdk
from sentry_sdk.ai.monitoring import record_token_usage
from sentry_sdk.consts import OP, SPANDATA
from sentry_sdk.integrations import DidNotEnable, Integration
from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration
from sentry_sdk.scope import should_send_default_pii
from sentry_sdk.utils import (
capture_internal_exceptions,
Expand Down Expand Up @@ -37,12 +37,7 @@ def __init__(self, include_prompts=True):
def setup_once():
# type: () -> None
version = package_version("anthropic")

if version is None:
raise DidNotEnable("Unparsable anthropic version.")

if version < (0, 16):
raise DidNotEnable("anthropic 0.16 or newer required.")
_check_minimum_version(AnthropicIntegration, version)

Messages.create = _wrap_message_create(Messages.create)
AsyncMessages.create = _wrap_message_create_async(AsyncMessages.create)
Expand Down
9 changes: 2 additions & 7 deletions sentry_sdk/integrations/ariadne.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import sentry_sdk
from sentry_sdk import get_client, capture_event
from sentry_sdk.integrations import DidNotEnable, Integration
from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration
from sentry_sdk.integrations.logging import ignore_logger
from sentry_sdk.integrations._wsgi_common import request_body_within_bounds
from sentry_sdk.scope import should_send_default_pii
Expand Down Expand Up @@ -36,12 +36,7 @@ class AriadneIntegration(Integration):
def setup_once():
# type: () -> None
version = package_version("ariadne")

if version is None:
raise DidNotEnable("Unparsable ariadne version.")

if version < (0, 20):
raise DidNotEnable("ariadne 0.20 or newer required.")
_check_minimum_version(AriadneIntegration, version)

ignore_logger("ariadne")

Expand Down
8 changes: 2 additions & 6 deletions sentry_sdk/integrations/arq.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import sentry_sdk
from sentry_sdk.consts import OP, SPANSTATUS
from sentry_sdk.integrations import DidNotEnable, Integration
from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration
from sentry_sdk.integrations.logging import ignore_logger
from sentry_sdk.scope import should_send_default_pii
from sentry_sdk.tracing import Transaction, TRANSACTION_SOURCE_TASK
Expand Down Expand Up @@ -55,11 +55,7 @@ def setup_once():
except (TypeError, ValueError):
version = None

if version is None:
raise DidNotEnable("Unparsable arq version: {}".format(ARQ_VERSION))

if version < (0, 23):
raise DidNotEnable("arq 0.23 or newer required.")
_check_minimum_version(ArqIntegration, version)

patch_enqueue_job()
patch_run_job()
Expand Down
12 changes: 5 additions & 7 deletions sentry_sdk/integrations/asyncpg.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import sentry_sdk
from sentry_sdk.consts import OP, SPANDATA
from sentry_sdk.integrations import Integration, DidNotEnable
from sentry_sdk.integrations import _check_minimum_version, Integration, DidNotEnable
from sentry_sdk.tracing import Span
from sentry_sdk.tracing_utils import add_query_source, record_sql_queries
from sentry_sdk.utils import (
Expand All @@ -20,12 +20,6 @@
except ImportError:
raise DidNotEnable("asyncpg not installed.")

# asyncpg.__version__ is a string containing the semantic version in the form of "<major>.<minor>.<patch>"
asyncpg_version = parse_version(asyncpg.__version__)

if asyncpg_version is not None and asyncpg_version < (0, 23, 0):
raise DidNotEnable("asyncpg >= 0.23.0 required")


class AsyncPGIntegration(Integration):
identifier = "asyncpg"
Expand All @@ -37,6 +31,10 @@ def __init__(self, *, record_params: bool = False):

@staticmethod
def setup_once() -> None:
# asyncpg.__version__ is a string containing the semantic version in the form of "<major>.<minor>.<patch>"
asyncpg_version = parse_version(asyncpg.__version__)
_check_minimum_version(AsyncPGIntegration, asyncpg_version)

asyncpg.Connection.execute = _wrap_execute(
asyncpg.Connection.execute,
)
Expand Down
12 changes: 2 additions & 10 deletions sentry_sdk/integrations/boto3.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import sentry_sdk
from sentry_sdk.consts import OP, SPANDATA
from sentry_sdk.integrations import Integration, DidNotEnable
from sentry_sdk.integrations import _check_minimum_version, Integration, DidNotEnable
from sentry_sdk.tracing import Span
from sentry_sdk.utils import (
capture_internal_exceptions,
Expand Down Expand Up @@ -35,16 +35,8 @@ class Boto3Integration(Integration):
@staticmethod
def setup_once():
# type: () -> None

version = parse_version(BOTOCORE_VERSION)

if version is None:
raise DidNotEnable(
"Unparsable botocore version: {}".format(BOTOCORE_VERSION)
)

if version < (1, 12):
raise DidNotEnable("Botocore 1.12 or newer is required.")
_check_minimum_version(Boto3Integration, version, "botocore")

orig_init = BaseClient.__init__

Expand Down
8 changes: 2 additions & 6 deletions sentry_sdk/integrations/bottle.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
Integration,
DidNotEnable,
_DEFAULT_FAILED_REQUEST_STATUS_CODES,
_check_minimum_version,
)
from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware
from sentry_sdk.integrations._wsgi_common import RequestExtractor
Expand Down Expand Up @@ -72,12 +73,7 @@ def __init__(
def setup_once():
# type: () -> None
version = parse_version(BOTTLE_VERSION)

if version is None:
raise DidNotEnable("Unparsable Bottle version: {}".format(BOTTLE_VERSION))

if version < (0, 12):
raise DidNotEnable("Bottle 0.12 or newer required.")
_check_minimum_version(BottleIntegration, version)

old_app = Bottle.__call__

Expand Down
5 changes: 2 additions & 3 deletions sentry_sdk/integrations/celery/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from sentry_sdk import isolation_scope
from sentry_sdk.api import continue_trace
from sentry_sdk.consts import OP, SPANSTATUS, SPANDATA
from sentry_sdk.integrations import Integration, DidNotEnable
from sentry_sdk.integrations import _check_minimum_version, Integration, DidNotEnable
from sentry_sdk.integrations.celery.beat import (
_patch_beat_apply_entry,
_patch_redbeat_maybe_due,
Expand Down Expand Up @@ -79,8 +79,7 @@ def __init__(
@staticmethod
def setup_once():
# type: () -> None
if CELERY_VERSION < (4, 4, 7):
raise DidNotEnable("Celery 4.4.7 or newer required.")
_check_minimum_version(CeleryIntegration, CELERY_VERSION)

_patch_build_tracer()
_patch_task_apply_async()
Expand Down
7 changes: 3 additions & 4 deletions sentry_sdk/integrations/clickhouse_driver.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import sentry_sdk
from sentry_sdk.consts import OP, SPANDATA
from sentry_sdk.integrations import Integration, DidNotEnable
from sentry_sdk.integrations import _check_minimum_version, Integration, DidNotEnable
from sentry_sdk.tracing import Span
from sentry_sdk.scope import should_send_default_pii
from sentry_sdk.utils import capture_internal_exceptions, ensure_integration_enabled
Expand Down Expand Up @@ -34,16 +34,15 @@ def __getitem__(self, _):
except ImportError:
raise DidNotEnable("clickhouse-driver not installed.")

if clickhouse_driver.VERSION < (0, 2, 0):
raise DidNotEnable("clickhouse-driver >= 0.2.0 required")


class ClickhouseDriverIntegration(Integration):
identifier = "clickhouse_driver"
origin = f"auto.db.{identifier}"

@staticmethod
def setup_once() -> None:
_check_minimum_version(ClickhouseDriverIntegration, clickhouse_driver.VERSION)

# Every query is done using the Connection's `send_query` function
clickhouse_driver.connection.Connection.send_query = _wrap_start(
clickhouse_driver.connection.Connection.send_query
Expand Down
6 changes: 2 additions & 4 deletions sentry_sdk/integrations/django/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
transaction_from_function,
walk_exception_chain,
)
from sentry_sdk.integrations import Integration, DidNotEnable
from sentry_sdk.integrations import _check_minimum_version, Integration, DidNotEnable
from sentry_sdk.integrations.logging import ignore_logger
from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware
from sentry_sdk.integrations._wsgi_common import (
Expand Down Expand Up @@ -154,9 +154,7 @@ def __init__(
@staticmethod
def setup_once():
# type: () -> None

if DJANGO_VERSION < (1, 8):
raise DidNotEnable("Django 1.8 or newer is required.")
_check_minimum_version(DjangoIntegration, DJANGO_VERSION)

install_sql_hook()
# Patch in our custom middleware.
Expand Down
9 changes: 2 additions & 7 deletions sentry_sdk/integrations/falcon.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import sentry_sdk
from sentry_sdk.integrations import Integration, DidNotEnable
from sentry_sdk.integrations import _check_minimum_version, Integration, DidNotEnable
from sentry_sdk.integrations._wsgi_common import RequestExtractor
from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware
from sentry_sdk.tracing import SOURCE_FOR_STYLE
Expand Down Expand Up @@ -135,12 +135,7 @@ def setup_once():
# type: () -> None

version = parse_version(FALCON_VERSION)

if version is None:
raise DidNotEnable("Unparsable Falcon version: {}".format(FALCON_VERSION))

if version < (1, 4):
raise DidNotEnable("Falcon 1.4 or newer required.")
_check_minimum_version(FalconIntegration, version)

_patch_wsgi_app()
_patch_handle_exception()
Expand Down
9 changes: 2 additions & 7 deletions sentry_sdk/integrations/flask.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import sentry_sdk
from sentry_sdk.integrations import DidNotEnable, Integration
from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration
from sentry_sdk.integrations._wsgi_common import (
DEFAULT_HTTP_METHODS_TO_CAPTURE,
RequestExtractor,
Expand Down Expand Up @@ -73,12 +73,7 @@ def __init__(
def setup_once():
# type: () -> None
version = package_version("flask")

if version is None:
raise DidNotEnable("Unparsable Flask version.")

if version < (0, 10):
raise DidNotEnable("Flask 0.10 or newer is required.")
_check_minimum_version(FlaskIntegration, version)

before_render_template.connect(_add_sentry_trace)
request_started.connect(_request_started)
Expand Down
11 changes: 3 additions & 8 deletions sentry_sdk/integrations/gql.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
parse_version,
)

from sentry_sdk.integrations import DidNotEnable, Integration
from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration
from sentry_sdk.scope import should_send_default_pii

try:
Expand All @@ -24,8 +24,6 @@

EventDataType = Dict[str, Union[str, Tuple[VariableDefinitionNode, ...]]]

MIN_GQL_VERSION = (3, 4, 1)


class GQLIntegration(Integration):
identifier = "gql"
Expand All @@ -34,11 +32,8 @@ class GQLIntegration(Integration):
def setup_once():
# type: () -> None
gql_version = parse_version(gql.__version__)
if gql_version is None or gql_version < MIN_GQL_VERSION:
raise DidNotEnable(
"GQLIntegration is only supported for GQL versions %s and above."
% ".".join(str(num) for num in MIN_GQL_VERSION)
)
_check_minimum_version(GQLIntegration, gql_version)

_patch_execute()


Expand Down
9 changes: 2 additions & 7 deletions sentry_sdk/integrations/graphene.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import sentry_sdk
from sentry_sdk.consts import OP
from sentry_sdk.integrations import DidNotEnable, Integration
from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration
from sentry_sdk.scope import should_send_default_pii
from sentry_sdk.utils import (
capture_internal_exceptions,
Expand Down Expand Up @@ -34,12 +34,7 @@ class GrapheneIntegration(Integration):
def setup_once():
# type: () -> None
version = package_version("graphene")

if version is None:
raise DidNotEnable("Unparsable graphene version.")

if version < (3, 3):
raise DidNotEnable("graphene 3.3 or newer required.")
_check_minimum_version(GrapheneIntegration, version)

_patch_graphql()

Expand Down
Loading
Loading