sentry-sdk 3.0.0a1__py2.py3-none-any.whl → 3.0.0a3__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of sentry-sdk might be problematic. Click here for more details.

Files changed (157) hide show
  1. sentry_sdk/__init__.py +2 -0
  2. sentry_sdk/_compat.py +5 -12
  3. sentry_sdk/_init_implementation.py +7 -7
  4. sentry_sdk/_log_batcher.py +17 -29
  5. sentry_sdk/_lru_cache.py +7 -9
  6. sentry_sdk/_queue.py +2 -4
  7. sentry_sdk/_types.py +11 -18
  8. sentry_sdk/_werkzeug.py +5 -7
  9. sentry_sdk/ai/monitoring.py +44 -31
  10. sentry_sdk/ai/utils.py +3 -4
  11. sentry_sdk/api.py +75 -87
  12. sentry_sdk/attachments.py +10 -12
  13. sentry_sdk/client.py +137 -155
  14. sentry_sdk/consts.py +430 -174
  15. sentry_sdk/crons/api.py +16 -17
  16. sentry_sdk/crons/decorator.py +25 -27
  17. sentry_sdk/debug.py +4 -6
  18. sentry_sdk/envelope.py +46 -112
  19. sentry_sdk/feature_flags.py +9 -15
  20. sentry_sdk/integrations/__init__.py +24 -19
  21. sentry_sdk/integrations/_asgi_common.py +15 -18
  22. sentry_sdk/integrations/_wsgi_common.py +22 -33
  23. sentry_sdk/integrations/aiohttp.py +32 -30
  24. sentry_sdk/integrations/anthropic.py +42 -37
  25. sentry_sdk/integrations/argv.py +3 -4
  26. sentry_sdk/integrations/ariadne.py +16 -18
  27. sentry_sdk/integrations/arq.py +21 -29
  28. sentry_sdk/integrations/asgi.py +63 -37
  29. sentry_sdk/integrations/asyncio.py +14 -16
  30. sentry_sdk/integrations/atexit.py +6 -10
  31. sentry_sdk/integrations/aws_lambda.py +26 -36
  32. sentry_sdk/integrations/beam.py +10 -18
  33. sentry_sdk/integrations/boto3.py +18 -16
  34. sentry_sdk/integrations/bottle.py +25 -34
  35. sentry_sdk/integrations/celery/__init__.py +41 -61
  36. sentry_sdk/integrations/celery/beat.py +23 -27
  37. sentry_sdk/integrations/celery/utils.py +15 -17
  38. sentry_sdk/integrations/chalice.py +8 -10
  39. sentry_sdk/integrations/clickhouse_driver.py +21 -31
  40. sentry_sdk/integrations/cloud_resource_context.py +9 -16
  41. sentry_sdk/integrations/cohere.py +27 -33
  42. sentry_sdk/integrations/dedupe.py +5 -8
  43. sentry_sdk/integrations/django/__init__.py +57 -72
  44. sentry_sdk/integrations/django/asgi.py +26 -34
  45. sentry_sdk/integrations/django/caching.py +23 -19
  46. sentry_sdk/integrations/django/middleware.py +17 -20
  47. sentry_sdk/integrations/django/signals_handlers.py +11 -10
  48. sentry_sdk/integrations/django/templates.py +19 -16
  49. sentry_sdk/integrations/django/transactions.py +16 -11
  50. sentry_sdk/integrations/django/views.py +6 -10
  51. sentry_sdk/integrations/dramatiq.py +21 -21
  52. sentry_sdk/integrations/excepthook.py +10 -10
  53. sentry_sdk/integrations/executing.py +3 -4
  54. sentry_sdk/integrations/falcon.py +27 -42
  55. sentry_sdk/integrations/fastapi.py +13 -16
  56. sentry_sdk/integrations/flask.py +31 -38
  57. sentry_sdk/integrations/gcp.py +13 -16
  58. sentry_sdk/integrations/gnu_backtrace.py +4 -6
  59. sentry_sdk/integrations/gql.py +16 -17
  60. sentry_sdk/integrations/graphene.py +13 -12
  61. sentry_sdk/integrations/grpc/__init__.py +19 -1
  62. sentry_sdk/integrations/grpc/aio/server.py +15 -14
  63. sentry_sdk/integrations/grpc/client.py +19 -9
  64. sentry_sdk/integrations/grpc/consts.py +2 -0
  65. sentry_sdk/integrations/grpc/server.py +12 -8
  66. sentry_sdk/integrations/httpx.py +9 -12
  67. sentry_sdk/integrations/huey.py +13 -20
  68. sentry_sdk/integrations/huggingface_hub.py +18 -18
  69. sentry_sdk/integrations/langchain.py +203 -113
  70. sentry_sdk/integrations/launchdarkly.py +13 -10
  71. sentry_sdk/integrations/litestar.py +37 -35
  72. sentry_sdk/integrations/logging.py +52 -65
  73. sentry_sdk/integrations/loguru.py +127 -57
  74. sentry_sdk/integrations/modules.py +3 -4
  75. sentry_sdk/integrations/openai.py +100 -88
  76. sentry_sdk/integrations/openai_agents/__init__.py +49 -0
  77. sentry_sdk/integrations/openai_agents/consts.py +1 -0
  78. sentry_sdk/integrations/openai_agents/patches/__init__.py +4 -0
  79. sentry_sdk/integrations/openai_agents/patches/agent_run.py +152 -0
  80. sentry_sdk/integrations/openai_agents/patches/models.py +52 -0
  81. sentry_sdk/integrations/openai_agents/patches/runner.py +42 -0
  82. sentry_sdk/integrations/openai_agents/patches/tools.py +84 -0
  83. sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
  84. sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +20 -0
  85. sentry_sdk/integrations/openai_agents/spans/ai_client.py +46 -0
  86. sentry_sdk/integrations/openai_agents/spans/execute_tool.py +47 -0
  87. sentry_sdk/integrations/openai_agents/spans/handoff.py +24 -0
  88. sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +41 -0
  89. sentry_sdk/integrations/openai_agents/utils.py +201 -0
  90. sentry_sdk/integrations/openfeature.py +11 -6
  91. sentry_sdk/integrations/pure_eval.py +6 -10
  92. sentry_sdk/integrations/pymongo.py +13 -17
  93. sentry_sdk/integrations/pyramid.py +31 -36
  94. sentry_sdk/integrations/quart.py +23 -28
  95. sentry_sdk/integrations/ray.py +73 -64
  96. sentry_sdk/integrations/redis/__init__.py +7 -4
  97. sentry_sdk/integrations/redis/_async_common.py +25 -12
  98. sentry_sdk/integrations/redis/_sync_common.py +19 -13
  99. sentry_sdk/integrations/redis/modules/caches.py +17 -8
  100. sentry_sdk/integrations/redis/modules/queries.py +9 -8
  101. sentry_sdk/integrations/redis/rb.py +3 -2
  102. sentry_sdk/integrations/redis/redis.py +4 -4
  103. sentry_sdk/integrations/redis/redis_cluster.py +21 -13
  104. sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +3 -2
  105. sentry_sdk/integrations/redis/utils.py +23 -24
  106. sentry_sdk/integrations/rq.py +13 -16
  107. sentry_sdk/integrations/rust_tracing.py +9 -6
  108. sentry_sdk/integrations/sanic.py +34 -46
  109. sentry_sdk/integrations/serverless.py +22 -27
  110. sentry_sdk/integrations/socket.py +27 -15
  111. sentry_sdk/integrations/spark/__init__.py +1 -0
  112. sentry_sdk/integrations/spark/spark_driver.py +45 -83
  113. sentry_sdk/integrations/spark/spark_worker.py +7 -11
  114. sentry_sdk/integrations/sqlalchemy.py +22 -19
  115. sentry_sdk/integrations/starlette.py +86 -90
  116. sentry_sdk/integrations/starlite.py +28 -34
  117. sentry_sdk/integrations/statsig.py +5 -4
  118. sentry_sdk/integrations/stdlib.py +28 -24
  119. sentry_sdk/integrations/strawberry.py +62 -49
  120. sentry_sdk/integrations/sys_exit.py +7 -11
  121. sentry_sdk/integrations/threading.py +12 -14
  122. sentry_sdk/integrations/tornado.py +28 -32
  123. sentry_sdk/integrations/trytond.py +4 -3
  124. sentry_sdk/integrations/typer.py +8 -6
  125. sentry_sdk/integrations/unleash.py +5 -4
  126. sentry_sdk/integrations/wsgi.py +47 -46
  127. sentry_sdk/logger.py +41 -10
  128. sentry_sdk/monitor.py +16 -28
  129. sentry_sdk/opentelemetry/consts.py +11 -4
  130. sentry_sdk/opentelemetry/contextvars_context.py +26 -16
  131. sentry_sdk/opentelemetry/propagator.py +38 -21
  132. sentry_sdk/opentelemetry/sampler.py +51 -34
  133. sentry_sdk/opentelemetry/scope.py +36 -37
  134. sentry_sdk/opentelemetry/span_processor.py +48 -58
  135. sentry_sdk/opentelemetry/tracing.py +58 -14
  136. sentry_sdk/opentelemetry/utils.py +186 -194
  137. sentry_sdk/profiler/continuous_profiler.py +108 -97
  138. sentry_sdk/profiler/transaction_profiler.py +70 -97
  139. sentry_sdk/profiler/utils.py +11 -15
  140. sentry_sdk/scope.py +251 -273
  141. sentry_sdk/scrubber.py +22 -26
  142. sentry_sdk/serializer.py +40 -54
  143. sentry_sdk/session.py +44 -61
  144. sentry_sdk/sessions.py +35 -49
  145. sentry_sdk/spotlight.py +15 -21
  146. sentry_sdk/tracing.py +121 -187
  147. sentry_sdk/tracing_utils.py +104 -122
  148. sentry_sdk/transport.py +131 -157
  149. sentry_sdk/utils.py +232 -309
  150. sentry_sdk/worker.py +16 -28
  151. {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/METADATA +3 -3
  152. sentry_sdk-3.0.0a3.dist-info/RECORD +168 -0
  153. {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/WHEEL +1 -1
  154. sentry_sdk-3.0.0a1.dist-info/RECORD +0 -154
  155. {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/entry_points.txt +0 -0
  156. {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/licenses/LICENSE +0 -0
  157. {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,5 @@
1
+ from __future__ import annotations
1
2
  from decimal import Decimal
2
- from typing import cast
3
3
 
4
4
  from opentelemetry import trace
5
5
  from opentelemetry.sdk.trace.sampling import Sampler, SamplingResult, Decision
@@ -21,15 +21,16 @@ from sentry_sdk.utils import is_valid_sample_rate, logger
21
21
  from typing import TYPE_CHECKING
22
22
 
23
23
  if TYPE_CHECKING:
24
- from typing import Any, Optional, Sequence, Union
24
+ from typing import Any, Optional, Sequence
25
25
  from opentelemetry.context import Context
26
26
  from opentelemetry.trace import Link, SpanKind
27
27
  from opentelemetry.trace.span import SpanContext
28
28
  from opentelemetry.util.types import Attributes
29
29
 
30
30
 
31
- def get_parent_sampled(parent_context, trace_id):
32
- # type: (Optional[SpanContext], int) -> Optional[bool]
31
+ def get_parent_sampled(
32
+ parent_context: Optional[SpanContext], trace_id: int
33
+ ) -> Optional[bool]:
33
34
  if parent_context is None:
34
35
  return None
35
36
 
@@ -54,8 +55,9 @@ def get_parent_sampled(parent_context, trace_id):
54
55
  return None
55
56
 
56
57
 
57
- def get_parent_sample_rate(parent_context, trace_id):
58
- # type: (Optional[SpanContext], int) -> Optional[float]
58
+ def get_parent_sample_rate(
59
+ parent_context: Optional[SpanContext], trace_id: int
60
+ ) -> Optional[float]:
59
61
  if parent_context is None:
60
62
  return None
61
63
 
@@ -74,8 +76,9 @@ def get_parent_sample_rate(parent_context, trace_id):
74
76
  return None
75
77
 
76
78
 
77
- def get_parent_sample_rand(parent_context, trace_id):
78
- # type: (Optional[SpanContext], int) -> Optional[Decimal]
79
+ def get_parent_sample_rand(
80
+ parent_context: Optional[SpanContext], trace_id: int
81
+ ) -> Optional[Decimal]:
79
82
  if parent_context is None:
80
83
  return None
81
84
 
@@ -91,8 +94,12 @@ def get_parent_sample_rand(parent_context, trace_id):
91
94
  return None
92
95
 
93
96
 
94
- def dropped_result(span_context, attributes, sample_rate=None, sample_rand=None):
95
- # type: (SpanContext, Attributes, Optional[float], Optional[Decimal]) -> SamplingResult
97
+ def dropped_result(
98
+ span_context: SpanContext,
99
+ attributes: Attributes,
100
+ sample_rate: Optional[float] = None,
101
+ sample_rand: Optional[Decimal] = None,
102
+ ) -> SamplingResult:
96
103
  """
97
104
  React to a span getting unsampled and return a DROP SamplingResult.
98
105
 
@@ -129,8 +136,12 @@ def dropped_result(span_context, attributes, sample_rate=None, sample_rand=None)
129
136
  )
130
137
 
131
138
 
132
- def sampled_result(span_context, attributes, sample_rate=None, sample_rand=None):
133
- # type: (SpanContext, Attributes, Optional[float], Optional[Decimal]) -> SamplingResult
139
+ def sampled_result(
140
+ span_context: SpanContext,
141
+ attributes: Attributes,
142
+ sample_rate: Optional[float] = None,
143
+ sample_rand: Optional[Decimal] = None,
144
+ ) -> SamplingResult:
134
145
  """
135
146
  React to a span being sampled and return a sampled SamplingResult.
136
147
 
@@ -151,8 +162,12 @@ def sampled_result(span_context, attributes, sample_rate=None, sample_rand=None)
151
162
  )
152
163
 
153
164
 
154
- def _update_trace_state(span_context, sampled, sample_rate=None, sample_rand=None):
155
- # type: (SpanContext, bool, Optional[float], Optional[Decimal]) -> TraceState
165
+ def _update_trace_state(
166
+ span_context: SpanContext,
167
+ sampled: bool,
168
+ sample_rate: Optional[float] = None,
169
+ sample_rand: Optional[Decimal] = None,
170
+ ) -> TraceState:
156
171
  trace_state = span_context.trace_state
157
172
 
158
173
  sampled = "true" if sampled else "false"
@@ -175,15 +190,14 @@ def _update_trace_state(span_context, sampled, sample_rate=None, sample_rand=Non
175
190
  class SentrySampler(Sampler):
176
191
  def should_sample(
177
192
  self,
178
- parent_context, # type: Optional[Context]
179
- trace_id, # type: int
180
- name, # type: str
181
- kind=None, # type: Optional[SpanKind]
182
- attributes=None, # type: Attributes
183
- links=None, # type: Optional[Sequence[Link]]
184
- trace_state=None, # type: Optional[TraceState]
185
- ):
186
- # type: (...) -> SamplingResult
193
+ parent_context: Optional[Context],
194
+ trace_id: int,
195
+ name: str,
196
+ kind: Optional[SpanKind] = None,
197
+ attributes: Attributes = None,
198
+ links: Optional[Sequence[Link]] = None,
199
+ trace_state: Optional[TraceState] = None,
200
+ ) -> SamplingResult:
187
201
  client = sentry_sdk.get_client()
188
202
 
189
203
  parent_span_context = trace.get_current_span(parent_context).get_span_context()
@@ -209,13 +223,12 @@ class SentrySampler(Sampler):
209
223
  sample_rand = parent_sample_rand
210
224
  else:
211
225
  # We are the head SDK and we need to generate a new sample_rand
212
- sample_rand = cast(Decimal, _generate_sample_rand(str(trace_id), (0, 1)))
226
+ sample_rand = _generate_sample_rand(str(trace_id), (0, 1))
213
227
 
214
228
  # Explicit sampled value provided at start_span
215
- custom_sampled = cast(
216
- "Optional[bool]", attributes.get(SentrySpanAttribute.CUSTOM_SAMPLED)
217
- )
218
- if custom_sampled is not None:
229
+ custom_sampled = attributes.get(SentrySpanAttribute.CUSTOM_SAMPLED)
230
+
231
+ if custom_sampled is not None and isinstance(custom_sampled, bool):
219
232
  if is_root_span:
220
233
  sample_rate = float(custom_sampled)
221
234
  if sample_rate > 0:
@@ -262,7 +275,8 @@ class SentrySampler(Sampler):
262
275
  sample_rate_to_propagate = sample_rate
263
276
 
264
277
  # If the sample rate is invalid, drop the span
265
- if not is_valid_sample_rate(sample_rate, source=self.__class__.__name__):
278
+ sample_rate = is_valid_sample_rate(sample_rate, source=self.__class__.__name__)
279
+ if sample_rate is None:
266
280
  logger.warning(
267
281
  f"[Tracing.Sampler] Discarding {name} because of invalid sample rate."
268
282
  )
@@ -275,7 +289,6 @@ class SentrySampler(Sampler):
275
289
  sample_rate_to_propagate = sample_rate
276
290
 
277
291
  # Compare sample_rand to sample_rate to make the final sampling decision
278
- sample_rate = float(cast("Union[bool, float, int]", sample_rate))
279
292
  sampled = sample_rand < Decimal.from_float(sample_rate)
280
293
 
281
294
  if sampled:
@@ -307,9 +320,13 @@ class SentrySampler(Sampler):
307
320
  return self.__class__.__name__
308
321
 
309
322
 
310
- def create_sampling_context(name, attributes, parent_span_context, trace_id):
311
- # type: (str, Attributes, Optional[SpanContext], int) -> dict[str, Any]
312
- sampling_context = {
323
+ def create_sampling_context(
324
+ name: str,
325
+ attributes: Attributes,
326
+ parent_span_context: Optional[SpanContext],
327
+ trace_id: int,
328
+ ) -> dict[str, Any]:
329
+ sampling_context: dict[str, Any] = {
313
330
  "transaction_context": {
314
331
  "name": name,
315
332
  "op": attributes.get(SentrySpanAttribute.OP) if attributes else None,
@@ -318,7 +335,7 @@ def create_sampling_context(name, attributes, parent_span_context, trace_id):
318
335
  ),
319
336
  },
320
337
  "parent_sampled": get_parent_sampled(parent_span_context, trace_id),
321
- } # type: dict[str, Any]
338
+ }
322
339
 
323
340
  if attributes is not None:
324
341
  sampling_context.update(attributes)
@@ -1,4 +1,4 @@
1
- from typing import cast
1
+ from __future__ import annotations
2
2
  from contextlib import contextmanager
3
3
  import warnings
4
4
 
@@ -24,9 +24,6 @@ from sentry_sdk.opentelemetry.consts import (
24
24
  SENTRY_USE_ISOLATION_SCOPE_KEY,
25
25
  TRACESTATE_SAMPLED_KEY,
26
26
  )
27
- from sentry_sdk.opentelemetry.contextvars_context import (
28
- SentryContextVarsRuntimeContext,
29
- )
30
27
  from sentry_sdk.opentelemetry.utils import trace_state_from_baggage
31
28
  from sentry_sdk.scope import Scope, ScopeType
32
29
  from sentry_sdk.tracing import Span
@@ -38,26 +35,21 @@ if TYPE_CHECKING:
38
35
 
39
36
  class PotelScope(Scope):
40
37
  @classmethod
41
- def _get_scopes(cls):
42
- # type: () -> Optional[Tuple[PotelScope, PotelScope]]
38
+ def _get_scopes(cls) -> Optional[Tuple[PotelScope, PotelScope]]:
43
39
  """
44
40
  Returns the current scopes tuple on the otel context. Internal use only.
45
41
  """
46
- return cast(
47
- "Optional[Tuple[PotelScope, PotelScope]]", get_value(SENTRY_SCOPES_KEY)
48
- )
42
+ return validate_scopes(get_value(SENTRY_SCOPES_KEY))
49
43
 
50
44
  @classmethod
51
- def get_current_scope(cls):
52
- # type: () -> PotelScope
45
+ def get_current_scope(cls) -> PotelScope:
53
46
  """
54
47
  Returns the current scope.
55
48
  """
56
49
  return cls._get_current_scope() or _INITIAL_CURRENT_SCOPE
57
50
 
58
51
  @classmethod
59
- def _get_current_scope(cls):
60
- # type: () -> Optional[PotelScope]
52
+ def _get_current_scope(cls) -> Optional[PotelScope]:
61
53
  """
62
54
  Returns the current scope without creating a new one. Internal use only.
63
55
  """
@@ -65,16 +57,14 @@ class PotelScope(Scope):
65
57
  return scopes[0] if scopes else None
66
58
 
67
59
  @classmethod
68
- def get_isolation_scope(cls):
69
- # type: () -> PotelScope
60
+ def get_isolation_scope(cls) -> PotelScope:
70
61
  """
71
62
  Returns the isolation scope.
72
63
  """
73
64
  return cls._get_isolation_scope() or _INITIAL_ISOLATION_SCOPE
74
65
 
75
66
  @classmethod
76
- def _get_isolation_scope(cls):
77
- # type: () -> Optional[PotelScope]
67
+ def _get_isolation_scope(cls) -> Optional[PotelScope]:
78
68
  """
79
69
  Returns the isolation scope without creating a new one. Internal use only.
80
70
  """
@@ -82,8 +72,9 @@ class PotelScope(Scope):
82
72
  return scopes[1] if scopes else None
83
73
 
84
74
  @contextmanager
85
- def continue_trace(self, environ_or_headers):
86
- # type: (Dict[str, Any]) -> Generator[None, None, None]
75
+ def continue_trace(
76
+ self, environ_or_headers: Dict[str, Any]
77
+ ) -> Generator[None, None, None]:
87
78
  """
88
79
  Sets the propagation context from environment or headers to continue an incoming trace.
89
80
  Any span started within this context manager will use the same trace_id, parent_span_id
@@ -98,8 +89,7 @@ class PotelScope(Scope):
98
89
  with use_span(NonRecordingSpan(span_context)):
99
90
  yield
100
91
 
101
- def _incoming_otel_span_context(self):
102
- # type: () -> Optional[SpanContext]
92
+ def _incoming_otel_span_context(self) -> Optional[SpanContext]:
103
93
  if self._propagation_context is None:
104
94
  return None
105
95
  # If sentry-trace extraction didn't have a parent_span_id, we don't have an upstream header
@@ -132,8 +122,7 @@ class PotelScope(Scope):
132
122
 
133
123
  return span_context
134
124
 
135
- def start_transaction(self, **kwargs):
136
- # type: (Any) -> Span
125
+ def start_transaction(self, **kwargs: Any) -> Span:
137
126
  """
138
127
  .. deprecated:: 3.0.0
139
128
  This function is deprecated and will be removed in a future release.
@@ -146,8 +135,7 @@ class PotelScope(Scope):
146
135
  )
147
136
  return self.start_span(**kwargs)
148
137
 
149
- def start_span(self, **kwargs):
150
- # type: (Any) -> Span
138
+ def start_span(self, **kwargs: Any) -> Span:
151
139
  return Span(**kwargs)
152
140
 
153
141
 
@@ -155,8 +143,7 @@ _INITIAL_CURRENT_SCOPE = PotelScope(ty=ScopeType.CURRENT)
155
143
  _INITIAL_ISOLATION_SCOPE = PotelScope(ty=ScopeType.ISOLATION)
156
144
 
157
145
 
158
- def setup_initial_scopes():
159
- # type: () -> None
146
+ def setup_initial_scopes() -> None:
160
147
  global _INITIAL_CURRENT_SCOPE, _INITIAL_ISOLATION_SCOPE
161
148
  _INITIAL_CURRENT_SCOPE = PotelScope(ty=ScopeType.CURRENT)
162
149
  _INITIAL_ISOLATION_SCOPE = PotelScope(ty=ScopeType.ISOLATION)
@@ -165,17 +152,18 @@ def setup_initial_scopes():
165
152
  attach(set_value(SENTRY_SCOPES_KEY, scopes))
166
153
 
167
154
 
168
- def setup_scope_context_management():
169
- # type: () -> None
155
+ def setup_scope_context_management() -> None:
170
156
  import opentelemetry.context
157
+ from sentry_sdk.opentelemetry.contextvars_context import (
158
+ SentryContextVarsRuntimeContext,
159
+ )
171
160
 
172
161
  opentelemetry.context._RUNTIME_CONTEXT = SentryContextVarsRuntimeContext()
173
162
  setup_initial_scopes()
174
163
 
175
164
 
176
165
  @contextmanager
177
- def isolation_scope():
178
- # type: () -> Generator[PotelScope, None, None]
166
+ def isolation_scope() -> Generator[PotelScope, None, None]:
179
167
  context = set_value(SENTRY_FORK_ISOLATION_SCOPE_KEY, True)
180
168
  token = attach(context)
181
169
  try:
@@ -185,8 +173,7 @@ def isolation_scope():
185
173
 
186
174
 
187
175
  @contextmanager
188
- def new_scope():
189
- # type: () -> Generator[PotelScope, None, None]
176
+ def new_scope() -> Generator[PotelScope, None, None]:
190
177
  token = attach(get_current())
191
178
  try:
192
179
  yield PotelScope.get_current_scope()
@@ -195,8 +182,7 @@ def new_scope():
195
182
 
196
183
 
197
184
  @contextmanager
198
- def use_scope(scope):
199
- # type: (PotelScope) -> Generator[PotelScope, None, None]
185
+ def use_scope(scope: PotelScope) -> Generator[PotelScope, None, None]:
200
186
  context = set_value(SENTRY_USE_CURRENT_SCOPE_KEY, scope)
201
187
  token = attach(context)
202
188
 
@@ -207,8 +193,9 @@ def use_scope(scope):
207
193
 
208
194
 
209
195
  @contextmanager
210
- def use_isolation_scope(isolation_scope):
211
- # type: (PotelScope) -> Generator[PotelScope, None, None]
196
+ def use_isolation_scope(
197
+ isolation_scope: PotelScope,
198
+ ) -> Generator[PotelScope, None, None]:
212
199
  context = set_value(SENTRY_USE_ISOLATION_SCOPE_KEY, isolation_scope)
213
200
  token = attach(context)
214
201
 
@@ -216,3 +203,15 @@ def use_isolation_scope(isolation_scope):
216
203
  yield isolation_scope
217
204
  finally:
218
205
  detach(token)
206
+
207
+
208
+ def validate_scopes(scopes: Any) -> Optional[Tuple[PotelScope, PotelScope]]:
209
+ if (
210
+ isinstance(scopes, tuple)
211
+ and len(scopes) == 2
212
+ and isinstance(scopes[0], PotelScope)
213
+ and isinstance(scopes[1], PotelScope)
214
+ ):
215
+ return scopes
216
+ else:
217
+ return None
@@ -1,5 +1,5 @@
1
+ from __future__ import annotations
1
2
  from collections import deque, defaultdict
2
- from typing import cast
3
3
 
4
4
  from opentelemetry.trace import (
5
5
  format_trace_id,
@@ -29,6 +29,7 @@ from sentry_sdk.opentelemetry.utils import (
29
29
  get_profile_context,
30
30
  get_sentry_meta,
31
31
  set_sentry_meta,
32
+ delete_sentry_meta,
32
33
  )
33
34
  from sentry_sdk.profiler.continuous_profiler import (
34
35
  try_autostart_continuous_profiler,
@@ -36,6 +37,7 @@ from sentry_sdk.profiler.continuous_profiler import (
36
37
  try_profile_lifecycle_trace_start,
37
38
  )
38
39
  from sentry_sdk.profiler.transaction_profiler import Profile
40
+ from sentry_sdk.utils import safe_str
39
41
  from sentry_sdk._types import TYPE_CHECKING
40
42
 
41
43
  if TYPE_CHECKING:
@@ -51,30 +53,24 @@ class SentrySpanProcessor(SpanProcessor):
51
53
  Converts OTel spans into Sentry spans so they can be sent to the Sentry backend.
52
54
  """
53
55
 
54
- def __new__(cls):
55
- # type: () -> SentrySpanProcessor
56
+ def __new__(cls) -> SentrySpanProcessor:
56
57
  if not hasattr(cls, "instance"):
57
58
  cls.instance = super().__new__(cls)
58
59
 
59
60
  return cls.instance
60
61
 
61
- def __init__(self):
62
- # type: () -> None
63
- self._children_spans = defaultdict(
64
- list
65
- ) # type: DefaultDict[int, List[ReadableSpan]]
66
- self._dropped_spans = defaultdict(lambda: 0) # type: DefaultDict[int, int]
62
+ def __init__(self) -> None:
63
+ self._children_spans: DefaultDict[int, List[ReadableSpan]] = defaultdict(list)
64
+ self._dropped_spans: DefaultDict[int, int] = defaultdict(lambda: 0)
67
65
 
68
- def on_start(self, span, parent_context=None):
69
- # type: (Span, Optional[Context]) -> None
66
+ def on_start(self, span: Span, parent_context: Optional[Context] = None) -> None:
70
67
  if is_sentry_span(span):
71
68
  return
72
69
 
73
70
  self._add_root_span(span, get_current_span(parent_context))
74
71
  self._start_profile(span)
75
72
 
76
- def on_end(self, span):
77
- # type: (ReadableSpan) -> None
73
+ def on_end(self, span: ReadableSpan) -> None:
78
74
  if is_sentry_span(span):
79
75
  return
80
76
 
@@ -87,18 +83,15 @@ class SentrySpanProcessor(SpanProcessor):
87
83
  self._append_child_span(span)
88
84
 
89
85
  # TODO-neel-potel not sure we need a clear like JS
90
- def shutdown(self):
91
- # type: () -> None
86
+ def shutdown(self) -> None:
92
87
  pass
93
88
 
94
89
  # TODO-neel-potel change default? this is 30 sec
95
90
  # TODO-neel-potel call this in client.flush
96
- def force_flush(self, timeout_millis=30000):
97
- # type: (int) -> bool
91
+ def force_flush(self, timeout_millis: int = 30000) -> bool:
98
92
  return True
99
93
 
100
- def _add_root_span(self, span, parent_span):
101
- # type: (Span, AbstractSpan) -> None
94
+ def _add_root_span(self, span: Span, parent_span: AbstractSpan) -> None:
102
95
  """
103
96
  This is required to make Span.root_span work
104
97
  since we can't traverse back to the root purely with otel efficiently.
@@ -111,8 +104,7 @@ class SentrySpanProcessor(SpanProcessor):
111
104
  # root span points to itself
112
105
  set_sentry_meta(span, "root_span", span)
113
106
 
114
- def _start_profile(self, span):
115
- # type: (Span) -> None
107
+ def _start_profile(self, span: Span) -> None:
116
108
  try_autostart_continuous_profiler()
117
109
 
118
110
  profiler_id = get_profiler_id()
@@ -147,14 +139,12 @@ class SentrySpanProcessor(SpanProcessor):
147
139
  span.set_attribute(SPANDATA.PROFILER_ID, profiler_id)
148
140
  set_sentry_meta(span, "continuous_profile", continuous_profile)
149
141
 
150
- def _stop_profile(self, span):
151
- # type: (ReadableSpan) -> None
142
+ def _stop_profile(self, span: ReadableSpan) -> None:
152
143
  continuous_profiler = get_sentry_meta(span, "continuous_profile")
153
144
  if continuous_profiler:
154
145
  continuous_profiler.stop()
155
146
 
156
- def _flush_root_span(self, span):
157
- # type: (ReadableSpan) -> None
147
+ def _flush_root_span(self, span: ReadableSpan) -> None:
158
148
  transaction_event = self._root_span_to_transaction_event(span)
159
149
  if not transaction_event:
160
150
  return
@@ -173,9 +163,9 @@ class SentrySpanProcessor(SpanProcessor):
173
163
  # TODO-neel-potel sort and cutoff max spans
174
164
 
175
165
  sentry_sdk.capture_event(transaction_event)
166
+ self._cleanup_references([span] + collected_spans)
176
167
 
177
- def _append_child_span(self, span):
178
- # type: (ReadableSpan) -> None
168
+ def _append_child_span(self, span: ReadableSpan) -> None:
179
169
  if not span.parent:
180
170
  return
181
171
 
@@ -190,14 +180,13 @@ class SentrySpanProcessor(SpanProcessor):
190
180
  else:
191
181
  self._dropped_spans[span.parent.span_id] += 1
192
182
 
193
- def _collect_children(self, span):
194
- # type: (ReadableSpan) -> tuple[List[ReadableSpan], int]
183
+ def _collect_children(self, span: ReadableSpan) -> tuple[List[ReadableSpan], int]:
195
184
  if not span.context:
196
185
  return [], 0
197
186
 
198
187
  children = []
199
188
  dropped_spans = 0
200
- bfs_queue = deque() # type: Deque[int]
189
+ bfs_queue: Deque[int] = deque()
201
190
  bfs_queue.append(span.context.span_id)
202
191
 
203
192
  while bfs_queue:
@@ -213,8 +202,7 @@ class SentrySpanProcessor(SpanProcessor):
213
202
 
214
203
  # we construct the event from scratch here
215
204
  # and not use the current Transaction class for easier refactoring
216
- def _root_span_to_transaction_event(self, span):
217
- # type: (ReadableSpan) -> Optional[Event]
205
+ def _root_span_to_transaction_event(self, span: ReadableSpan) -> Optional[Event]:
218
206
  if not span.context:
219
207
  return None
220
208
 
@@ -231,10 +219,8 @@ class SentrySpanProcessor(SpanProcessor):
231
219
  if profile_context:
232
220
  contexts["profile"] = profile_context
233
221
 
234
- (_, description, _, http_status, _) = span_data
235
-
236
- if http_status:
237
- contexts["response"] = {"status_code": http_status}
222
+ if span_data.http_status:
223
+ contexts["response"] = {"status_code": span_data.http_status}
238
224
 
239
225
  if span.resource.attributes:
240
226
  contexts[OTEL_SENTRY_CONTEXT] = {"resource": dict(span.resource.attributes)}
@@ -242,30 +228,26 @@ class SentrySpanProcessor(SpanProcessor):
242
228
  event.update(
243
229
  {
244
230
  "type": "transaction",
245
- "transaction": transaction_name or description,
231
+ "transaction": transaction_name or span_data.description,
246
232
  "transaction_info": {"source": transaction_source or "custom"},
247
233
  "contexts": contexts,
248
234
  }
249
235
  )
250
236
 
251
- profile = cast("Optional[Profile]", get_sentry_meta(span, "profile"))
252
- if profile:
237
+ profile = get_sentry_meta(span, "profile")
238
+ if profile is not None and isinstance(profile, Profile):
253
239
  profile.__exit__(None, None, None)
254
240
  if profile.valid():
255
241
  event["profile"] = profile
256
- set_sentry_meta(span, "profile", None)
257
242
 
258
243
  return event
259
244
 
260
- def _span_to_json(self, span):
261
- # type: (ReadableSpan) -> Optional[dict[str, Any]]
245
+ def _span_to_json(self, span: ReadableSpan) -> Optional[dict[str, Any]]:
262
246
  if not span.context:
263
247
  return None
264
248
 
265
- # This is a safe cast because dict[str, Any] is a superset of Event
266
- span_json = cast(
267
- "dict[str, Any]", self._common_span_transaction_attributes_as_json(span)
268
- )
249
+ # need to ignore the type here due to TypedDict nonsense
250
+ span_json: Optional[dict[str, Any]] = self._common_span_transaction_attributes_as_json(span) # type: ignore
269
251
  if span_json is None:
270
252
  return None
271
253
 
@@ -273,19 +255,21 @@ class SentrySpanProcessor(SpanProcessor):
273
255
  span_id = format_span_id(span.context.span_id)
274
256
  parent_span_id = format_span_id(span.parent.span_id) if span.parent else None
275
257
 
276
- (op, description, status, _, origin) = extract_span_data(span)
258
+ span_data = extract_span_data(span)
277
259
 
278
260
  span_json.update(
279
261
  {
280
262
  "trace_id": trace_id,
281
263
  "span_id": span_id,
282
- "op": op,
283
- "description": description,
284
- "status": status,
285
- "origin": origin or DEFAULT_SPAN_ORIGIN,
264
+ "description": span_data.description,
265
+ "origin": span_data.origin or DEFAULT_SPAN_ORIGIN,
286
266
  }
287
267
  )
288
268
 
269
+ if span_data.op:
270
+ span_json["op"] = span_data.op
271
+ if span_data.status:
272
+ span_json["status"] = span_data.status
289
273
  if parent_span_id:
290
274
  span_json["parent_span_id"] = parent_span_id
291
275
 
@@ -298,24 +282,30 @@ class SentrySpanProcessor(SpanProcessor):
298
282
 
299
283
  return span_json
300
284
 
301
- def _common_span_transaction_attributes_as_json(self, span):
302
- # type: (ReadableSpan) -> Optional[Event]
285
+ def _common_span_transaction_attributes_as_json(
286
+ self, span: ReadableSpan
287
+ ) -> Optional[Event]:
303
288
  if not span.start_time or not span.end_time:
304
289
  return None
305
290
 
306
- common_json = {
291
+ common_json: Event = {
307
292
  "start_timestamp": convert_from_otel_timestamp(span.start_time),
308
293
  "timestamp": convert_from_otel_timestamp(span.end_time),
309
- } # type: Event
294
+ }
310
295
 
311
296
  tags = extract_span_attributes(span, SentrySpanAttribute.TAG)
312
297
  if tags:
313
- common_json["tags"] = tags
298
+ common_json["tags"] = {
299
+ tag: safe_str(tag_value) for tag, tag_value in tags.items()
300
+ }
314
301
 
315
302
  return common_json
316
303
 
317
- def _log_debug_info(self):
318
- # type: () -> None
304
+ def _cleanup_references(self, spans: List[ReadableSpan]) -> None:
305
+ for span in spans:
306
+ delete_sentry_meta(span)
307
+
308
+ def _log_debug_info(self) -> None:
319
309
  import pprint
320
310
 
321
311
  pprint.pprint(