From 32506167e5a336ec240ffb9be383b3840c6a008b Mon Sep 17 00:00:00 2001 From: DragonBot00 Date: Mon, 7 Sep 2026 14:56:52 -0500 Subject: [PATCH] ref: Remove residual Python 2 compatibility code Python 2 support was dropped in SDK 2.0.0. Clean up leftover shims: - Remove with_metaclass() from _compat.py, use native metaclass= keyword - Drop try/except for ModuleNotFoundError (available since Python 3.6) - Replace super(_Client, self) with super() - Remove im_class/im_func try/except in qualname_from_function - Remove __name__ fallbacks where __qualname__ is always available - Remove stale py2/py2.7 comments from Django middleware and tests - Simplify IOError/except patterns (IOError is OSError in Python 3) - Remove except AttributeError guards around nanosecond_time() - Remove bist_wheel universal=1 from setup.py No behavior change. Minimum supported Python stays at 3.6. --- sentry_sdk/_compat.py | 14 --------- sentry_sdk/client.py | 2 +- sentry_sdk/hub.py | 3 +- sentry_sdk/integrations/django/middleware.py | 1 - .../integrations/django/signals_handlers.py | 2 -- sentry_sdk/tracing.py | 30 ++++++++----------- sentry_sdk/utils.py | 8 ++--- tests/conftest.py | 5 +--- 8 files changed, 18 insertions(+), 47 deletions(-) diff --git a/sentry_sdk/_compat.py b/sentry_sdk/_compat.py index f62175c09f..0373c6ffb4 100644 --- a/sentry_sdk/_compat.py +++ b/sentry_sdk/_compat.py @@ -1,10 +1,4 @@ import sys -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from typing import Any, TypeVar - - T = TypeVar("T") PY37 = sys.version_info[0] == 3 and sys.version_info[1] >= 7 @@ -13,14 +7,6 @@ PY311 = sys.version_info[0] == 3 and sys.version_info[1] >= 11 -def with_metaclass(meta: "Any", *bases: "Any") -> "Any": - class MetaClass(type): - def __new__(metacls: "Any", name: "Any", this_bases: "Any", d: "Any") -> "Any": - return meta(name, bases, d) - - return type.__new__(MetaClass, "temporary_class", (), {}) - - def check_uwsgi_thread_support() -> bool: # We check two things here: # diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py index 8ec83e267e..a033ee75f2 100644 --- a/sentry_sdk/client.py +++ b/sentry_sdk/client.py @@ -519,7 +519,7 @@ class _Client(BaseClient): """ def __init__(self, *args: "Any", **kwargs: "Any") -> None: - super(_Client, self).__init__(options=get_options(*args, **kwargs)) + super().__init__(options=get_options(*args, **kwargs)) self._init_impl() def __getstate__(self) -> "Any": diff --git a/sentry_sdk/hub.py b/sentry_sdk/hub.py index b17444d06e..92461b64ac 100644 --- a/sentry_sdk/hub.py +++ b/sentry_sdk/hub.py @@ -8,7 +8,6 @@ get_global_scope, get_isolation_scope, ) -from sentry_sdk._compat import with_metaclass from sentry_sdk.client import Client from sentry_sdk.consts import INSTRUMENTER from sentry_sdk.scope import _ScopeManager @@ -109,7 +108,7 @@ def main(cls) -> "Hub": return GLOBAL_HUB -class Hub(with_metaclass(HubMeta)): # type: ignore +class Hub(metaclass=HubMeta): """ .. deprecated:: 2.0.0 The Hub is deprecated. Its functionality will be merged into :py:class:`sentry_sdk.scope.Scope`. diff --git a/sentry_sdk/integrations/django/middleware.py b/sentry_sdk/integrations/django/middleware.py index 01ed8962e0..1a511c9a92 100644 --- a/sentry_sdk/integrations/django/middleware.py +++ b/sentry_sdk/integrations/django/middleware.py @@ -142,7 +142,6 @@ def sync_sentry_wrapped_method(*args: "Any", **kwargs: "Any") -> "Any": sentry_wrapped_method = sync_sentry_wrapped_method try: - # fails for __call__ of function on Python 2 (see py2.7-django-1.11) sentry_wrapped_method = wraps(old_method)(sentry_wrapped_method) # Necessary for Django 3.1 diff --git a/sentry_sdk/integrations/django/signals_handlers.py b/sentry_sdk/integrations/django/signals_handlers.py index 711e74b441..19b3a8b27c 100644 --- a/sentry_sdk/integrations/django/signals_handlers.py +++ b/sentry_sdk/integrations/django/signals_handlers.py @@ -18,8 +18,6 @@ def _get_receiver_name(receiver: "Callable[..., Any]") -> str: if hasattr(receiver, "__qualname__"): name = receiver.__qualname__ - elif hasattr(receiver, "__name__"): # Python 2.7 has no __qualname__ - name = receiver.__name__ elif hasattr( receiver, "func" ): # certain functions (like partials) dont have a name diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index aab2794621..fc88dc16fb 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -327,12 +327,9 @@ def __init__( elif isinstance(start_timestamp, float): start_timestamp = datetime.fromtimestamp(start_timestamp, timezone.utc) self.start_timestamp = start_timestamp - try: - # profiling depends on this value and requires that - # it is measured in nanoseconds - self._start_timestamp_monotonic_ns = nanosecond_time() - except AttributeError: - pass + # profiling depends on this value and requires that + # it is measured in nanoseconds + self._start_timestamp_monotonic_ns = nanosecond_time() #: End timestamp of span self.timestamp: "Optional[datetime]" = None @@ -675,18 +672,15 @@ def finish( # This span is already finished, ignore. return None - try: - if end_timestamp: - if isinstance(end_timestamp, float): - end_timestamp = datetime.fromtimestamp(end_timestamp, timezone.utc) - self.timestamp = end_timestamp - else: - elapsed = nanosecond_time() - self._start_timestamp_monotonic_ns - self.timestamp = self.start_timestamp + timedelta( - microseconds=elapsed / 1000 - ) - except AttributeError: - self.timestamp = datetime.now(timezone.utc) + if end_timestamp: + if isinstance(end_timestamp, float): + end_timestamp = datetime.fromtimestamp(end_timestamp, timezone.utc) + self.timestamp = end_timestamp + else: + elapsed = nanosecond_time() - self._start_timestamp_monotonic_ns + self.timestamp = self.start_timestamp + timedelta( + microseconds=elapsed / 1000 + ) scope = scope or sentry_sdk.get_current_scope() diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index c8dba71106..e580d7c959 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -144,7 +144,7 @@ def get_git_revision() -> "Optional[str]": .strip() .decode("utf-8") ) - except (OSError, IOError, FileNotFoundError): + except OSError: return None return revision @@ -484,7 +484,7 @@ def get_lines_from_file( if loader is not None and hasattr(loader, "get_source"): try: source_str: "Optional[str]" = loader.get_source(module) - except (ImportError, IOError): + except (ImportError, OSError): source_str = None if source_str is not None: source = source_str.splitlines() @@ -492,7 +492,7 @@ def get_lines_from_file( if source is None: try: source = linecache.getlines(filename) - except (OSError, IOError): + except OSError: return [], None, [] if not source: @@ -1527,8 +1527,6 @@ def qualname_from_function(func: "Callable[..., Any]") -> "Optional[str]": if hasattr(func, "__qualname__"): func_qualname = func.__qualname__ - elif hasattr(func, "__name__"): - func_qualname = func.__name__ if func_qualname is not None: if hasattr(func, "__module__") and isinstance(func.__module__, str): diff --git a/tests/conftest.py b/tests/conftest.py index 741aec1938..8419433c80 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -686,8 +686,6 @@ def __eq__(self, test_string): if not isinstance(test_string, self.valid_types): return False - # this is safe even in py2 because as of 2.6, `bytes` exists in py2 - # as an alias for `str` if isinstance(test_string, bytes): test_string = test_string.decode() @@ -707,8 +705,7 @@ def _safe_is_equal(x, y): Compares two values, preferring to use the first's __eq__ method if it exists and is implemented. - Accounts for py2/py3 differences (like ints in py2 not having a __eq__ - method), as well as the incomparability of certain types exposed by using + Accounts for the incomparability of certain types exposed by using raw __eq__ () rather than ==. """