sentry-sdk 0.7.5__py2.py3-none-any.whl → 2.46.0__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.
Files changed (193) hide show
  1. sentry_sdk/__init__.py +48 -30
  2. sentry_sdk/_compat.py +74 -61
  3. sentry_sdk/_init_implementation.py +84 -0
  4. sentry_sdk/_log_batcher.py +172 -0
  5. sentry_sdk/_lru_cache.py +47 -0
  6. sentry_sdk/_metrics_batcher.py +167 -0
  7. sentry_sdk/_queue.py +289 -0
  8. sentry_sdk/_types.py +338 -0
  9. sentry_sdk/_werkzeug.py +98 -0
  10. sentry_sdk/ai/__init__.py +7 -0
  11. sentry_sdk/ai/monitoring.py +137 -0
  12. sentry_sdk/ai/utils.py +144 -0
  13. sentry_sdk/api.py +496 -80
  14. sentry_sdk/attachments.py +75 -0
  15. sentry_sdk/client.py +1023 -103
  16. sentry_sdk/consts.py +1438 -66
  17. sentry_sdk/crons/__init__.py +10 -0
  18. sentry_sdk/crons/api.py +62 -0
  19. sentry_sdk/crons/consts.py +4 -0
  20. sentry_sdk/crons/decorator.py +135 -0
  21. sentry_sdk/debug.py +15 -14
  22. sentry_sdk/envelope.py +369 -0
  23. sentry_sdk/feature_flags.py +71 -0
  24. sentry_sdk/hub.py +611 -280
  25. sentry_sdk/integrations/__init__.py +276 -49
  26. sentry_sdk/integrations/_asgi_common.py +108 -0
  27. sentry_sdk/integrations/_wsgi_common.py +180 -44
  28. sentry_sdk/integrations/aiohttp.py +291 -42
  29. sentry_sdk/integrations/anthropic.py +439 -0
  30. sentry_sdk/integrations/argv.py +9 -8
  31. sentry_sdk/integrations/ariadne.py +161 -0
  32. sentry_sdk/integrations/arq.py +247 -0
  33. sentry_sdk/integrations/asgi.py +341 -0
  34. sentry_sdk/integrations/asyncio.py +144 -0
  35. sentry_sdk/integrations/asyncpg.py +208 -0
  36. sentry_sdk/integrations/atexit.py +17 -10
  37. sentry_sdk/integrations/aws_lambda.py +377 -62
  38. sentry_sdk/integrations/beam.py +176 -0
  39. sentry_sdk/integrations/boto3.py +137 -0
  40. sentry_sdk/integrations/bottle.py +221 -0
  41. sentry_sdk/integrations/celery/__init__.py +529 -0
  42. sentry_sdk/integrations/celery/beat.py +293 -0
  43. sentry_sdk/integrations/celery/utils.py +43 -0
  44. sentry_sdk/integrations/chalice.py +134 -0
  45. sentry_sdk/integrations/clickhouse_driver.py +177 -0
  46. sentry_sdk/integrations/cloud_resource_context.py +280 -0
  47. sentry_sdk/integrations/cohere.py +274 -0
  48. sentry_sdk/integrations/dedupe.py +48 -14
  49. sentry_sdk/integrations/django/__init__.py +584 -191
  50. sentry_sdk/integrations/django/asgi.py +245 -0
  51. sentry_sdk/integrations/django/caching.py +204 -0
  52. sentry_sdk/integrations/django/middleware.py +187 -0
  53. sentry_sdk/integrations/django/signals_handlers.py +91 -0
  54. sentry_sdk/integrations/django/templates.py +79 -5
  55. sentry_sdk/integrations/django/transactions.py +49 -22
  56. sentry_sdk/integrations/django/views.py +96 -0
  57. sentry_sdk/integrations/dramatiq.py +226 -0
  58. sentry_sdk/integrations/excepthook.py +50 -13
  59. sentry_sdk/integrations/executing.py +67 -0
  60. sentry_sdk/integrations/falcon.py +272 -0
  61. sentry_sdk/integrations/fastapi.py +141 -0
  62. sentry_sdk/integrations/flask.py +142 -88
  63. sentry_sdk/integrations/gcp.py +239 -0
  64. sentry_sdk/integrations/gnu_backtrace.py +99 -0
  65. sentry_sdk/integrations/google_genai/__init__.py +301 -0
  66. sentry_sdk/integrations/google_genai/consts.py +16 -0
  67. sentry_sdk/integrations/google_genai/streaming.py +155 -0
  68. sentry_sdk/integrations/google_genai/utils.py +576 -0
  69. sentry_sdk/integrations/gql.py +162 -0
  70. sentry_sdk/integrations/graphene.py +151 -0
  71. sentry_sdk/integrations/grpc/__init__.py +168 -0
  72. sentry_sdk/integrations/grpc/aio/__init__.py +7 -0
  73. sentry_sdk/integrations/grpc/aio/client.py +95 -0
  74. sentry_sdk/integrations/grpc/aio/server.py +100 -0
  75. sentry_sdk/integrations/grpc/client.py +91 -0
  76. sentry_sdk/integrations/grpc/consts.py +1 -0
  77. sentry_sdk/integrations/grpc/server.py +66 -0
  78. sentry_sdk/integrations/httpx.py +178 -0
  79. sentry_sdk/integrations/huey.py +174 -0
  80. sentry_sdk/integrations/huggingface_hub.py +378 -0
  81. sentry_sdk/integrations/langchain.py +1132 -0
  82. sentry_sdk/integrations/langgraph.py +337 -0
  83. sentry_sdk/integrations/launchdarkly.py +61 -0
  84. sentry_sdk/integrations/litellm.py +287 -0
  85. sentry_sdk/integrations/litestar.py +315 -0
  86. sentry_sdk/integrations/logging.py +307 -96
  87. sentry_sdk/integrations/loguru.py +213 -0
  88. sentry_sdk/integrations/mcp.py +566 -0
  89. sentry_sdk/integrations/modules.py +14 -31
  90. sentry_sdk/integrations/openai.py +725 -0
  91. sentry_sdk/integrations/openai_agents/__init__.py +61 -0
  92. sentry_sdk/integrations/openai_agents/consts.py +1 -0
  93. sentry_sdk/integrations/openai_agents/patches/__init__.py +5 -0
  94. sentry_sdk/integrations/openai_agents/patches/agent_run.py +140 -0
  95. sentry_sdk/integrations/openai_agents/patches/error_tracing.py +77 -0
  96. sentry_sdk/integrations/openai_agents/patches/models.py +50 -0
  97. sentry_sdk/integrations/openai_agents/patches/runner.py +45 -0
  98. sentry_sdk/integrations/openai_agents/patches/tools.py +77 -0
  99. sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
  100. sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +21 -0
  101. sentry_sdk/integrations/openai_agents/spans/ai_client.py +42 -0
  102. sentry_sdk/integrations/openai_agents/spans/execute_tool.py +48 -0
  103. sentry_sdk/integrations/openai_agents/spans/handoff.py +19 -0
  104. sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +86 -0
  105. sentry_sdk/integrations/openai_agents/utils.py +199 -0
  106. sentry_sdk/integrations/openfeature.py +35 -0
  107. sentry_sdk/integrations/opentelemetry/__init__.py +7 -0
  108. sentry_sdk/integrations/opentelemetry/consts.py +5 -0
  109. sentry_sdk/integrations/opentelemetry/integration.py +58 -0
  110. sentry_sdk/integrations/opentelemetry/propagator.py +117 -0
  111. sentry_sdk/integrations/opentelemetry/span_processor.py +391 -0
  112. sentry_sdk/integrations/otlp.py +82 -0
  113. sentry_sdk/integrations/pure_eval.py +141 -0
  114. sentry_sdk/integrations/pydantic_ai/__init__.py +47 -0
  115. sentry_sdk/integrations/pydantic_ai/consts.py +1 -0
  116. sentry_sdk/integrations/pydantic_ai/patches/__init__.py +4 -0
  117. sentry_sdk/integrations/pydantic_ai/patches/agent_run.py +215 -0
  118. sentry_sdk/integrations/pydantic_ai/patches/graph_nodes.py +110 -0
  119. sentry_sdk/integrations/pydantic_ai/patches/model_request.py +40 -0
  120. sentry_sdk/integrations/pydantic_ai/patches/tools.py +98 -0
  121. sentry_sdk/integrations/pydantic_ai/spans/__init__.py +3 -0
  122. sentry_sdk/integrations/pydantic_ai/spans/ai_client.py +246 -0
  123. sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py +49 -0
  124. sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py +112 -0
  125. sentry_sdk/integrations/pydantic_ai/utils.py +223 -0
  126. sentry_sdk/integrations/pymongo.py +214 -0
  127. sentry_sdk/integrations/pyramid.py +112 -68
  128. sentry_sdk/integrations/quart.py +237 -0
  129. sentry_sdk/integrations/ray.py +165 -0
  130. sentry_sdk/integrations/redis/__init__.py +48 -0
  131. sentry_sdk/integrations/redis/_async_common.py +116 -0
  132. sentry_sdk/integrations/redis/_sync_common.py +119 -0
  133. sentry_sdk/integrations/redis/consts.py +19 -0
  134. sentry_sdk/integrations/redis/modules/__init__.py +0 -0
  135. sentry_sdk/integrations/redis/modules/caches.py +118 -0
  136. sentry_sdk/integrations/redis/modules/queries.py +65 -0
  137. sentry_sdk/integrations/redis/rb.py +32 -0
  138. sentry_sdk/integrations/redis/redis.py +69 -0
  139. sentry_sdk/integrations/redis/redis_cluster.py +107 -0
  140. sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +50 -0
  141. sentry_sdk/integrations/redis/utils.py +148 -0
  142. sentry_sdk/integrations/rq.py +95 -37
  143. sentry_sdk/integrations/rust_tracing.py +284 -0
  144. sentry_sdk/integrations/sanic.py +294 -123
  145. sentry_sdk/integrations/serverless.py +48 -19
  146. sentry_sdk/integrations/socket.py +96 -0
  147. sentry_sdk/integrations/spark/__init__.py +4 -0
  148. sentry_sdk/integrations/spark/spark_driver.py +316 -0
  149. sentry_sdk/integrations/spark/spark_worker.py +116 -0
  150. sentry_sdk/integrations/sqlalchemy.py +142 -0
  151. sentry_sdk/integrations/starlette.py +737 -0
  152. sentry_sdk/integrations/starlite.py +292 -0
  153. sentry_sdk/integrations/statsig.py +37 -0
  154. sentry_sdk/integrations/stdlib.py +235 -29
  155. sentry_sdk/integrations/strawberry.py +394 -0
  156. sentry_sdk/integrations/sys_exit.py +70 -0
  157. sentry_sdk/integrations/threading.py +158 -28
  158. sentry_sdk/integrations/tornado.py +84 -52
  159. sentry_sdk/integrations/trytond.py +50 -0
  160. sentry_sdk/integrations/typer.py +60 -0
  161. sentry_sdk/integrations/unleash.py +33 -0
  162. sentry_sdk/integrations/unraisablehook.py +53 -0
  163. sentry_sdk/integrations/wsgi.py +201 -119
  164. sentry_sdk/logger.py +96 -0
  165. sentry_sdk/metrics.py +81 -0
  166. sentry_sdk/monitor.py +120 -0
  167. sentry_sdk/profiler/__init__.py +49 -0
  168. sentry_sdk/profiler/continuous_profiler.py +730 -0
  169. sentry_sdk/profiler/transaction_profiler.py +839 -0
  170. sentry_sdk/profiler/utils.py +195 -0
  171. sentry_sdk/py.typed +0 -0
  172. sentry_sdk/scope.py +1713 -85
  173. sentry_sdk/scrubber.py +177 -0
  174. sentry_sdk/serializer.py +405 -0
  175. sentry_sdk/session.py +177 -0
  176. sentry_sdk/sessions.py +275 -0
  177. sentry_sdk/spotlight.py +242 -0
  178. sentry_sdk/tracing.py +1486 -0
  179. sentry_sdk/tracing_utils.py +1236 -0
  180. sentry_sdk/transport.py +806 -134
  181. sentry_sdk/types.py +52 -0
  182. sentry_sdk/utils.py +1625 -465
  183. sentry_sdk/worker.py +54 -25
  184. sentry_sdk-2.46.0.dist-info/METADATA +268 -0
  185. sentry_sdk-2.46.0.dist-info/RECORD +189 -0
  186. {sentry_sdk-0.7.5.dist-info → sentry_sdk-2.46.0.dist-info}/WHEEL +1 -1
  187. sentry_sdk-2.46.0.dist-info/entry_points.txt +2 -0
  188. sentry_sdk-2.46.0.dist-info/licenses/LICENSE +21 -0
  189. sentry_sdk/integrations/celery.py +0 -119
  190. sentry_sdk-0.7.5.dist-info/LICENSE +0 -9
  191. sentry_sdk-0.7.5.dist-info/METADATA +0 -36
  192. sentry_sdk-0.7.5.dist-info/RECORD +0 -39
  193. {sentry_sdk-0.7.5.dist-info → sentry_sdk-2.46.0.dist-info}/top_level.txt +0 -0
