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
sentry_sdk/__init__.py CHANGED
@@ -22,6 +22,7 @@ __all__ = [ # noqa
22
22
  "capture_exception",
23
23
  "capture_message",
24
24
  "continue_trace",
25
+ "new_trace",
25
26
  "flush",
26
27
  "get_baggage",
27
28
  "get_client",
@@ -45,6 +46,9 @@ __all__ = [ # noqa
45
46
  "trace",
46
47
  "monitor",
47
48
  "logger",
49
+ "start_session",
50
+ "end_session",
51
+ "set_transaction_name",
48
52
  ]
49
53
 
50
54
  # Initialize the debug support after everything is loaded
sentry_sdk/_compat.py CHANGED
@@ -1,12 +1,9 @@
1
+ from __future__ import annotations
1
2
  import sys
2
-
3
3
  from typing import TYPE_CHECKING
4
4
 
5
5
  if TYPE_CHECKING:
6
6
  from typing import Any
7
- from typing import TypeVar
8
-
9
- T = TypeVar("T")
10
7
 
11
8
 
12
9
  PY38 = sys.version_info[0] == 3 and sys.version_info[1] >= 8
@@ -14,18 +11,15 @@ PY310 = sys.version_info[0] == 3 and sys.version_info[1] >= 10
14
11
  PY311 = sys.version_info[0] == 3 and sys.version_info[1] >= 11
15
12
 
16
13
 
17
- def with_metaclass(meta, *bases):
18
- # type: (Any, *Any) -> Any
14
+ def with_metaclass(meta: Any, *bases: Any) -> Any:
19
15
  class MetaClass(type):
20
- def __new__(metacls, name, this_bases, d):
21
- # type: (Any, Any, Any, Any) -> Any
16
+ def __new__(metacls: Any, name: Any, this_bases: Any, d: Any) -> Any:
22
17
  return meta(name, bases, d)
23
18
 
24
19
  return type.__new__(MetaClass, "temporary_class", (), {})
25
20
 
26
21
 
27
- def check_uwsgi_thread_support():
28
- # type: () -> bool
22
+ def check_uwsgi_thread_support() -> bool:
29
23
  # We check two things here:
30
24
  #
31
25
  # 1. uWSGI doesn't run in threaded mode by default -- issue a warning if
@@ -45,8 +39,7 @@ def check_uwsgi_thread_support():
45
39
 
46
40
  from sentry_sdk.consts import FALSE_VALUES
47
41
 
48
- def enabled(option):
49
- # type: (str) -> bool
42
+ def enabled(option: str) -> bool:
50
43
  value = opt.get(option, False)
51
44
  if isinstance(value, bool):
52
45
  return value
@@ -1,23 +1,23 @@
1
+ from __future__ import annotations
2
+
1
3
  from typing import TYPE_CHECKING
2
4
 
5
+ if TYPE_CHECKING:
6
+ from typing import Optional, Any
7
+
3
8
  import sentry_sdk
4
9
  from sentry_sdk.consts import ClientConstructor
5
10
  from sentry_sdk.opentelemetry.scope import setup_scope_context_management
6
11
 
7
- if TYPE_CHECKING:
8
- from typing import Any, Optional
9
-
10
12
 
11
- def _check_python_deprecations():
12
- # type: () -> None
13
+ def _check_python_deprecations() -> None:
13
14
  # Since we're likely to deprecate Python versions in the future, I'm keeping
14
15
  # this handy function around. Use this to detect the Python version used and
15
16
  # to output logger.warning()s if it's deprecated.
16
17
  pass
17
18
 
18
19
 
19
- def _init(*args, **kwargs):
20
- # type: (*Optional[str], **Any) -> None
20
+ def _init(*args: Optional[str], **kwargs: Any) -> None:
21
21
  """Initializes the SDK and optionally integrations.
22
22
 
23
23
  This takes the same arguments as the client constructor.
@@ -1,37 +1,35 @@
1
+ from __future__ import annotations
1
2
  import os
2
3
  import random
3
4
  import threading
4
5
  from datetime import datetime, timezone
5
- from typing import Optional, List, Callable, TYPE_CHECKING, Any
6
6
 
7
7
  from sentry_sdk.utils import format_timestamp, safe_repr
8
8
  from sentry_sdk.envelope import Envelope, Item, PayloadRef
9
9
 
10
+ from typing import TYPE_CHECKING
11
+
10
12
  if TYPE_CHECKING:
11
13
  from sentry_sdk._types import Log
14
+ from typing import Optional, List, Callable, Any
12
15
 
13
16
 
14
17
  class LogBatcher:
15
18
  MAX_LOGS_BEFORE_FLUSH = 100
16
19
  FLUSH_WAIT_TIME = 5.0
17
20
 
18
- def __init__(
19
- self,
20
- capture_func, # type: Callable[[Envelope], None]
21
- ):
22
- # type: (...) -> None
23
- self._log_buffer = [] # type: List[Log]
21
+ def __init__(self, capture_func: Callable[[Envelope], None]) -> None:
22
+ self._log_buffer: List[Log] = []
24
23
  self._capture_func = capture_func
25
24
  self._running = True
26
25
  self._lock = threading.Lock()
27
26
 
28
- self._flush_event = threading.Event() # type: threading.Event
27
+ self._flush_event = threading.Event()
29
28
 
30
- self._flusher = None # type: Optional[threading.Thread]
31
- self._flusher_pid = None # type: Optional[int]
29
+ self._flusher: Optional[threading.Thread] = None
30
+ self._flusher_pid: Optional[int] = None
32
31
 
33
- def _ensure_thread(self):
34
- # type: (...) -> bool
32
+ def _ensure_thread(self) -> bool:
35
33
  """For forking processes we might need to restart this thread.
36
34
  This ensures that our process actually has that thread running.
37
35
  """
@@ -63,18 +61,13 @@ class LogBatcher:
63
61
 
64
62
  return True
65
63
 
66
- def _flush_loop(self):
67
- # type: (...) -> None
64
+ def _flush_loop(self) -> None:
68
65
  while self._running:
69
66
  self._flush_event.wait(self.FLUSH_WAIT_TIME + random.random())
70
67
  self._flush_event.clear()
71
68
  self._flush()
72
69
 
73
- def add(
74
- self,
75
- log, # type: Log
76
- ):
77
- # type: (...) -> None
70
+ def add(self, log: Log) -> None:
78
71
  if not self._ensure_thread() or self._flusher is None:
79
72
  return None
80
73
 
@@ -83,8 +76,7 @@ class LogBatcher:
83
76
  if len(self._log_buffer) >= self.MAX_LOGS_BEFORE_FLUSH:
84
77
  self._flush_event.set()
85
78
 
86
- def kill(self):
87
- # type: (...) -> None
79
+ def kill(self) -> None:
88
80
  if self._flusher is None:
89
81
  return
90
82
 
@@ -92,15 +84,12 @@ class LogBatcher:
92
84
  self._flush_event.set()
93
85
  self._flusher = None
94
86
 
95
- def flush(self):
96
- # type: (...) -> None
87
+ def flush(self) -> None:
97
88
  self._flush()
98
89
 
99
90
  @staticmethod
100
- def _log_to_transport_format(log):
101
- # type: (Log) -> Any
102
- def format_attribute(val):
103
- # type: (int | float | str | bool) -> Any
91
+ def _log_to_transport_format(log: Log) -> Any:
92
+ def format_attribute(val: int | float | str | bool) -> Any:
104
93
  if isinstance(val, bool):
105
94
  return {"value": val, "type": "boolean"}
106
95
  if isinstance(val, int):
@@ -128,8 +117,7 @@ class LogBatcher:
128
117
 
129
118
  return res
130
119
 
131
- def _flush(self):
132
- # type: (...) -> Optional[Envelope]
120
+ def _flush(self) -> Optional[Envelope]:
133
121
 
134
122
  envelope = Envelope(
135
123
  headers={"sent_at": format_timestamp(datetime.now(timezone.utc))}
sentry_sdk/_lru_cache.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from typing import TYPE_CHECKING
2
4
 
3
5
  if TYPE_CHECKING:
@@ -8,17 +10,15 @@ _SENTINEL = object()
8
10
 
9
11
 
10
12
  class LRUCache:
11
- def __init__(self, max_size):
12
- # type: (int) -> None
13
+ def __init__(self, max_size: int) -> None:
13
14
  if max_size <= 0:
14
15
  raise AssertionError(f"invalid max_size: {max_size}")
15
16
  self.max_size = max_size
16
- self._data = {} # type: dict[Any, Any]
17
+ self._data: dict[Any, Any] = {}
17
18
  self.hits = self.misses = 0
18
19
  self.full = False
19
20
 
20
- def set(self, key, value):
21
- # type: (Any, Any) -> None
21
+ def set(self, key: Any, value: Any) -> None:
22
22
  current = self._data.pop(key, _SENTINEL)
23
23
  if current is not _SENTINEL:
24
24
  self._data[key] = value
@@ -29,8 +29,7 @@ class LRUCache:
29
29
  self._data[key] = value
30
30
  self.full = len(self._data) >= self.max_size
31
31
 
32
- def get(self, key, default=None):
33
- # type: (Any, Any) -> Any
32
+ def get(self, key: Any, default: Any = None) -> Any:
34
33
  try:
35
34
  ret = self._data.pop(key)
36
35
  except KeyError:
@@ -42,6 +41,5 @@ class LRUCache:
42
41
 
43
42
  return ret
44
43
 
45
- def get_all(self):
46
- # type: () -> list[tuple[Any, Any]]
44
+ def get_all(self) -> list[tuple[Any, Any]]:
47
45
  return list(self._data.items())
sentry_sdk/_queue.py CHANGED
@@ -10,9 +10,6 @@ https://codewithoutrules.com/2017/08/16/concurrency-python/
10
10
  https://bugs.python.org/issue14976
11
11
  https://github.com/sqlalchemy/sqlalchemy/blob/4eb747b61f0c1b1c25bdee3856d7195d10a0c227/lib/sqlalchemy/queue.py#L1
12
12
 
13
- We also vendor the code to evade eventlet's broken monkeypatching, see
14
- https://github.com/getsentry/sentry-python/pull/484
15
-
16
13
 
17
14
  Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
18
15
  2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Python Software Foundation;
@@ -81,6 +78,7 @@ from typing import TYPE_CHECKING
81
78
  if TYPE_CHECKING:
82
79
  from typing import Any
83
80
 
81
+
84
82
  __all__ = ["EmptyError", "FullError", "Queue"]
85
83
 
86
84
 
@@ -275,7 +273,7 @@ class Queue:
275
273
 
276
274
  # Initialize the queue representation
277
275
  def _init(self, maxsize):
278
- self.queue = deque() # type: Any
276
+ self.queue: Any = deque()
279
277
 
280
278
  def _qsize(self):
281
279
  return len(self.queue)
sentry_sdk/_types.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from typing import TYPE_CHECKING, TypeVar, Union
2
4
 
3
5
 
@@ -18,32 +20,27 @@ class AnnotatedValue:
18
20
 
19
21
  __slots__ = ("value", "metadata")
20
22
 
21
- def __init__(self, value, metadata):
22
- # type: (Optional[Any], Dict[str, Any]) -> None
23
+ def __init__(self, value: Optional[Any], metadata: Dict[str, Any]) -> None:
23
24
  self.value = value
24
25
  self.metadata = metadata
25
26
 
26
- def __eq__(self, other):
27
- # type: (Any) -> bool
27
+ def __eq__(self, other: Any) -> bool:
28
28
  if not isinstance(other, AnnotatedValue):
29
29
  return False
30
30
 
31
31
  return self.value == other.value and self.metadata == other.metadata
32
32
 
33
- def __str__(self):
34
- # type: (AnnotatedValue) -> str
33
+ def __str__(self) -> str:
35
34
  return str({"value": str(self.value), "metadata": str(self.metadata)})
36
35
 
37
- def __len__(self):
38
- # type: (AnnotatedValue) -> int
36
+ def __len__(self) -> int:
39
37
  if self.value is not None:
40
38
  return len(self.value)
41
39
  else:
42
40
  return 0
43
41
 
44
42
  @classmethod
45
- def removed_because_raw_data(cls):
46
- # type: () -> AnnotatedValue
43
+ def removed_because_raw_data(cls) -> AnnotatedValue:
47
44
  """The value was removed because it could not be parsed. This is done for request body values that are not json nor a form."""
48
45
  return AnnotatedValue(
49
46
  value="",
@@ -58,8 +55,7 @@ class AnnotatedValue:
58
55
  )
59
56
 
60
57
  @classmethod
61
- def removed_because_over_size_limit(cls, value=""):
62
- # type: (Any) -> AnnotatedValue
58
+ def removed_because_over_size_limit(cls, value: Any = "") -> AnnotatedValue:
63
59
  """
64
60
  The actual value was removed because the size of the field exceeded the configured maximum size,
65
61
  for example specified with the max_request_body_size sdk option.
@@ -77,8 +73,7 @@ class AnnotatedValue:
77
73
  )
