Skip to content

Commit ddb4a29

Browse files
ref(api): Remove store endpoint (getsentry#2656)
## Summary This change removes all usages of the deprecated `store` endpoint from the Python SDK. From now on, events that were previously sent to the `store` endpoint will now be sent as envelopes to the `envelope` endpoint. ## Breaking API changes - `sentry_sdk.transport.Transport` is now an abstract base class, and therefore, it cannot be instantiated directly. Subclasses must implement the `capture_envelope` method. - `sentry_sdk.utils.Auth.store_api_url` has been removed. - `sentry_sdk.utils.Auth.get_api_url`'s now accepts a `sentry_sdk.consts.EndpointType` enum instead of a string as its only parameter. Supplying this parameter is currently unnecessary, since the parameter's default value is the only possible `sentry_sdk.consts.EndpointType` value. ## Backwards-compatible API changes - `sentry_sdk.transport.Transport.capture_event` has been deprecated. Please use `sentry_sdk.transport.Transport.capture_envelope`, instead. - Passing a function to `sentry_sdk.init`'s `transport` keyword argument has been deprecated. If you wish to provide a custom transport, please pass a `sentry_sdk.transport.Transport` instance or a subclass. ## Other changes - `sentry_sdk.transport.HttpTransport._send_event` has been removed, and uses of this method have been removed from the codebase, including from tests. - Cleaned up some transport-related test code _________________________ * Remove store endpoint * Fix linter error * Add stacklevel to warn call * Remove `store_api_url` test, update `get_api_url` test * Fix mypy * Correct import * Use `Enum` instead of `StrEnum` * Update `envelope.py` * Remove `Envelope.events` calls * Fix `capture_events_forksafe` * Hopefully fix circular import * Manually set TestTransport * Fix circular import * Revert "Fix circular import" This reverts commit e681bdb. * Revert "Hopefully fix circular import" This reverts commit 7105849. * Move EndpointType to top of file * Fix AWS tests * Remove TODO comment * Undo ABC change I will make a separate PR for this * Update * Rename envelope_item to envelope_items * Remove unneeded import statement * Updated migration guide * Put back `has_tracing_enabled` check * Remove test for replay context * Update MIGRATION_GUIDE.md * Auto-enable more integrations (getsentry#2671) * Remove deprecated code (getsentry#2666) * remove deprecated client options * remove .install() * remove new_span Fixes getsentryGH-1957 --------- Co-authored-by: Ivana Kellyerova <ivana.kellyerova@sentry.io>
1 parent cb2c70f commit ddb4a29

17 files changed

Lines changed: 246 additions & 447 deletions

File tree

MIGRATION_GUIDE.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,17 @@
2222
- 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`.
2323
- The deprecated `with_locals` configuration option was removed. Use `include_local_variables` instead. See https://docs.sentry.io/platforms/python/configuration/options/#include-local-variables.
2424
- The deprecated `request_bodies` configuration option was removed. Use `max_request_body_size`. See https://docs.sentry.io/platforms/python/configuration/options/#max-request-body-size.
25+
- Removed `sentry_sdk.utils.Auth.store_api_url`.
26+
- `sentry_sdk.utils.Auth.get_api_url`'s now accepts a `sentry_sdk.consts.EndpointType` enum instead of a string as its only parameter. We recommend omitting this argument when calling the function, since the parameter's default value is the only possible `sentry_sdk.consts.EndpointType` value. The parameter exists for future compatibility.
2527
- Removed `tracing_utils_py2.py`. The `start_child_span_decorator` is now in `sentry_sdk.tracing_utils`.
2628
- Removed support for the `install` method for custom integrations. Please use `setup_once` instead.
2729
- Removed `sentry_sdk.tracing.Span.new_span`. Use `sentry_sdk.tracing.Span.start_child` instead.
2830
- Removed `sentry_sdk.tracing.Transaction.new_span`. Use `sentry_sdk.tracing.Transaction.start_child` instead.
31+
- Removed support for the `install` method for custom integrations. Please use `setup_once` instead.
32+
- Removed `sentry_sdk.tracing.Span.new_span`. Use `sentry_sdk.tracing.Span.start_child` instead.
33+
- Removed `sentry_sdk.tracing.Transaction.new_span`. Use `sentry_sdk.tracing.Transaction.start_child` instead.
2934

3035
## Deprecated
36+
37+
- Deprecated `sentry_sdk.transport.Transport.capture_event`. Please use `sentry_sdk.transport.Transport.capture_envelope`, instead.
38+
- Passing a function to `sentry_sdk.init`'s `transport` keyword argument has been deprecated. If you wish to provide a custom transport, please pass a `sentry_sdk.transport.Transport` instance or a subclass.

sentry_sdk/_types.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@
5757
"monitor",
5858
]
5959
SessionStatus = Literal["ok", "exited", "crashed", "abnormal"]
60-
EndpointType = Literal["store", "envelope"]
6160

6261
DurationUnit = Literal[
6362
"nanosecond",

sentry_sdk/client.py

Lines changed: 24 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
logger,
1818
)
1919
from sentry_sdk.serializer import serialize
20-
from sentry_sdk.tracing import trace, has_tracing_enabled
20+
from sentry_sdk.tracing import trace
2121
from sentry_sdk.transport import make_transport
2222
from sentry_sdk.consts import (
2323
DEFAULT_MAX_VALUE_LENGTH,
@@ -588,58 +588,40 @@ def capture_event(
588588
):
589589
return None
590590

591-
tracing_enabled = has_tracing_enabled(self.options)
592591
attachments = hint.get("attachments")
593592

594593
trace_context = event_opt.get("contexts", {}).get("trace") or {}
595594
dynamic_sampling_context = trace_context.pop("dynamic_sampling_context", {})
596595

597-
# If tracing is enabled all events should go to /envelope endpoint.
598-
# If no tracing is enabled only transactions, events with attachments, and checkins should go to the /envelope endpoint.
599-
should_use_envelope_endpoint = (
600-
tracing_enabled
601-
or is_transaction
602-
or is_checkin
603-
or bool(attachments)
604-
or bool(self.spotlight)
605-
)
606-
if should_use_envelope_endpoint:
607-
headers = {
608-
"event_id": event_opt["event_id"],
609-
"sent_at": format_timestamp(datetime.now(timezone.utc)),
610-
}
611-
612-
if dynamic_sampling_context:
613-
headers["trace"] = dynamic_sampling_context
614-
615-
envelope = Envelope(headers=headers)
616-
617-
if is_transaction:
618-
if profile is not None:
619-
envelope.add_profile(profile.to_json(event_opt, self.options))
620-
envelope.add_transaction(event_opt)
621-
elif is_checkin:
622-
envelope.add_checkin(event_opt)
623-
else:
624-
envelope.add_event(event_opt)
596+
headers = {
597+
"event_id": event_opt["event_id"],
598+
"sent_at": format_timestamp(datetime.now(timezone.utc)),
599+
}
625600

626-
for attachment in attachments or ():
627-
envelope.add_item(attachment.to_envelope_item())
601+
if dynamic_sampling_context:
602+
headers["trace"] = dynamic_sampling_context
628603

629-
if self.spotlight:
630-
self.spotlight.capture_envelope(envelope)
604+
envelope = Envelope(headers=headers)
631605

632-
if self.transport is None:
633-
return None
606+
if is_transaction:
607+
if profile is not None:
608+
envelope.add_profile(profile.to_json(event_opt, self.options))
609+
envelope.add_transaction(event_opt)
610+
elif is_checkin:
611+
envelope.add_checkin(event_opt)
612+
else:
613+
envelope.add_event(event_opt)
634614

635-
self.transport.capture_envelope(envelope)
615+
for attachment in attachments or ():
616+
envelope.add_item(attachment.to_envelope_item())
636617

637-
else:
638-
if self.transport is None:
639-
return None
618+
if self.spotlight:
619+
self.spotlight.capture_envelope(envelope)
620+
621+
if self.transport is None:
622+
return None
640623

641-
# All other events go to the legacy /store/ endpoint (will be removed in the future).
642-
self.transport.capture_event(event_opt)
624+
self.transport.capture_envelope(envelope)
643625

644626
return event_id
645627

sentry_sdk/consts.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
1+
from enum import Enum
12
from sentry_sdk._types import TYPE_CHECKING
23

34
# up top to prevent circular import due to integration import
45
DEFAULT_MAX_VALUE_LENGTH = 1024
56

7+
8+
# Also needs to be at the top to prevent circular import
9+
class EndpointType(Enum):
10+
"""
11+
The type of an endpoint. This is an enum, rather than a constant, for historical reasons
12+
(the old /store endpoint). The enum also preserve future compatibility, in case we ever
13+
have a new endpoint.
14+
"""
15+
16+
ENVELOPE = "envelope"
17+
18+
619
if TYPE_CHECKING:
720
import sentry_sdk
821

sentry_sdk/envelope.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ def parse_json(data):
2626

2727

2828
class Envelope:
29+
"""
30+
Represents a Sentry Envelope. The calling code is responsible for adhering to the constraints
31+
documented in the Sentry docs: https://develop.sentry.dev/sdk/envelopes/#data-model. In particular,
32+
each envelope may have at most one Item with type "event" or "transaction" (but not both).
33+
"""
34+
2935
def __init__(
3036
self,
3137
headers=None, # type: Optional[Dict[str, Any]]

sentry_sdk/scope.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1077,17 +1077,6 @@ def _apply_contexts_to_event(self, event, hint, options):
10771077
else:
10781078
contexts["trace"] = self.get_trace_context()
10791079

1080-
# Add "reply_id" context
1081-
try:
1082-
replay_id = contexts["trace"]["dynamic_sampling_context"]["replay_id"]
1083-
except (KeyError, TypeError):
1084-
replay_id = None
1085-
1086-
if replay_id is not None:
1087-
contexts["replay"] = {
1088-
"replay_id": replay_id,
1089-
}
1090-
10911080
@_disable_capture
10921081
def apply_to_event(
10931082
self,

sentry_sdk/transport.py

Lines changed: 57 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import io
2+
import warnings
23
import urllib3
34
import certifi
45
import gzip
56
import time
67
from datetime import datetime, timedelta, timezone
78
from collections import defaultdict
89

9-
from sentry_sdk.utils import Dsn, logger, capture_internal_exceptions, json_dumps
10+
from sentry_sdk.consts import EndpointType
11+
from sentry_sdk.utils import Dsn, logger, capture_internal_exceptions
1012
from sentry_sdk.worker import BackgroundWorker
1113
from sentry_sdk.envelope import Envelope, Item, PayloadRef
1214
from sentry_sdk._types import TYPE_CHECKING
@@ -25,7 +27,7 @@
2527
from urllib3.poolmanager import PoolManager
2628
from urllib3.poolmanager import ProxyManager
2729

28-
from sentry_sdk._types import Event, EndpointType
30+
from sentry_sdk._types import Event
2931

3032
DataCategory = Optional[str]
3133

@@ -58,10 +60,21 @@ def capture_event(
5860
):
5961
# type: (...) -> None
6062
"""
63+
DEPRECATED: Please use capture_envelope instead.
64+
6165
This gets invoked with the event dictionary when an event should
6266
be sent to sentry.
6367
"""
64-
raise NotImplementedError()
68+
69+
warnings.warn(
70+
"capture_event is deprecated, please use capture_envelope instead!",
71+
DeprecationWarning,
72+
stacklevel=2,
73+
)
74+
75+
envelope = Envelope()
76+
envelope.add_event(event)
77+
self.capture_envelope(envelope)
6578

6679
def capture_envelope(
6780
self, envelope # type: Envelope
@@ -71,9 +84,8 @@ def capture_envelope(
7184
Send an envelope to Sentry.
7285
7386
Envelopes are a data container format that can hold any type of data
74-
submitted to Sentry. We use it for transactions and sessions, but
75-
regular "error" events should go through `capture_event` for backwards
76-
compat.
87+
submitted to Sentry. We use it to send all event data (including errors,
88+
transactions, crons checkins, etc.) to Sentry.
7789
"""
7890
raise NotImplementedError()
7991

@@ -83,13 +95,23 @@ def flush(
8395
callback=None, # type: Optional[Any]
8496
):
8597
# type: (...) -> None
86-
"""Wait `timeout` seconds for the current events to be sent out."""
87-
pass
98+
"""
99+
Wait `timeout` seconds for the current events to be sent out.
100+
101+
The default implementation is a no-op, since this method may only be relevant to some transports.
102+
Subclasses should override this method if necessary.
103+
"""
104+
return None
88105

89106
def kill(self):
90107
# type: () -> None
91-
"""Forcefully kills the transport."""
92-
pass
108+
"""
109+
Forcefully kills the transport.
110+
111+
The default implementation is a no-op, since this method may only be relevant to some transports.
112+
Subclasses should override this method if necessary.
113+
"""
114+
return None
93115

94116
def record_lost_event(
95117
self,
@@ -216,7 +238,7 @@ def _send_request(
216238
self,
217239
body, # type: bytes
218240
headers, # type: Dict[str, str]
219-
endpoint_type="store", # type: EndpointType
241+
endpoint_type=EndpointType.ENVELOPE, # type: EndpointType
220242
envelope=None, # type: Optional[Envelope]
221243
):
222244
# type: (...) -> None
@@ -333,46 +355,6 @@ def is_healthy(self):
333355
# type: () -> bool
334356
return not (self._is_worker_full() or self._is_rate_limited())
335357

336-
def _send_event(
337-
self, event # type: Event
338-
):
339-
# type: (...) -> None
340-
341-
if self._check_disabled("error"):
342-
self.on_dropped_event("self_rate_limits")
343-
self.record_lost_event("ratelimit_backoff", data_category="error")
344-
return None
345-
346-
body = io.BytesIO()
347-
if self._compresslevel == 0:
348-
body.write(json_dumps(event))
349-
else:
350-
with gzip.GzipFile(
351-
fileobj=body, mode="w", compresslevel=self._compresslevel
352-
) as f:
353-
f.write(json_dumps(event))
354-
355-
assert self.parsed_dsn is not None
356-
logger.debug(
357-
"Sending event, type:%s level:%s event_id:%s project:%s host:%s"
358-
% (
359-
event.get("type") or "null",
360-
event.get("level") or "null",
361-
event.get("event_id") or "null",
362-
self.parsed_dsn.project_id,
363-
self.parsed_dsn.host,
364-
)
365-
)
366-
367-
headers = {
368-
"Content-Type": "application/json",
369-
}
370-
if self._compresslevel > 0:
371-
headers["Content-Encoding"] = "gzip"
372-
373-
self._send_request(body.getvalue(), headers=headers)
374-
return None
375-
376358
def _send_envelope(
377359
self, envelope # type: Envelope
378360
):
@@ -430,7 +412,7 @@ def _send_envelope(
430412
self._send_request(
431413
body.getvalue(),
432414
headers=headers,
433-
endpoint_type="envelope",
415+
endpoint_type=EndpointType.ENVELOPE,
434416
envelope=envelope,
435417
)
436418
return None
@@ -501,23 +483,6 @@ def _make_pool(
501483
else:
502484
return urllib3.PoolManager(**opts)
503485

504-
def capture_event(
505-
self, event # type: Event
506-
):
507-
# type: (...) -> None
508-
hub = self.hub_cls.current
509-
510-
def send_event_wrapper():
511-
# type: () -> None
512-
with hub:
513-
with capture_internal_exceptions():
514-
self._send_event(event)
515-
self._flush_client_reports()
516-
517-
if not self._worker.submit(send_event_wrapper):
518-
self.on_dropped_event("full_queue")
519-
self.record_lost_event("queue_overflow", data_category="error")
520-
521486
def capture_envelope(
522487
self, envelope # type: Envelope
523488
):
@@ -555,6 +520,11 @@ def kill(self):
555520

556521

557522
class _FunctionTransport(Transport):
523+
"""
524+
DEPRECATED: Users wishing to provide a custom transport should subclass
525+
the Transport class, rather than providing a function.
526+
"""
527+
558528
def __init__(
559529
self, func # type: Callable[[Event], None]
560530
):
@@ -569,19 +539,33 @@ def capture_event(
569539
self._func(event)
570540
return None
571541

542+
def capture_envelope(self, envelope: Envelope) -> None:
543+
# Since function transports expect to be called with an event, we need
544+
# to iterate over the envelope and call the function for each event, via
545+
# the deprecated capture_event method.
546+
event = envelope.get_event()
547+
if event is not None:
548+
self.capture_event(event)
549+
572550

573551
def make_transport(options):
574552
# type: (Dict[str, Any]) -> Optional[Transport]
575553
ref_transport = options["transport"]
576554

577-
# If no transport is given, we use the http transport class
578-
if ref_transport is None:
579-
transport_cls = HttpTransport # type: Type[Transport]
580-
elif isinstance(ref_transport, Transport):
555+
# By default, we use the http transport class
556+
transport_cls = HttpTransport # type: Type[Transport]
557+
558+
if isinstance(ref_transport, Transport):
581559
return ref_transport
582560
elif isinstance(ref_transport, type) and issubclass(ref_transport, Transport):
583561
transport_cls = ref_transport
584562
elif callable(ref_transport):
563+
warnings.warn(
564+
"Function transports are deprecated and will be removed in a future release."
565+
"Please provide a Transport instance or subclass, instead.",
566+
DeprecationWarning,
567+
stacklevel=2,
568+
)
585569
return _FunctionTransport(ref_transport)
586570

587571
# if a transport class is given only instantiate it if the dsn is not

0 commit comments

Comments
 (0)