@@ -1,58 +1,132 @@
1
+ from contextlib import contextmanager
1
2
  import json
3
+ from copy import deepcopy
2
4
 
3
- from sentry_sdk.hub import Hub, _should_send_default_pii
4
- from sentry_sdk.utils import AnnotatedValue
5
- from sentry_sdk._compat import text_type
5
+ import sentry_sdk
6
+ from sentry_sdk.scope import should_send_default_pii
7
+ from sentry_sdk.utils import AnnotatedValue, logger
6
8
 
7
- if False:
9
+ try:
10
+ from django.http.request import RawPostDataException
11
+ except ImportError:
12
+ RawPostDataException = None
13
+
14
+ from typing import TYPE_CHECKING
15
+
16
+ if TYPE_CHECKING:
8
17
  from typing import Any
9
18
  from typing import Dict
19
+ from typing import Iterator
20
+ from typing import Mapping
21
+ from typing import MutableMapping
10
22
  from typing import Optional
11
23
  from typing import Union
24
+ from sentry_sdk._types import Event, HttpStatusCodeRange
25
+
26
+
27
+ SENSITIVE_ENV_KEYS = (
28
+ "REMOTE_ADDR",
29
+ "HTTP_X_FORWARDED_FOR",
30
+ "HTTP_SET_COOKIE",
31
+ "HTTP_COOKIE",
32
+ "HTTP_AUTHORIZATION",
33
+ "HTTP_X_API_KEY",
34
+ "HTTP_X_FORWARDED_FOR",
35
+ "HTTP_X_REAL_IP",
36
+ )
37
+
38
+ SENSITIVE_HEADERS = tuple(
39
+ x[len("HTTP_") :] for x in SENSITIVE_ENV_KEYS if x.startswith("HTTP_")
40
+ )
12
41
 
