Skip to content

Remove compatibility utils for old Python #2645

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 11 commits into from
Jan 16, 2024
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
2 changes: 2 additions & 0 deletions MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
## Changed

- The `BackgroundWorker` thread used to process events was renamed from `raven-sentry.BackgroundWorker` to `sentry-sdk.BackgroundWorker`.
- The `reraise` function was moved from `sentry_sdk._compat` to `sentry_sdk.utils`.

## Removed

- Removed support for Python 2 and Python 3.5. The SDK now requires at least Python 3.6.
- Removed support for Celery 3.\*.
- Removed support for Django 1.8, 1.9, 1.10.
- Removed support for Flask 0.\*.
- A number of compatibility utilities were removed from `sentry_sdk._compat`: the constants `PY2` and `PY33`; the functions `datetime_utcnow`, `utc_from_timestamp`, `implements_str`, `contextmanager`; and the aliases `text_type`, `string_types`, `number_types`, `int_types`, `iteritems`, `binary_sequence_types`.

## Deprecated
104 changes: 0 additions & 104 deletions sentry_sdk/_compat.py
Original file line number Diff line number Diff line change
@@ -1,122 +1,18 @@
import sys
import contextlib
from datetime import datetime
from functools import wraps

from sentry_sdk._types import TYPE_CHECKING

if TYPE_CHECKING:
from typing import Optional
from typing import Tuple
from typing import Any
from typing import Type
from typing import TypeVar
from typing import Callable

T = TypeVar("T")


PY2 = sys.version_info[0] == 2
PY33 = sys.version_info[0] == 3 and sys.version_info[1] >= 3
PY37 = sys.version_info[0] == 3 and sys.version_info[1] >= 7
PY310 = sys.version_info[0] == 3 and sys.version_info[1] >= 10
PY311 = sys.version_info[0] == 3 and sys.version_info[1] >= 11

if PY2:
import urlparse

text_type = unicode # noqa

string_types = (str, text_type)
number_types = (int, long, float) # noqa
int_types = (int, long) # noqa
iteritems = lambda x: x.iteritems() # noqa: B301
binary_sequence_types = (bytearray, memoryview)

def datetime_utcnow():
return datetime.utcnow()

def utc_from_timestamp(timestamp):
return datetime.utcfromtimestamp(timestamp)

def implements_str(cls):
# type: (T) -> T
cls.__unicode__ = cls.__str__
cls.__str__ = lambda x: unicode(x).encode("utf-8") # noqa
return cls

# The line below is written as an "exec" because it triggers a syntax error in Python 3
exec("def reraise(tp, value, tb=None):\n raise tp, value, tb")

def contextmanager(func):
# type: (Callable) -> Callable
"""
Decorator which creates a contextmanager that can also be used as a
decorator, similar to how the built-in contextlib.contextmanager
function works in Python 3.2+.
"""
contextmanager_func = contextlib.contextmanager(func)

@wraps(func)
class DecoratorContextManager:
def __init__(self, *args, **kwargs):
# type: (...) -> None
self.the_contextmanager = contextmanager_func(*args, **kwargs)

def __enter__(self):
# type: () -> None
self.the_contextmanager.__enter__()

def __exit__(self, *args, **kwargs):
# type: (...) -> None
self.the_contextmanager.__exit__(*args, **kwargs)

def __call__(self, decorated_func):
# type: (Callable) -> Callable[...]
@wraps(decorated_func)
def when_called(*args, **kwargs):
# type: (...) -> Any
with self.the_contextmanager:
return_val = decorated_func(*args, **kwargs)
return return_val

return when_called

return DecoratorContextManager

else:
from datetime import timezone
import urllib.parse as urlparse # noqa

text_type = str
string_types = (text_type,) # type: Tuple[type]
number_types = (int, float) # type: Tuple[type, type]
int_types = (int,)
iteritems = lambda x: x.items()
binary_sequence_types = (bytes, bytearray, memoryview)

def datetime_utcnow():
# type: () -> datetime
return datetime.now(timezone.utc)

def utc_from_timestamp(timestamp):
# type: (float) -> datetime
return datetime.fromtimestamp(timestamp, timezone.utc)

def implements_str(x):
# type: (T) -> T
return x

def reraise(tp, value, tb=None):
# type: (Optional[Type[BaseException]], Optional[BaseException], Optional[Any]) -> None
assert value is not None
if value.__traceback__ is not tb:
raise value.with_traceback(tb)
raise value

# contextlib.contextmanager already can be used as decorator in Python 3.2+
contextmanager = contextlib.contextmanager