78
74
 
79
75
  @classmethod
80
- def substituted_because_contains_sensitive_data(cls):
81
- # type: () -> AnnotatedValue
76
+ def substituted_because_contains_sensitive_data(cls) -> AnnotatedValue:
82
77
  """The actual value was removed because it contained sensitive information."""
83
78
  return AnnotatedValue(
84
79
  value=SENSITIVE_DATA_SUBSTITUTE,
@@ -267,5 +262,3 @@ if TYPE_CHECKING:
267
262
  )
268
263
 
269
264
  HttpStatusCodeRange = Union[int, Container[int]]
270
-
271
- OtelExtractedSpanData = tuple[str, str, Optional[str], Optional[int], Optional[str]]
sentry_sdk/_werkzeug.py CHANGED
@@ -32,12 +32,12 @@ THIS SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF
32
32
  SUCH DAMAGE.
33
33
  """
34
34
 
35
+ from __future__ import annotations
36
+
35
37
  from typing import TYPE_CHECKING
36
38
 
37
39
  if TYPE_CHECKING:
38
- from typing import Dict
39
- from typing import Iterator
40
- from typing import Tuple
40
+ from typing import Dict, Iterator, Tuple
41
41
 
42
42
 
43
43
  #
@@ -47,8 +47,7 @@ if TYPE_CHECKING:
47
47
  # We need this function because Django does not give us a "pure" http header
48
48
  # dict. So we might as well use it for all WSGI integrations.
49
49
  #
50
- def _get_headers(environ):
51
- # type: (Dict[str, str]) -> Iterator[Tuple[str, str]]
50
+ def _get_headers(environ: Dict[str, str]) -> Iterator[Tuple[str, str]]:
52
51
  """
