Skip to content

Centralize minimum version checking #3910

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

Merged
merged 7 commits into from
Jan 9, 2025
Merged
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
@@ -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,
@@ -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,
@@ -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
8 changes: 2 additions & 6 deletions sentry_sdk/integrations/aiohttp.py
Original file line number Diff line number Diff line change
@@ -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,
)
@@ -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
9 changes: 2 additions & 7 deletions sentry_sdk/integrations/anthropic.py
Original file line number Diff line number Diff line change
@@ -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,
@@ -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)
9 changes: 2 additions & 7 deletions sentry_sdk/integrations/ariadne.py
Original file line number Diff line number Diff line change
@@ -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
@@ -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")

8 changes: 2 additions & 6 deletions sentry_sdk/integrations/arq.py
Original file line number Diff line number Diff line change
@@ -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
@@ -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()
12 changes: 5 additions & 7 deletions sentry_sdk/integrations/asyncpg.py
Original file line number Diff line number Diff line change
@@ -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 (
@@ -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"
@@ -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,
)
12 changes: 2 additions & 10 deletions sentry_sdk/integrations/boto3.py
Original file line number Diff line number Diff line change
@@ -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,
@@ -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__

8 changes: 2 additions & 6 deletions sentry_sdk/integrations/bottle.py
Original file line number Diff line number Diff line change
@@ -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
@@ -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__

5 changes: 2 additions & 3 deletions sentry_sdk/integrations/celery/__init__.py
Original file line number Diff line number Diff line change
@@ -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,
@@ -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()
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
@@ -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
6 changes: 2 additions & 4 deletions sentry_sdk/integrations/django/__init__.py
Original file line number Diff line number Diff line change
@@ -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 (
@@ -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.
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
@@ -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()
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,
@@ -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)
11 changes: 3 additions & 8 deletions sentry_sdk/integrations/gql.py
Original file line number Diff line number Diff line change
@@ -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:
@@ -24,8 +24,6 @@

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

MIN_GQL_VERSION = (3, 4, 1)


class GQLIntegration(Integration):
identifier = "gql"
@@ -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()


9 changes: 2 additions & 7 deletions sentry_sdk/integrations/graphene.py
Original file line number Diff line number Diff line change
@@ -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,
@@ -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()

9 changes: 2 additions & 7 deletions sentry_sdk/integrations/ray.py
Original file line number Diff line number Diff line change
@@ -3,7 +3,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.tracing import TRANSACTION_SOURCE_TASK
from sentry_sdk.utils import (
event_from_exception,
@@ -136,11 +136,6 @@ class RayIntegration(Integration):
def setup_once():
# type: () -> None
version = package_version("ray")

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

if version < (2, 7, 0):
raise DidNotEnable("Ray 2.7.0 or newer required")
_check_minimum_version(RayIntegration, version)

_patch_ray_remote()
10 changes: 2 additions & 8 deletions sentry_sdk/integrations/rq.py
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
import sentry_sdk
from sentry_sdk.consts import OP
from sentry_sdk.api import continue_trace
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.tracing import TRANSACTION_SOURCE_TASK
from sentry_sdk.utils import (
@@ -41,14 +41,8 @@ class RqIntegration(Integration):
@staticmethod
def setup_once():
# type: () -> None

version = parse_version(RQ_VERSION)

if version is None:
raise DidNotEnable("Unparsable RQ version: {}".format(RQ_VERSION))

if version < (0, 6):
raise DidNotEnable("RQ 0.6 or newer is required.")
_check_minimum_version(RqIntegration, version)

old_perform_job = Worker.perform_job

12 changes: 3 additions & 9 deletions sentry_sdk/integrations/sanic.py
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@
import sentry_sdk
from sentry_sdk import continue_trace
from sentry_sdk.consts import OP
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, _filter_headers
from sentry_sdk.integrations.logging import ignore_logger
from sentry_sdk.tracing import TRANSACTION_SOURCE_COMPONENT, TRANSACTION_SOURCE_URL
@@ -73,14 +73,8 @@ def __init__(self, unsampled_statuses=frozenset({404})):
@staticmethod
def setup_once():
# type: () -> None

SanicIntegration.version = parse_version(SANIC_VERSION)

if SanicIntegration.version is None:
raise DidNotEnable("Unparsable Sanic version: {}".format(SANIC_VERSION))

if SanicIntegration.version < (0, 8):
raise DidNotEnable("Sanic 0.8 or newer required.")
_check_minimum_version(SanicIntegration, SanicIntegration.version)

if not HAS_REAL_CONTEXTVARS:
# We better have contextvars or we're going to leak state between
@@ -102,7 +96,7 @@ def setup_once():
# https://github.com/huge-success/sanic/issues/1332
ignore_logger("root")

if SanicIntegration.version < (21, 9):
if SanicIntegration.version is not None and SanicIntegration.version < (21, 9):
_setup_legacy_sanic()
return

12 changes: 2 additions & 10 deletions sentry_sdk/integrations/sqlalchemy.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from sentry_sdk.consts import SPANSTATUS, SPANDATA
from sentry_sdk.integrations import Integration, DidNotEnable
from sentry_sdk.integrations import _check_minimum_version, Integration, DidNotEnable
from sentry_sdk.tracing_utils import add_query_source, record_sql_queries
from sentry_sdk.utils import (
capture_internal_exceptions,
@@ -31,16 +31,8 @@ class SqlalchemyIntegration(Integration):
@staticmethod
def setup_once():
# type: () -> None

version = parse_version(SQLALCHEMY_VERSION)

if version is None:
raise DidNotEnable(
"Unparsable SQLAlchemy version: {}".format(SQLALCHEMY_VERSION)
)

if version < (1, 2):
raise DidNotEnable("SQLAlchemy 1.2 or newer required.")
_check_minimum_version(SqlalchemyIntegration, version)

listen(Engine, "before_cursor_execute", _before_cursor_execute)
listen(Engine, "after_cursor_execute", _after_cursor_execute)
11 changes: 2 additions & 9 deletions sentry_sdk/integrations/strawberry.py
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@

import sentry_sdk
from sentry_sdk.consts import OP
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.scope import should_send_default_pii
from sentry_sdk.tracing import TRANSACTION_SOURCE_COMPONENT
@@ -75,14 +75,7 @@ def __init__(self, async_execution=None):
def setup_once():
# type: () -> None
version = package_version("strawberry-graphql")

if version is None:
raise DidNotEnable(
"Unparsable strawberry-graphql version: {}".format(version)
)

if version < (0, 209, 5):
raise DidNotEnable("strawberry-graphql 0.209.5 or newer required.")
_check_minimum_version(StrawberryIntegration, version, "strawberry-graphql")

_patch_schema_init()
_patch_execute()
5 changes: 2 additions & 3 deletions sentry_sdk/integrations/tornado.py
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@
capture_internal_exceptions,
transaction_from_function,
)
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,
_filter_headers,
@@ -52,8 +52,7 @@ class TornadoIntegration(Integration):
@staticmethod
def setup_once():
# type: () -> None
if TORNADO_VERSION < (6, 0):
raise DidNotEnable("Tornado 6.0+ required")
_check_minimum_version(TornadoIntegration, TORNADO_VERSION)

if not HAS_REAL_CONTEXTVARS:
# Tornado is async. We better have contextvars or we're going to leak