def with_metaclass(meta, *bases):
# type: (Any, *Any) -> Any
Expand Down
4 changes: 1 addition & 3 deletions sentry_sdk/_werkzeug.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@
SUCH DAMAGE.
"""

from sentry_sdk._compat import iteritems

from sentry_sdk._types import TYPE_CHECKING

if TYPE_CHECKING:
Expand All @@ -54,7 +52,7 @@ def _get_headers(environ):
"""
Returns only proper HTTP headers.
"""
for key, value in iteritems(environ):
for key, value in environ.items():
key = str(key)
if key.startswith("HTTP_") and key not in (
"HTTP_CONTENT_TYPE",
Expand Down
18 changes: 9 additions & 9 deletions sentry_sdk/client.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from importlib import import_module
import os
import uuid
import random
import socket
from datetime import datetime, timezone
from importlib import import_module

from sentry_sdk._compat import datetime_utcnow, string_types, text_type, iteritems
from sentry_sdk.utils import (
capture_internal_exceptions,
current_stacktrace,
Expand Down Expand Up @@ -61,7 +61,7 @@

def _get_options(*args, **kwargs):
# type: (*Optional[str], **Any) -> Dict[str, Any]
if args and (isinstance(args[0], (text_type, bytes, str)) or args[0] is None):
if args and (isinstance(args[0], (bytes, str)) or args[0] is None):
dsn = args[0] # type: Optional[str]
args = args[1:]
else:
Expand All @@ -75,7 +75,7 @@ def _get_options(*args, **kwargs):
if dsn is not None and options.get("dsn") is None:
options["dsn"] = dsn

for key, value in iteritems(options):
for key, value in options.items():
if key not in rv:
# Option "with_locals" was renamed to "include_local_variables"
if key == "with_locals":
Expand Down Expand Up @@ -313,7 +313,7 @@ def _prepare_event(
# type: (...) -> Optional[Event]

if event.get("timestamp") is None:
event["timestamp"] = datetime_utcnow()
event["timestamp"] = datetime.now(timezone.utc)

if scope is not None:
is_transaction = event.get("type") == "transaction"
Expand Down Expand Up @@ -356,7 +356,7 @@ def _prepare_event(

for key in "release", "environment", "server_name", "dist":
if event.get(key) is None and self.options[key] is not None:
event[key] = text_type(self.options[key]).strip()
event[key] = str(self.options[key]).strip()
if event.get("sdk") is None:
sdk_info = dict(SDK_INFO)
sdk_info["integrations"] = sorted(self.integrations.keys())
Expand Down Expand Up @@ -435,7 +435,7 @@ def _is_ignored_error(self, event, hint):
for ignored_error in self.options["ignore_errors"]:
# String types are matched against the type name in the
# exception only
if isinstance(ignored_error, string_types):
if isinstance(ignored_error, str):
if ignored_error == error_full_name or ignored_error == error_type_name:
return True
else:
Expand Down Expand Up @@ -538,7 +538,7 @@ def _update_session_from_event(

if session.user_agent is None:
headers = (event.get("request") or {}).get("headers")
for k, v in iteritems(headers or {}):
for k, v in (headers or {}).items():
if k.lower() == "user-agent":
user_agent = v
break
Expand Down Expand Up @@ -621,7 +621,7 @@ def capture_event(
if should_use_envelope_endpoint:
headers = {
"event_id": event_opt["event_id"],
"sent_at": format_timestamp(datetime_utcnow()),
"sent_at": format_timestamp(datetime.now(timezone.utc)),
}

if dynamic_sampling_context:
Expand Down
4 changes: 2 additions & 2 deletions sentry_sdk/crons/decorator.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import sys
from contextlib import contextmanager

from sentry_sdk._compat import contextmanager, reraise
from sentry_sdk._types import TYPE_CHECKING
from sentry_sdk.crons import capture_checkin
from sentry_sdk.crons.consts import MonitorStatus
from sentry_sdk.utils import now
from sentry_sdk.utils import now, reraise

if TYPE_CHECKING:
from typing import Generator, Optional
Expand Down
9 changes: 4 additions & 5 deletions sentry_sdk/db/explain_plan/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import datetime
from datetime import datetime, timedelta, timezone

from sentry_sdk._compat import datetime_utcnow
from sentry_sdk.consts import TYPE_CHECKING

if TYPE_CHECKING:
Expand All @@ -16,11 +15,11 @@ def cache_statement(statement, options):
# type: (str, dict[str, Any]) -> None
global EXPLAIN_CACHE

now = datetime_utcnow()
now = datetime.now(timezone.utc)
explain_cache_timeout_seconds = options.get(
"explain_cache_timeout_seconds", EXPLAIN_CACHE_TIMEOUT_SECONDS
)
expiration_time = now + datetime.timedelta(seconds=explain_cache_timeout_seconds)
expiration_time = now + timedelta(seconds=explain_cache_timeout_seconds)

EXPLAIN_CACHE[hash(statement)] = expiration_time

Expand All @@ -32,7 +31,7 @@ def remove_expired_cache_items():
"""
global EXPLAIN_CACHE