42
+ DEFAULT_HTTP_METHODS_TO_CAPTURE = (
43
+ "CONNECT",
44
+ "DELETE",
45
+ "GET",
46
+ # "HEAD", # do not capture HEAD requests by default
47
+ # "OPTIONS", # do not capture OPTIONS requests by default
48
+ "PATCH",
49
+ "POST",
50
+ "PUT",
51
+ "TRACE",
52
+ )
53
+
54
+
55
+ # This noop context manager can be replaced with "from contextlib import nullcontext" when we drop Python 3.6 support
56
+ @contextmanager
57
+ def nullcontext():
58
+ # type: () -> Iterator[None]
59
+ yield
60
+
61
+
62
+ def request_body_within_bounds(client, content_length):
63
+ # type: (Optional[sentry_sdk.client.BaseClient], int) -> bool
64
+ if client is None:
65
+ return False
66
+
67
+ bodies = client.options["max_request_body_size"]
68
+ return not (
69
+ bodies == "never"
70
+ or (bodies == "small" and content_length > 10**3)
71
+ or (bodies == "medium" and content_length > 10**4)
72
+ )
73
+
74
+
75
+ class RequestExtractor:
76
+ """
77
+ Base class for request extraction.
78
+ """
79
+
80
+ # It does not make sense to make this class an ABC because it is not used
81
+ # for typing, only so that child classes can inherit common methods from
82
+ # it. Only some child classes implement all methods that raise
83
+ # NotImplementedError in this class.
13
84
 
