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