now = datetime_utcnow()
now = datetime.now(timezone.utc)

for key, expiration_time in EXPLAIN_CACHE.items():
expiration_in_the_past = expiration_time < now
Expand Down
11 changes: 5 additions & 6 deletions sentry_sdk/envelope.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import json
import mimetypes

from sentry_sdk._compat import text_type, PY2
from sentry_sdk._types import TYPE_CHECKING
from sentry_sdk.session import Session
from sentry_sdk.utils import json_dumps, capture_internal_exceptions
Expand All @@ -19,9 +18,9 @@


def parse_json(data):
# type: (Union[bytes, text_type]) -> Any
# type: (Union[bytes, str]) -> Any
# on some python 3 versions this needs to be bytes
if not PY2 and isinstance(data, bytes):
if isinstance(data, bytes):
data = data.decode("utf-8", "replace")
return json.loads(data)

Expand Down Expand Up @@ -159,7 +158,7 @@ class PayloadRef(object):
def __init__(
self,
bytes=None, # type: Optional[bytes]
path=None, # type: Optional[Union[bytes, text_type]]
path=None, # type: Optional[Union[bytes, str]]
json=None, # type: Optional[Any]
):
# type: (...) -> None
Expand Down Expand Up @@ -202,7 +201,7 @@ def __repr__(self):
class Item(object):
def __init__(
self,
payload, # type: Union[bytes, text_type, PayloadRef]
payload, # type: Union[bytes, str, PayloadRef]
headers=None, # type: Optional[Dict[str, Any]]
type=None, # type: Optional[str]
content_type=None, # type: Optional[str]
Expand All @@ -215,7 +214,7 @@ def __init__(
self.headers = headers
if isinstance(payload, bytes):
payload = PayloadRef(bytes=payload)
elif isinstance(payload, text_type):
elif isinstance(payload, str):
payload = PayloadRef(bytes=payload.encode("utf-8"))
else:
payload = payload
Expand Down
6 changes: 3 additions & 3 deletions sentry_sdk/hub.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import copy
import sys

from contextlib import contextmanager
from datetime import datetime, timezone

from sentry_sdk._compat import datetime_utcnow, with_metaclass
from sentry_sdk._compat import with_metaclass
from sentry_sdk.consts import INSTRUMENTER
from sentry_sdk.scope import Scope
from sentry_sdk.client import Client
Expand Down Expand Up @@ -438,7 +438,7 @@ def add_breadcrumb(self, crumb=None, hint=None, **kwargs):
hint = dict(hint or ()) # type: Hint

if crumb.get("timestamp") is None:
crumb["timestamp"] = datetime_utcnow()
crumb["timestamp"] = datetime.now(timezone.utc)
if crumb.get("type") is None:
crumb["type"] = "default"

Expand Down
7 changes: 3 additions & 4 deletions sentry_sdk/integrations/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import absolute_import
from threading import Lock

from sentry_sdk._compat import iteritems
from sentry_sdk._types import TYPE_CHECKING
from sentry_sdk.utils import logger

Expand Down Expand Up @@ -124,7 +123,7 @@ def setup_integrations(
integrations[instance.identifier] = instance
used_as_default_integration.add(instance.identifier)

for identifier, integration in iteritems(integrations):
for identifier, integration in integrations.items():
with _installer_lock:
if identifier not in _processed_integrations:
logger.debug(
Expand All @@ -139,7 +138,7 @@ def setup_integrations(
"deprecated. Use `setup_once`.",
identifier,
)
integration.install()
integration.install() # type: ignore
else:
raise
except DidNotEnable as e:
Expand All @@ -156,7 +155,7 @@ def setup_integrations(

integrations = {
identifier: integration
for identifier, integration in iteritems(integrations)
for identifier, integration in integrations.items()
if identifier in _installed_integrations
}

Expand Down
5 changes: 4 additions & 1 deletion sentry_sdk/integrations/_asgi_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
from typing import Any
from typing import Dict
from typing import Optional
from typing import Union
from typing_extensions import Literal

from sentry_sdk.utils import AnnotatedValue


def _get_headers(asgi_scope):
# type: (Any) -> Dict[str, str]
Expand All @@ -29,7 +32,7 @@ def _get_headers(asgi_scope):


def _get_url(asgi_scope, default_scheme, host):
# type: (Dict[str, Any], Literal["ws", "http"], Optional[str]) -> str
# type: (Dict[str, Any], Literal["ws", "http"], Optional[Union[AnnotatedValue, str]]) -> str
"""
Extract URL from the ASGI scope, without also including the querystring.
"""
Expand Down
Loading