53
52
  Returns only proper HTTP headers.
54
53
  """
@@ -67,8 +66,7 @@ def _get_headers(environ):
67
66
  # `get_host` comes from `werkzeug.wsgi.get_host`
68
67
  # https://github.com/pallets/werkzeug/blob/1.0.1/src/werkzeug/wsgi.py#L145
69
68
  #
70
- def get_host(environ, use_x_forwarded_for=False):
71
- # type: (Dict[str, str], bool) -> str
69
+ def get_host(environ: Dict[str, str], use_x_forwarded_for: bool = False) -> str:
72
70
  """
73
71
  Return the host for the given WSGI environment.
74
72
  """
@@ -1,3 +1,4 @@
1
+ from __future__ import annotations
1
2
  import inspect
2
3
  from functools import wraps
3
4
 
@@ -15,34 +16,29 @@ if TYPE_CHECKING:
15
16
  _ai_pipeline_name = ContextVar("ai_pipeline_name", default=None)
16
17
 
17
18
 
18
- def set_ai_pipeline_name(name):
19
- # type: (Optional[str]) -> None
19
+ def set_ai_pipeline_name(name: Optional[str]) -> None:
20
20
  _ai_pipeline_name.set(name)
21
21
 
22
22
 
23
- def get_ai_pipeline_name():
24
- # type: () -> Optional[str]
23
+ def get_ai_pipeline_name() -> Optional[str]:
25
24
  return _ai_pipeline_name.get()
26
25
 
27
26
 
28
- def ai_track(description, **span_kwargs):
29
- # type: (str, Any) -> Callable[..., Any]
30
- def decorator(f):
31
- # type: (Callable[..., Any]) -> Callable[..., Any]
32
- def sync_wrapped(*args, **kwargs):
33
- # type: (Any, Any) -> Any
27
+ def ai_track(description: str, **span_kwargs: Any) -> Callable[..., Any]:
28
+ def decorator(f: Callable[..., Any]) -> Callable[..., Any]:
29
+ def sync_wrapped(*args: Any, **kwargs: Any) -> Any:
34
30
  curr_pipeline = _ai_pipeline_name.get()
35
- op = span_kwargs.get("op", "ai.run" if curr_pipeline else "ai.pipeline")
31
+ op = span_kwargs.pop("op", "ai.run" if curr_pipeline else "ai.pipeline")
36
32
 
37
33
  with start_span(
38
- name=description, op=op, only_if_parent=True, **span_kwargs
34
+ name=description, op=op, only_as_child_span=True, **span_kwargs
39
35
  ) as span:
40
36
  for k, v in kwargs.pop("sentry_tags", {}).items():
41
37
  span.set_tag(k, v)
42
38
  for k, v in kwargs.pop("sentry_data", {}).items():
43
39
  span.set_attribute(k, v)
44
40
  if curr_pipeline:
45
- span.set_attribute(SPANDATA.AI_PIPELINE_NAME, curr_pipeline)
41
+ span.set_attribute(SPANDATA.GEN_AI_PIPELINE_NAME, curr_pipeline)
46
42
  return f(*args, **kwargs)
47
43
  else:
48
44
  _ai_pipeline_name.set(description)
@@ -60,20 +56,19 @@ def ai_track(description, **span_kwargs):
60
56
  _ai_pipeline_name.set(None)
61
57
  return res
62
58
 
63
- async def async_wrapped(*args, **kwargs):
64
- # type: (Any, Any) -> Any
59
+ async def async_wrapped(*args: Any, **kwargs: Any) -> Any:
65
60
  curr_pipeline = _ai_pipeline_name.get()
66
- op = span_kwargs.get("op", "ai.run" if curr_pipeline else "ai.pipeline")
61
+ op = span_kwargs.pop("op", "ai.run" if curr_pipeline else "ai.pipeline")
67
62
 
68
63
  with start_span(
69
- name=description, op=op, only_if_parent=True, **span_kwargs
64
+ name=description, op=op, only_as_child_span=True, **span_kwargs
70
65
  ) as span:
71
66
  for k, v in kwargs.pop("sentry_tags", {}).items():
72
67
  span.set_tag(k, v)
73
68
  for k, v in kwargs.pop("sentry_data", {}).items():
74
69
  span.set_attribute(k, v)
75
70
  if curr_pipeline:
76
- span.set_attribute(SPANDATA.AI_PIPELINE_NAME, curr_pipeline)
71
+ span.set_attribute(SPANDATA.GEN_AI_PIPELINE_NAME, curr_pipeline)
77
72
  return await f(*args, **kwargs)
78
73
  else:
79
74
  _ai_pipeline_name.set(description)
@@ -100,21 +95,38 @@ def ai_track(description, **span_kwargs):
100
95
 
101
96
 
102
97
  def record_token_usage(
103
- span, prompt_tokens=None, completion_tokens=None, total_tokens=None
104
- ):
105
- # type: (Span, Optional[int], Optional[int], Optional[int]) -> None
98
+ span: Span,
99
+ input_tokens: Optional[int] = None,
100
+ input_tokens_cached: Optional[int] = None,
101
+ output_tokens: Optional[int] = None,
102
+ output_tokens_reasoning: Optional[int] = None,
103
+ total_tokens: Optional[int] = None,
104
+ ) -> None:
105
+ # TODO: move pipeline name elsewhere
106
106
  ai_pipeline_name = get_ai_pipeline_name()
107
107
  if ai_pipeline_name:
108
- span.set_attribute(SPANDATA.AI_PIPELINE_NAME, ai_pipeline_name)
109
- if prompt_tokens is not None:
110
- span.set_attribute(SPANDATA.AI_PROMPT_TOKENS_USED, prompt_tokens)
111
- if completion_tokens is not None:
112
- span.set_attribute(SPANDATA.AI_COMPLETION_TOKENS_USED, completion_tokens)
113
- if (
114
- total_tokens is None
115
- and prompt_tokens is not None
116
- and completion_tokens is not None
117
- ):
118
- total_tokens = prompt_tokens + completion_tokens
108
+ span.set_attribute(SPANDATA.GEN_AI_PIPELINE_NAME, ai_pipeline_name)
109
+
110
+ if input_tokens is not None:
111
+ span.set_attribute(SPANDATA.GEN_AI_USAGE_INPUT_TOKENS, input_tokens)
112
+
113
+ if input_tokens_cached is not None:
114
+ span.set_attribute(
115
+ SPANDATA.GEN_AI_USAGE_INPUT_TOKENS_CACHED,
116
+ input_tokens_cached,
117
+ )
118
+
119
+ if output_tokens is not None:
120
+ span.set_attribute(SPANDATA.GEN_AI_USAGE_OUTPUT_TOKENS, output_tokens)
121
+
122
+ if output_tokens_reasoning is not None:
123
+ span.set_attribute(
124
+ SPANDATA.GEN_AI_USAGE_OUTPUT_TOKENS_REASONING,
125
+ output_tokens_reasoning,
126
+ )
127
+
128
+ if total_tokens is None and input_tokens is not None and output_tokens is not None:
129
+ total_tokens = input_tokens + output_tokens
130
+
119
131
  if total_tokens is not None:
120
- span.set_attribute(SPANDATA.AI_TOTAL_TOKENS_USED, total_tokens)
132
+ span.set_attribute(SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS, total_tokens)
sentry_sdk/ai/utils.py CHANGED
@@ -1,3 +1,4 @@
1
+ from __future__ import annotations
1
2
  from typing import TYPE_CHECKING
2
3
 
3
4
  if TYPE_CHECKING:
@@ -7,8 +8,7 @@ from sentry_sdk.tracing import Span
7
8
  from sentry_sdk.utils import logger
8
9
 
9
10
 
10
- def _normalize_data(data):
11
- # type: (Any) -> Any
11
+ def _normalize_data(data: Any) -> Any:
12
12
 
13
13
  # convert pydantic data (e.g. OpenAI v1+) to json compatible format
14
14
  if hasattr(data, "model_dump"):
@@ -23,10 +23,13 @@ def _normalize_data(data):
23
23
  return list(_normalize_data(x) for x in data)
24
24
  if isinstance(data, dict):
25
25
  return {k: _normalize_data(v) for (k, v) in data.items()}
26
+
26
27
  return data
27
28
 
28
29
 
29
- def set_data_normalized(span, key, value):
30
- # type: (Span, str, Any) -> None
30
+ def set_data_normalized(span: Span, key: str, value: Any) -> None:
31
31
  normalized = _normalize_data(value)
32
- span.set_attribute(key, normalized)
32
+ if isinstance(normalized, (int, float, bool, str)):
33
+ span.set_attribute(key, normalized)
34
+ else:
35
+ span.set_attribute(key, str(normalized))