14
- class RequestExtractor(object):
15
85
  def __init__(self, request):
16
86
  # type: (Any) -> None
17
87
  self.request = request
18
88
 
19
89
  def extract_into_event(self, event):
20
- # type: (Dict[str, Any]) -> None
21
- client = Hub.current.client
22
- if client is None:
90
+ # type: (Event) -> None
91
+ client = sentry_sdk.get_client()
92
+ if not client.is_active():
23
93
  return
24
94
 
25
95
  data = None # type: Optional[Union[AnnotatedValue, Dict[str, Any]]]
26
96
 
27
97
  content_length = self.content_length()
28
- request_info = event.setdefault("request", {})
98
+ request_info = event.get("request", {})
29
99
 
30
- if _should_send_default_pii():
100
+ if should_send_default_pii():
31
101
  request_info["cookies"] = dict(self.cookies())
32
102
 
33
- bodies = client.options["request_bodies"]
34
- if (
35
- bodies == "never"
36
- or (bodies == "small" and content_length > 10 ** 3)
37
- or (bodies == "medium" and content_length > 10 ** 4)
38
- ):
39
- data = AnnotatedValue(
40
- "",
41
- {"rem": [["!config", "x", 0, content_length]], "len": content_length},
42
- )
103
+ if not request_body_within_bounds(client, content_length):
104
+ data = AnnotatedValue.removed_because_over_size_limit()
43
105
  else:
106
+ # First read the raw body data
107
+ # It is important to read this first because if it is Django
108
+ # it will cache the body and then we can read the cached version
109
+ # again in parsed_body() (or json() or wherever).
110
+ raw_data = None
111
+ try:
112
+ raw_data = self.raw_data()
113
+ except (RawPostDataException, ValueError):
114
+ # If DjangoRestFramework is used it already read the body for us
115
+ # so reading it here will fail. We can ignore this.
116
+ pass
117
+
44
118
  parsed_body = self.parsed_body()
45
- if parsed_body:
119
+ if parsed_body is not None:
46
120
  data = parsed_body
47
- elif self.raw_data():
48
- data = AnnotatedValue(
49
- "",
50
- {"rem": [["!raw", "x", 0, content_length]], "len": content_length},
51
- )
121
+ elif raw_data:
122
+ data = AnnotatedValue.removed_because_raw_data()
52
123
  else:
53
- return
124
+ data = None
54
125
 
55
- request_info["data"] = data
126
+ if data is not None:
127
+ request_info["data"] = data
128
+
129
+ event["request"] = deepcopy(request_info)
56
130
 
57
131
  def content_length(self):
58
132
  # type: () -> int
@@ -62,25 +136,35 @@ class RequestExtractor(object):
62
136
  return 0
63
137
 
64
138
  def cookies(self):
139
+ # type: () -> MutableMapping[str, Any]
65
140
  raise NotImplementedError()
66
141
 
67
142
  def raw_data(self):
143
+ # type: () -> Optional[Union[str, bytes]]
68
144
  raise NotImplementedError()
69
145
 
70
146
  def form(self):
147
+ # type: () -> Optional[Dict[str, Any]]
71
148
  raise NotImplementedError()
72
149
 
73
150
  def parsed_body(self):
74
151
  # type: () -> Optional[Dict[str, Any]]
75
- form = self.form()
76
- files = self.files()
152
+ try:
153
+ form = self.form()
154
+ except Exception:
155
+ form = None
156
+ try:
157
+ files = self.files()
158
+ except Exception:
159
+ files = None
160
+
77
161
  if form or files:
