Skip to content

Commit

Permalink
Centralize minimum version checking (#3910)
Browse files Browse the repository at this point in the history
For [populating tox
automatically](#3808),
we need to store min versions of frameworks/libraries in a
programmatically accessible place.

The obvious place for this would be in each integration; however, since
integrations can't be imported unless the respective framework is
installed, this couldn't be used from the script (unless we'd always
install all requirements of all integrations prior to running it, which
takes a non trivial amount of time). So instead I've opted for a central
place within `sentry_sdk/integrations/__init__.py`.

Note: the min versions probably need updating. Not sure when this was
last done, but some of them look quite ancient and we probably don't
support them because we'd already dropped the last Python version they'd
be able to run on.
  • Loading branch information
sentrivana authored Jan 9, 2025
1 parent 4432e26 commit be53273
Show file tree
Hide file tree
Showing 21 changed files with 87 additions and 136 deletions.
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

0 comments on commit be53273

Please sign in to comment.