78
- data = dict(form.items())
79
- for k, v in files.items():
80
- size = self.size_of_file(v)
81
- data[k] = AnnotatedValue(
82
- "", {"len": size, "rem": [["!raw", "x", 0, size]]}
83
- )
162
+ data = {}
163
+ if form:
164
+ data = dict(form.items())
165
+ if files:
166
+ for key in files.keys():
167
+ data[key] = AnnotatedValue.removed_because_raw_data()
84
168
 
85
169
  return data
86
170
 
@@ -93,28 +177,43 @@ class RequestExtractor(object):
93
177
  def json(self):
94
178
  # type: () -> Optional[Any]
95
179
  try:
96
- if self.is_json():
180
+ if not self.is_json():
181
+ return None
182
+
183
+ try:
97
184
  raw_data = self.raw_data()
98
- if not isinstance(raw_data, text_type):
99
- raw_data = raw_data.decode("utf-8")
185
+ except (RawPostDataException, ValueError):
186
+ # The body might have already been read, in which case this will
187
+ # fail
188
+ raw_data = None
189
+
190
+ if raw_data is None:
191
+ return None
192
+
193
+ if isinstance(raw_data, str):
100
194
  return json.loads(raw_data)
195
+ else:
196
+ return json.loads(raw_data.decode("utf-8"))
101
197
  except ValueError:
102
198
  pass
103
199
 
104
200
  return None
105
201
 
106
202
  def files(self):
203
+ # type: () -> Optional[Dict[str, Any]]
107
204
  raise NotImplementedError()
108
205
 
109
206
  def size_of_file(self, file):
207
+ # type: (Any) -> int
110
208
  raise NotImplementedError()
111
209
 
112
210
  def env(self):
211
+ # type: () -> Dict[str, Any]
113
212
  raise NotImplementedError()
114
213
 
115
214
 
116
215
  def _is_json_content_type(ct):
117
- # type: (str) -> bool
216
+ # type: (Optional[str]) -> bool
118
217
  mt = (ct or "").split(";", 1)[0]
119
218
  return (
120
219
  mt == "application/json"
@@ -124,12 +223,49 @@ def _is_json_content_type(ct):
124
223
 
125
224
 
126
225
  def _filter_headers(headers):
127
- # type: (Dict[str, str]) -> Dict[str, str]
128
- if _should_send_default_pii():
226
+ # type: (Mapping[str, str]) -> Mapping[str, Union[AnnotatedValue, str]]
227
+ if should_send_default_pii():
129
228
  return headers
130
229
 
131
230
  return {
132
- k: v
231
+ k: (
232
+ v
233
+ if k.upper().replace("-", "_") not in SENSITIVE_HEADERS
234
+ else AnnotatedValue.removed_because_over_size_limit()
235
+ )
133
236
  for k, v in headers.items()
134
- if k.lower().replace("_", "-") not in ("set-cookie", "cookie", "authorization")
135
237
  }
238
+
239
+
240
+ def _in_http_status_code_range(code, code_ranges):
241
+ # type: (object, list[HttpStatusCodeRange]) -> bool
242
+ for target in code_ranges:
243
+ if isinstance(target, int):
244
+ if code == target:
245
+ return True
246
+ continue
247
+
248
+ try:
249
+ if code in target:
250
+ return True
251
+ except TypeError:
252
+ logger.warning(
253
+ "failed_request_status_codes has to be a list of integers or containers"
254
+ )
255
+
256
+ return False
257
+
258
+
259
+ class HttpCodeRangeContainer:
260
+ """
261
+ Wrapper to make it possible to use list[HttpStatusCodeRange] as a Container[int].
262
+ Used for backwards compatibility with the old `failed_request_status_codes` option.
263
+ """
264
+
265
+ def __init__(self, code_ranges):
266
+ # type: (list[HttpStatusCodeRange]) -> None
267
+ self._code_ranges = code_ranges
268
+
269
+ def __contains__(self, item):
270
+ # type: (object) -> bool
271
+ return _in_http_status_code_range(item, self._code_ranges)