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.
- sentry_sdk/__init__.py +2 -0
- sentry_sdk/_compat.py +5 -12
- sentry_sdk/_init_implementation.py +7 -7
- sentry_sdk/_log_batcher.py +17 -29
- sentry_sdk/_lru_cache.py +7 -9
- sentry_sdk/_queue.py +2 -4
- sentry_sdk/_types.py +11 -18
- sentry_sdk/_werkzeug.py +5 -7
- sentry_sdk/ai/monitoring.py +44 -31
- sentry_sdk/ai/utils.py +3 -4
- sentry_sdk/api.py +75 -87
- sentry_sdk/attachments.py +10 -12
- sentry_sdk/client.py +137 -155
- sentry_sdk/consts.py +430 -174
- sentry_sdk/crons/api.py +16 -17
- sentry_sdk/crons/decorator.py +25 -27
- sentry_sdk/debug.py +4 -6
- sentry_sdk/envelope.py +46 -112
- sentry_sdk/feature_flags.py +9 -15
- sentry_sdk/integrations/__init__.py +24 -19
- sentry_sdk/integrations/_asgi_common.py +15 -18
- sentry_sdk/integrations/_wsgi_common.py +22 -33
- sentry_sdk/integrations/aiohttp.py +32 -30
- sentry_sdk/integrations/anthropic.py +42 -37
- sentry_sdk/integrations/argv.py +3 -4
- sentry_sdk/integrations/ariadne.py +16 -18
- sentry_sdk/integrations/arq.py +21 -29
- sentry_sdk/integrations/asgi.py +63 -37
- sentry_sdk/integrations/asyncio.py +14 -16
- sentry_sdk/integrations/atexit.py +6 -10
- sentry_sdk/integrations/aws_lambda.py +26 -36
- sentry_sdk/integrations/beam.py +10 -18
- sentry_sdk/integrations/boto3.py +18 -16
- sentry_sdk/integrations/bottle.py +25 -34
- sentry_sdk/integrations/celery/__init__.py +41 -61
- sentry_sdk/integrations/celery/beat.py +23 -27
- sentry_sdk/integrations/celery/utils.py +15 -17
- sentry_sdk/integrations/chalice.py +8 -10
- sentry_sdk/integrations/clickhouse_driver.py +21 -31
- sentry_sdk/integrations/cloud_resource_context.py +9 -16
- sentry_sdk/integrations/cohere.py +27 -33
- sentry_sdk/integrations/dedupe.py +5 -8
- sentry_sdk/integrations/django/__init__.py +57 -72
- sentry_sdk/integrations/django/asgi.py +26 -34
- sentry_sdk/integrations/django/caching.py +23 -19
- sentry_sdk/integrations/django/middleware.py +17 -20
- sentry_sdk/integrations/django/signals_handlers.py +11 -10
- sentry_sdk/integrations/django/templates.py +19 -16
- sentry_sdk/integrations/django/transactions.py +16 -11
- sentry_sdk/integrations/django/views.py +6 -10
- sentry_sdk/integrations/dramatiq.py +21 -21
- sentry_sdk/integrations/excepthook.py +10 -10
- sentry_sdk/integrations/executing.py +3 -4
- sentry_sdk/integrations/falcon.py +27 -42
- sentry_sdk/integrations/fastapi.py +13 -16
- sentry_sdk/integrations/flask.py +31 -38
- sentry_sdk/integrations/gcp.py +13 -16
- sentry_sdk/integrations/gnu_backtrace.py +4 -6
- sentry_sdk/integrations/gql.py +16 -17
- sentry_sdk/integrations/graphene.py +13 -12
- sentry_sdk/integrations/grpc/__init__.py +19 -1
- sentry_sdk/integrations/grpc/aio/server.py +15 -14
- sentry_sdk/integrations/grpc/client.py +19 -9
- sentry_sdk/integrations/grpc/consts.py +2 -0
- sentry_sdk/integrations/grpc/server.py +12 -8
- sentry_sdk/integrations/httpx.py +9 -12
- sentry_sdk/integrations/huey.py +13 -20
- sentry_sdk/integrations/huggingface_hub.py +18 -18
- sentry_sdk/integrations/langchain.py +203 -113
- sentry_sdk/integrations/launchdarkly.py +13 -10
- sentry_sdk/integrations/litestar.py +37 -35
- sentry_sdk/integrations/logging.py +52 -65
- sentry_sdk/integrations/loguru.py +127 -57
- sentry_sdk/integrations/modules.py +3 -4
- sentry_sdk/integrations/openai.py +100 -88
- sentry_sdk/integrations/openai_agents/__init__.py +49 -0
- sentry_sdk/integrations/openai_agents/consts.py +1 -0
- sentry_sdk/integrations/openai_agents/patches/__init__.py +4 -0
- sentry_sdk/integrations/openai_agents/patches/agent_run.py +152 -0
- sentry_sdk/integrations/openai_agents/patches/models.py +52 -0
- sentry_sdk/integrations/openai_agents/patches/runner.py +42 -0
- sentry_sdk/integrations/openai_agents/patches/tools.py +84 -0
- sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
- sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +20 -0
- sentry_sdk/integrations/openai_agents/spans/ai_client.py +46 -0
- sentry_sdk/integrations/openai_agents/spans/execute_tool.py +47 -0
- sentry_sdk/integrations/openai_agents/spans/handoff.py +24 -0
- sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +41 -0
- sentry_sdk/integrations/openai_agents/utils.py +201 -0
- sentry_sdk/integrations/openfeature.py +11 -6
- sentry_sdk/integrations/pure_eval.py +6 -10
- sentry_sdk/integrations/pymongo.py +13 -17
- sentry_sdk/integrations/pyramid.py +31 -36
- sentry_sdk/integrations/quart.py +23 -28
- sentry_sdk/integrations/ray.py +73 -64
- sentry_sdk/integrations/redis/__init__.py +7 -4
- sentry_sdk/integrations/redis/_async_common.py +25 -12
- sentry_sdk/integrations/redis/_sync_common.py +19 -13
- sentry_sdk/integrations/redis/modules/caches.py +17 -8
- sentry_sdk/integrations/redis/modules/queries.py +9 -8
- sentry_sdk/integrations/redis/rb.py +3 -2
- sentry_sdk/integrations/redis/redis.py +4 -4
- sentry_sdk/integrations/redis/redis_cluster.py +21 -13
- sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +3 -2
- sentry_sdk/integrations/redis/utils.py +23 -24
- sentry_sdk/integrations/rq.py +13 -16
- sentry_sdk/integrations/rust_tracing.py +9 -6
- sentry_sdk/integrations/sanic.py +34 -46
- sentry_sdk/integrations/serverless.py +22 -27
- sentry_sdk/integrations/socket.py +27 -15
- sentry_sdk/integrations/spark/__init__.py +1 -0
- sentry_sdk/integrations/spark/spark_driver.py +45 -83
- sentry_sdk/integrations/spark/spark_worker.py +7 -11
- sentry_sdk/integrations/sqlalchemy.py +22 -19
- sentry_sdk/integrations/starlette.py +86 -90
- sentry_sdk/integrations/starlite.py +28 -34
- sentry_sdk/integrations/statsig.py +5 -4
- sentry_sdk/integrations/stdlib.py +28 -24
- sentry_sdk/integrations/strawberry.py +62 -49
- sentry_sdk/integrations/sys_exit.py +7 -11
- sentry_sdk/integrations/threading.py +12 -14
- sentry_sdk/integrations/tornado.py +28 -32
- sentry_sdk/integrations/trytond.py +4 -3
- sentry_sdk/integrations/typer.py +8 -6
- sentry_sdk/integrations/unleash.py +5 -4
- sentry_sdk/integrations/wsgi.py +47 -46
- sentry_sdk/logger.py +41 -10
- sentry_sdk/monitor.py +16 -28
- sentry_sdk/opentelemetry/consts.py +11 -4
- sentry_sdk/opentelemetry/contextvars_context.py +26 -16
- sentry_sdk/opentelemetry/propagator.py +38 -21
- sentry_sdk/opentelemetry/sampler.py +51 -34
- sentry_sdk/opentelemetry/scope.py +36 -37
- sentry_sdk/opentelemetry/span_processor.py +48 -58
- sentry_sdk/opentelemetry/tracing.py +58 -14
- sentry_sdk/opentelemetry/utils.py +186 -194
- sentry_sdk/profiler/continuous_profiler.py +108 -97
- sentry_sdk/profiler/transaction_profiler.py +70 -97
- sentry_sdk/profiler/utils.py +11 -15
- sentry_sdk/scope.py +251 -273
- sentry_sdk/scrubber.py +22 -26
- sentry_sdk/serializer.py +40 -54
- sentry_sdk/session.py +44 -61
- sentry_sdk/sessions.py +35 -49
- sentry_sdk/spotlight.py +15 -21
- sentry_sdk/tracing.py +121 -187
- sentry_sdk/tracing_utils.py +104 -122
- sentry_sdk/transport.py +131 -157
- sentry_sdk/utils.py +232 -309
- sentry_sdk/worker.py +16 -28
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/METADATA +3 -3
- sentry_sdk-3.0.0a3.dist-info/RECORD +168 -0
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/WHEEL +1 -1
- sentry_sdk-3.0.0a1.dist-info/RECORD +0 -154
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/entry_points.txt +0 -0
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/licenses/LICENSE +0 -0
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/top_level.txt +0 -0
sentry_sdk/utils.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
1
2
|
import base64
|
|
2
3
|
import json
|
|
3
4
|
import linecache
|
|
@@ -34,21 +35,19 @@ from sentry_sdk.consts import (
|
|
|
34
35
|
)
|
|
35
36
|
from sentry_sdk._types import Annotated, AnnotatedValue, SENSITIVE_DATA_SUBSTITUTE
|
|
36
37
|
|
|
37
|
-
from typing import TYPE_CHECKING
|
|
38
|
+
from typing import TYPE_CHECKING, overload
|
|
38
39
|
|
|
39
40
|
if TYPE_CHECKING:
|
|
40
41
|
from types import FrameType, TracebackType
|
|
41
42
|
from typing import (
|
|
42
43
|
Any,
|
|
43
44
|
Callable,
|
|
44
|
-
cast,
|
|
45
45
|
ContextManager,
|
|
46
46
|
Dict,
|
|
47
47
|
Iterator,
|
|
48
48
|
List,
|
|
49
49
|
NoReturn,
|
|
50
50
|
Optional,
|
|
51
|
-
overload,
|
|
52
51
|
ParamSpec,
|
|
53
52
|
Set,
|
|
54
53
|
Tuple,
|
|
@@ -95,8 +94,7 @@ of the exception tree.
|
|
|
95
94
|
"""
|
|
96
95
|
|
|
97
96
|
|
|
98
|
-
def env_to_bool(value, *, strict=False):
|
|
99
|
-
# type: (Any, Optional[bool]) -> bool | None
|
|
97
|
+
def env_to_bool(value: Any, *, strict: Optional[bool] = False) -> Optional[bool]:
|
|
100
98
|
"""Casts an ENV variable value to boolean using the constants defined above.
|
|
101
99
|
In strict mode, it may return None if the value doesn't match any of the predefined values.
|
|
102
100
|
"""
|
|
@@ -111,14 +109,12 @@ def env_to_bool(value, *, strict=False):
|
|
|
111
109
|
return None if strict else bool(value)
|
|
112
110
|
|
|
113
111
|
|
|
114
|
-
def json_dumps(data):
|
|
115
|
-
# type: (Any) -> bytes
|
|
112
|
+
def json_dumps(data: Any) -> bytes:
|
|
116
113
|
"""Serialize data into a compact JSON representation encoded as UTF-8."""
|
|
117
114
|
return json.dumps(data, allow_nan=False, separators=(",", ":")).encode("utf-8")
|
|
118
115
|
|
|
119
116
|
|
|
120
|
-
def get_git_revision():
|
|
121
|
-
# type: () -> Optional[str]
|
|
117
|
+
def get_git_revision() -> Optional[str]:
|
|
122
118
|
try:
|
|
123
119
|
with open(os.path.devnull, "w+") as null:
|
|
124
120
|
# prevent command prompt windows from popping up on windows
|
|
@@ -145,8 +141,7 @@ def get_git_revision():
|
|
|
145
141
|
return revision
|
|
146
142
|
|
|
147
143
|
|
|
148
|
-
def get_default_release():
|
|
149
|
-
# type: () -> Optional[str]
|
|
144
|
+
def get_default_release() -> Optional[str]:
|
|
150
145
|
"""Try to guess a default release."""
|
|
151
146
|
release = os.environ.get("SENTRY_RELEASE")
|
|
152
147
|
if release:
|
|
@@ -169,8 +164,7 @@ def get_default_release():
|
|
|
169
164
|
return None
|
|
170
165
|
|
|
171
166
|
|
|
172
|
-
def get_sdk_name(installed_integrations):
|
|
173
|
-
# type: (List[str]) -> str
|
|
167
|
+
def get_sdk_name(installed_integrations: List[str]) -> str:
|
|
174
168
|
"""Return the SDK name including the name of the used web framework."""
|
|
175
169
|
|
|
176
170
|
# Note: I can not use for example sentry_sdk.integrations.django.DjangoIntegration.identifier
|
|
@@ -208,12 +202,15 @@ def get_sdk_name(installed_integrations):
|
|
|
208
202
|
class CaptureInternalException:
|
|
209
203
|
__slots__ = ()
|
|
210
204
|
|
|
211
|
-
def __enter__(self):
|
|
212
|
-
# type: () -> ContextManager[Any]
|
|
205
|
+
def __enter__(self) -> ContextManager[Any]:
|
|
213
206
|
return self
|
|
214
207
|
|
|
215
|
-
def __exit__(
|
|
216
|
-
|
|
208
|
+
def __exit__(
|
|
209
|
+
self,
|
|
210
|
+
ty: Optional[Type[BaseException]],
|
|
211
|
+
value: Optional[BaseException],
|
|
212
|
+
tb: Optional[TracebackType],
|
|
213
|
+
) -> bool:
|
|
217
214
|
if ty is not None and value is not None:
|
|
218
215
|
capture_internal_exception((ty, value, tb))
|
|
219
216
|
|
|
@@ -223,13 +220,11 @@ class CaptureInternalException:
|
|
|
223
220
|
_CAPTURE_INTERNAL_EXCEPTION = CaptureInternalException()
|
|
224
221
|
|
|
225
222
|
|
|
226
|
-
def capture_internal_exceptions():
|
|
227
|
-
# type: () -> ContextManager[Any]
|
|
223
|
+
def capture_internal_exceptions() -> ContextManager[Any]:
|
|
228
224
|
return _CAPTURE_INTERNAL_EXCEPTION
|
|
229
225
|
|
|
230
226
|
|
|
231
|
-
def capture_internal_exception(exc_info):
|
|
232
|
-
# type: (ExcInfo) -> None
|
|
227
|
+
def capture_internal_exception(exc_info: ExcInfo) -> None:
|
|
233
228
|
"""
|
|
234
229
|
Capture an exception that is likely caused by a bug in the SDK
|
|
235
230
|
itself.
|
|
@@ -240,13 +235,11 @@ def capture_internal_exception(exc_info):
|
|
|
240
235
|
logger.error("Internal error in sentry_sdk", exc_info=exc_info)
|
|
241
236
|
|
|
242
237
|
|
|
243
|
-
def to_timestamp(value):
|
|
244
|
-
# type: (datetime) -> float
|
|
238
|
+
def to_timestamp(value: datetime) -> float:
|
|
245
239
|
return (value - epoch).total_seconds()
|
|
246
240
|
|
|
247
241
|
|
|
248
|
-
def format_timestamp(value):
|
|
249
|
-
# type: (datetime) -> str
|
|
242
|
+
def format_timestamp(value: datetime) -> str:
|
|
250
243
|
"""Formats a timestamp in RFC 3339 format.
|
|
251
244
|
|
|
252
245
|
Any datetime objects with a non-UTC timezone are converted to UTC, so that all timestamps are formatted in UTC.
|
|
@@ -258,8 +251,9 @@ def format_timestamp(value):
|
|
|
258
251
|
return utctime.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
|
|
259
252
|
|
|
260
253
|
|
|
261
|
-
def event_hint_with_exc_info(
|
|
262
|
-
|
|
254
|
+
def event_hint_with_exc_info(
|
|
255
|
+
exc_info: Optional[ExcInfo] = None,
|
|
256
|
+
) -> Dict[str, Optional[ExcInfo]]:
|
|
263
257
|
"""Creates a hint with the exc info filled in."""
|
|
264
258
|
if exc_info is None:
|
|
265
259
|
exc_info = sys.exc_info()
|
|
@@ -277,8 +271,7 @@ class BadDsn(ValueError):
|
|
|
277
271
|
class Dsn:
|
|
278
272
|
"""Represents a DSN."""
|
|
279
273
|
|
|
280
|
-
def __init__(self, value):
|
|
281
|
-
# type: (Union[Dsn, str]) -> None
|
|
274
|
+
def __init__(self, value: Union[Dsn, str]) -> None:
|
|
282
275
|
if isinstance(value, Dsn):
|
|
283
276
|
self.__dict__ = dict(value.__dict__)
|
|
284
277
|
return
|
|
@@ -294,7 +287,7 @@ class Dsn:
|
|
|
294
287
|
self.host = parts.hostname
|
|
295
288
|
|
|
296
289
|
if parts.port is None:
|
|
297
|
-
self.port = self.scheme == "https" and 443 or 80
|
|
290
|
+
self.port: int = self.scheme == "https" and 443 or 80
|
|
298
291
|
else:
|
|
299
292
|
self.port = parts.port
|
|
300
293
|
|
|
@@ -314,16 +307,14 @@ class Dsn:
|
|
|
314
307
|
self.path = "/".join(path) + "/"
|
|
315
308
|
|
|
316
309
|
@property
|
|
317
|
-
def netloc(self):
|
|
318
|
-
# type: () -> str
|
|
310
|
+
def netloc(self) -> str:
|
|
319
311
|
"""The netloc part of a DSN."""
|
|
320
312
|
rv = self.host
|
|
321
313
|
if (self.scheme, self.port) not in (("http", 80), ("https", 443)):
|
|
322
314
|
rv = "%s:%s" % (rv, self.port)
|
|
323
315
|
return rv
|
|
324
316
|
|
|
325
|
-
def to_auth(self, client=None):
|
|
326
|
-
# type: (Optional[Any]) -> Auth
|
|
317
|
+
def to_auth(self, client: Optional[Any] = None) -> Auth:
|
|
327
318
|
"""Returns the auth info object for this dsn."""
|
|
328
319
|
return Auth(
|
|
329
320
|
scheme=self.scheme,
|
|
@@ -335,8 +326,7 @@ class Dsn:
|
|
|
335
326
|
client=client,
|
|
336
327
|
)
|
|
337
328
|
|
|
338
|
-
def __str__(self):
|
|
339
|
-
# type: () -> str
|
|
329
|
+
def __str__(self) -> str:
|
|
340
330
|
return "%s://%s%s@%s%s%s" % (
|
|
341
331
|
self.scheme,
|
|
342
332
|
self.public_key,
|
|
@@ -352,16 +342,15 @@ class Auth:
|
|
|
352
342
|
|
|
353
343
|
def __init__(
|
|
354
344
|
self,
|
|
355
|
-
scheme,
|
|
356
|
-
host,
|
|
357
|
-
project_id,
|
|
358
|
-
public_key,
|
|
359
|
-
secret_key=None,
|
|
360
|
-
version=7,
|
|
361
|
-
client=None,
|
|
362
|
-
path="/",
|
|
363
|
-
):
|
|
364
|
-
# type: (str, str, str, str, Optional[str], int, Optional[Any], str) -> None
|
|
345
|
+
scheme: str,
|
|
346
|
+
host: str,
|
|
347
|
+
project_id: str,
|
|
348
|
+
public_key: str,
|
|
349
|
+
secret_key: Optional[str] = None,
|
|
350
|
+
version: int = 7,
|
|
351
|
+
client: Optional[Any] = None,
|
|
352
|
+
path: str = "/",
|
|
353
|
+
) -> None:
|
|
365
354
|
self.scheme = scheme
|
|
366
355
|
self.host = host
|
|
367
356
|
self.path = path
|
|
@@ -371,10 +360,7 @@ class Auth:
|
|
|
371
360
|
self.version = version
|
|
372
361
|
self.client = client
|
|
373
362
|
|
|
374
|
-
def get_api_url(
|
|
375
|
-
self, type=EndpointType.ENVELOPE # type: EndpointType
|
|
376
|
-
):
|
|
377
|
-
# type: (...) -> str
|
|
363
|
+
def get_api_url(self, type: EndpointType = EndpointType.ENVELOPE) -> str:
|
|
378
364
|
"""Returns the API url for storing events."""
|
|
379
365
|
return "%s://%s%sapi/%s/%s/" % (
|
|
380
366
|
self.scheme,
|
|
@@ -384,8 +370,7 @@ class Auth:
|
|
|
384
370
|
type.value,
|
|
385
371
|
)
|
|
386
372
|
|
|
387
|
-
def to_header(self):
|
|
388
|
-
# type: () -> str
|
|
373
|
+
def to_header(self) -> str:
|
|
389
374
|
"""Returns the auth header a string."""
|
|
390
375
|
rv = [("sentry_key", self.public_key), ("sentry_version", self.version)]
|
|
391
376
|
if self.client is not None:
|
|
@@ -395,21 +380,18 @@ class Auth:
|
|
|
395
380
|
return "Sentry " + ", ".join("%s=%s" % (key, value) for key, value in rv)
|
|
396
381
|
|
|
397
382
|
|
|
398
|
-
def get_type_name(cls):
|
|
399
|
-
# type: (Optional[type]) -> Optional[str]
|
|
383
|
+
def get_type_name(cls: Optional[type]) -> Optional[str]:
|
|
400
384
|
return getattr(cls, "__qualname__", None) or getattr(cls, "__name__", None)
|
|
401
385
|
|
|
402
386
|
|
|
403
|
-
def get_type_module(cls):
|
|
404
|
-
# type: (Optional[type]) -> Optional[str]
|
|
387
|
+
def get_type_module(cls: Optional[type]) -> Optional[str]:
|
|
405
388
|
mod = getattr(cls, "__module__", None)
|
|
406
389
|
if mod not in (None, "builtins", "__builtins__"):
|
|
407
390
|
return mod
|
|
408
391
|
return None
|
|
409
392
|
|
|
410
393
|
|
|
411
|
-
def should_hide_frame(frame):
|
|
412
|
-
# type: (FrameType) -> bool
|
|
394
|
+
def should_hide_frame(frame: FrameType) -> bool:
|
|
413
395
|
try:
|
|
414
396
|
mod = frame.f_globals["__name__"]
|
|
415
397
|
if mod.startswith("sentry_sdk."):
|
|
@@ -427,9 +409,8 @@ def should_hide_frame(frame):
|
|
|
427
409
|
return False
|
|
428
410
|
|
|
429
411
|
|
|
430
|
-
def iter_stacks(tb):
|
|
431
|
-
|
|
432
|
-
tb_ = tb # type: Optional[TracebackType]
|
|
412
|
+
def iter_stacks(tb: Optional[TracebackType]) -> Iterator[TracebackType]:
|
|
413
|
+
tb_: Optional[TracebackType] = tb
|
|
433
414
|
while tb_ is not None:
|
|
434
415
|
if not should_hide_frame(tb_.tb_frame):
|
|
435
416
|
yield tb_
|
|
@@ -437,18 +418,17 @@ def iter_stacks(tb):
|
|
|
437
418
|
|
|
438
419
|
|
|
439
420
|
def get_lines_from_file(
|
|
440
|
-
filename
|
|
441
|
-
lineno
|
|
442
|
-
max_length
|
|
443
|
-
loader
|
|
444
|
-
module
|
|
445
|
-
):
|
|
446
|
-
# type: (...) -> Tuple[List[Annotated[str]], Optional[Annotated[str]], List[Annotated[str]]]
|
|
421
|
+
filename: str,
|
|
422
|
+
lineno: int,
|
|
423
|
+
max_length: Optional[int] = None,
|
|
424
|
+
loader: Optional[Any] = None,
|
|
425
|
+
module: Optional[str] = None,
|
|
426
|
+
) -> Tuple[List[Annotated[str]], Optional[Annotated[str]], List[Annotated[str]]]:
|
|
447
427
|
context_lines = 5
|
|
448
428
|
source = None
|
|
449
429
|
if loader is not None and hasattr(loader, "get_source"):
|
|
450
430
|
try:
|
|
451
|
-
source_str = loader.get_source(module)
|
|
431
|
+
source_str: Optional[str] = loader.get_source(module)
|
|
452
432
|
except (ImportError, IOError):
|
|
453
433
|
source_str = None
|
|
454
434
|
if source_str is not None:
|
|
@@ -483,13 +463,12 @@ def get_lines_from_file(
|
|
|
483
463
|
|
|
484
464
|
|
|
485
465
|
def get_source_context(
|
|
486
|
-
frame
|
|
487
|
-
tb_lineno
|
|
488
|
-
max_value_length
|
|
489
|
-
):
|
|
490
|
-
# type: (...) -> Tuple[List[Annotated[str]], Optional[Annotated[str]], List[Annotated[str]]]
|
|
466
|
+
frame: FrameType,
|
|
467
|
+
tb_lineno: Optional[int],
|
|
468
|
+
max_value_length: Optional[int] = None,
|
|
469
|
+
) -> Tuple[List[Annotated[str]], Optional[Annotated[str]], List[Annotated[str]]]:
|
|
491
470
|
try:
|
|
492
|
-
abs_path = frame.f_code.co_filename
|
|
471
|
+
abs_path: Optional[str] = frame.f_code.co_filename
|
|
493
472
|
except Exception:
|
|
494
473
|
abs_path = None
|
|
495
474
|
try:
|
|
@@ -510,24 +489,23 @@ def get_source_context(
|
|
|
510
489
|
return [], None, []
|
|
511
490
|
|
|
512
491
|
|
|
513
|
-
def safe_str(value):
|
|
514
|
-
# type: (Any) -> str
|
|
492
|
+
def safe_str(value: Any) -> str:
|
|
515
493
|
try:
|
|
516
494
|
return str(value)
|
|
517
495
|
except Exception:
|
|
518
496
|
return safe_repr(value)
|
|
519
497
|
|
|
520
498
|
|
|
521
|
-
def safe_repr(value):
|
|
522
|
-
# type: (Any) -> str
|
|
499
|
+
def safe_repr(value: Any) -> str:
|
|
523
500
|
try:
|
|
524
501
|
return repr(value)
|
|
525
502
|
except Exception:
|
|
526
503
|
return "<broken repr>"
|
|
527
504
|
|
|
528
505
|
|
|
529
|
-
def filename_for_module(
|
|
530
|
-
|
|
506
|
+
def filename_for_module(
|
|
507
|
+
module: Optional[str], abs_path: Optional[str]
|
|
508
|
+
) -> Optional[str]:
|
|
531
509
|
if not abs_path or not module:
|
|
532
510
|
return abs_path
|
|
533
511
|
|
|
@@ -551,14 +529,13 @@ def filename_for_module(module, abs_path):
|
|
|
551
529
|
|
|
552
530
|
|
|
553
531
|
def serialize_frame(
|
|
554
|
-
frame,
|
|
555
|
-
tb_lineno=None,
|
|
556
|
-
include_local_variables=True,
|
|
557
|
-
include_source_context=True,
|
|
558
|
-
max_value_length=None,
|
|
559
|
-
custom_repr=None,
|
|
560
|
-
):
|
|
561
|
-
# type: (FrameType, Optional[int], bool, bool, Optional[int], Optional[Callable[..., Optional[str]]]) -> Dict[str, Any]
|
|
532
|
+
frame: FrameType,
|
|
533
|
+
tb_lineno: Optional[int] = None,
|
|
534
|
+
include_local_variables: bool = True,
|
|
535
|
+
include_source_context: bool = True,
|
|
536
|
+
max_value_length: Optional[int] = None,
|
|
537
|
+
custom_repr: Optional[Callable[..., Optional[str]]] = None,
|
|
538
|
+
) -> Dict[str, Any]:
|
|
562
539
|
f_code = getattr(frame, "f_code", None)
|
|
563
540
|
if not f_code:
|
|
564
541
|
abs_path = None
|
|
@@ -574,13 +551,18 @@ def serialize_frame(
|
|
|
574
551
|
if tb_lineno is None:
|
|
575
552
|
tb_lineno = frame.f_lineno
|
|
576
553
|
|
|
577
|
-
|
|
554
|
+
try:
|
|
555
|
+
os_abs_path = os.path.abspath(abs_path) if abs_path else None
|
|
556
|
+
except Exception:
|
|
557
|
+
os_abs_path = None
|
|
558
|
+
|
|
559
|
+
rv: Dict[str, Any] = {
|
|
578
560
|
"filename": filename_for_module(module, abs_path) or None,
|
|
579
|
-
"abs_path":
|
|
561
|
+
"abs_path": os_abs_path,
|
|
580
562
|
"function": function or "<unknown>",
|
|
581
563
|
"module": module,
|
|
582
564
|
"lineno": tb_lineno,
|
|
583
|
-
}
|
|
565
|
+
}
|
|
584
566
|
|
|
585
567
|
if include_source_context:
|
|
586
568
|
rv["pre_context"], rv["context_line"], rv["post_context"] = get_source_context(
|
|
@@ -598,15 +580,14 @@ def serialize_frame(
|
|
|
598
580
|
|
|
599
581
|
|
|
600
582
|
def current_stacktrace(
|
|
601
|
-
include_local_variables=True,
|
|
602
|
-
include_source_context=True,
|
|
603
|
-
max_value_length
|
|
604
|
-
):
|
|
605
|
-
# type: (...) -> Dict[str, Any]
|
|
583
|
+
include_local_variables: bool = True,
|
|
584
|
+
include_source_context: bool = True,
|
|
585
|
+
max_value_length: Optional[int] = None,
|
|
586
|
+
) -> Dict[str, Any]:
|
|
606
587
|
__tracebackhide__ = True
|
|
607
588
|
frames = []
|
|
608
589
|
|
|
609
|
-
f = sys._getframe()
|
|
590
|
+
f: Optional[FrameType] = sys._getframe()
|
|
610
591
|
while f is not None:
|
|
611
592
|
if not should_hide_frame(f):
|
|
612
593
|
frames.append(
|
|
@@ -624,24 +605,22 @@ def current_stacktrace(
|
|
|
624
605
|
return {"frames": frames}
|
|
625
606
|
|
|
626
607
|
|
|
627
|
-
def get_errno(exc_value):
|
|
628
|
-
# type: (BaseException) -> Optional[Any]
|
|
608
|
+
def get_errno(exc_value: BaseException) -> Optional[Any]:
|
|
629
609
|
return getattr(exc_value, "errno", None)
|
|
630
610
|
|
|
631
611
|
|
|
632
|
-
def get_error_message(exc_value):
|
|
633
|
-
|
|
634
|
-
message = (
|
|
612
|
+
def get_error_message(exc_value: Optional[BaseException]) -> str:
|
|
613
|
+
message: str = (
|
|
635
614
|
getattr(exc_value, "message", "")
|
|
636
615
|
or getattr(exc_value, "detail", "")
|
|
637
616
|
or safe_str(exc_value)
|
|
638
|
-
)
|
|
617
|
+
)
|
|
639
618
|
|
|
640
619
|
# __notes__ should be a list of strings when notes are added
|
|
641
620
|
# via add_note, but can be anything else if __notes__ is set
|
|
642
621
|
# directly. We only support strings in __notes__, since that
|
|
643
622
|
# is the correct use.
|
|
644
|
-
notes = getattr(exc_value, "__notes__", None)
|
|
623
|
+
notes: object = getattr(exc_value, "__notes__", None)
|
|
645
624
|
if isinstance(notes, list) and len(notes) > 0:
|
|
646
625
|
message += "\n" + "\n".join(note for note in notes if isinstance(note, str))
|
|
647
626
|
|
|
@@ -649,24 +628,23 @@ def get_error_message(exc_value):
|
|
|
649
628
|
|
|
650
629
|
|
|
651
630
|
def single_exception_from_error_tuple(
|
|
652
|
-
exc_type
|
|
653
|
-
exc_value
|
|
654
|
-
tb
|
|
655
|
-
client_options
|
|
656
|
-
mechanism
|
|
657
|
-
exception_id
|
|
658
|
-
parent_id
|
|
659
|
-
source
|
|
660
|
-
full_stack
|
|
661
|
-
):
|
|
662
|
-
# type: (...) -> Dict[str, Any]
|
|
631
|
+
exc_type: Optional[type],
|
|
632
|
+
exc_value: Optional[BaseException],
|
|
633
|
+
tb: Optional[TracebackType],
|
|
634
|
+
client_options: Optional[Dict[str, Any]] = None,
|
|
635
|
+
mechanism: Optional[Dict[str, Any]] = None,
|
|
636
|
+
exception_id: Optional[int] = None,
|
|
637
|
+
parent_id: Optional[int] = None,
|
|
638
|
+
source: Optional[str] = None,
|
|
639
|
+
full_stack: Optional[list[dict[str, Any]]] = None,
|
|
640
|
+
) -> Dict[str, Any]:
|
|
663
641
|
"""
|
|
664
642
|
Creates a dict that goes into the events `exception.values` list and is ingestible by Sentry.
|
|
665
643
|
|
|
666
644
|
See the Exception Interface documentation for more details:
|
|
667
645
|
https://develop.sentry.dev/sdk/event-payloads/exception/
|
|
668
646
|
"""
|
|
669
|
-
exception_value
|
|
647
|
+
exception_value: Dict[str, Any] = {}
|
|
670
648
|
exception_value["mechanism"] = (
|
|
671
649
|
mechanism.copy() if mechanism else {"type": "generic", "handled": True}
|
|
672
650
|
)
|
|
@@ -715,7 +693,7 @@ def single_exception_from_error_tuple(
|
|
|
715
693
|
max_value_length = client_options["max_value_length"]
|
|
716
694
|
custom_repr = client_options.get("custom_repr")
|
|
717
695
|
|
|
718
|
-
frames = [
|
|
696
|
+
frames: List[Dict[str, Any]] = [
|
|
719
697
|
serialize_frame(
|
|
720
698
|
tb.tb_frame,
|
|
721
699
|
tb_lineno=tb.tb_lineno,
|
|
@@ -727,7 +705,7 @@ def single_exception_from_error_tuple(
|
|
|
727
705
|
# Process at most MAX_STACK_FRAMES + 1 frames, to avoid hanging on
|
|
728
706
|
# processing a super-long stacktrace.
|
|
729
707
|
for tb, _ in zip(iter_stacks(tb), range(MAX_STACK_FRAMES + 1))
|
|
730
|
-
]
|
|
708
|
+
]
|
|
731
709
|
|
|
732
710
|
if len(frames) > MAX_STACK_FRAMES:
|
|
733
711
|
# If we have more frames than the limit, we remove the stacktrace completely.
|
|
@@ -755,12 +733,11 @@ HAS_CHAINED_EXCEPTIONS = hasattr(Exception, "__suppress_context__")
|
|
|
755
733
|
|
|
756
734
|
if HAS_CHAINED_EXCEPTIONS:
|
|
757
735
|
|
|
758
|
-
def walk_exception_chain(exc_info):
|
|
759
|
-
# type: (ExcInfo) -> Iterator[ExcInfo]
|
|
736
|
+
def walk_exception_chain(exc_info: ExcInfo) -> Iterator[ExcInfo]:
|
|
760
737
|
exc_type, exc_value, tb = exc_info
|
|
761
738
|
|
|
762
739
|
seen_exceptions = []
|
|
763
|
-
seen_exception_ids = set()
|
|
740
|
+
seen_exception_ids: Set[int] = set()
|
|
764
741
|
|
|
765
742
|
while (
|
|
766
743
|
exc_type is not None
|
|
@@ -787,23 +764,21 @@ if HAS_CHAINED_EXCEPTIONS:
|
|
|
787
764
|
|
|
788
765
|
else:
|
|
789
766
|
|
|
790
|
-
def walk_exception_chain(exc_info):
|
|
791
|
-
# type: (ExcInfo) -> Iterator[ExcInfo]
|
|
767
|
+
def walk_exception_chain(exc_info: ExcInfo) -> Iterator[ExcInfo]:
|
|
792
768
|
yield exc_info
|
|
793
769
|
|
|
794
770
|
|
|
795
771
|
def exceptions_from_error(
|
|
796
|
-
exc_type
|
|
797
|
-
exc_value
|
|
798
|
-
tb
|
|
799
|
-
client_options
|
|
800
|
-
mechanism
|
|
801
|
-
exception_id=0,
|
|
802
|
-
parent_id=0,
|
|
803
|
-
source
|
|
804
|
-
full_stack
|
|
805
|
-
):
|
|
806
|
-
# type: (...) -> Tuple[int, List[Dict[str, Any]]]
|
|
772
|
+
exc_type: Optional[type],
|
|
773
|
+
exc_value: Optional[BaseException],
|
|
774
|
+
tb: Optional[TracebackType],
|
|
775
|
+
client_options: Optional[Dict[str, Any]] = None,
|
|
776
|
+
mechanism: Optional[Dict[str, Any]] = None,
|
|
777
|
+
exception_id: int = 0,
|
|
778
|
+
parent_id: int = 0,
|
|
779
|
+
source: Optional[str] = None,
|
|
780
|
+
full_stack: Optional[list[dict[str, Any]]] = None,
|
|
781
|
+
) -> Tuple[int, List[Dict[str, Any]]]:
|
|
807
782
|
"""
|
|
808
783
|
Converts the given exception information into the Sentry structured "exception" format.
|
|
809
784
|
This will return a list of exceptions (a flattened tree of exceptions) in the
|
|
@@ -838,7 +813,9 @@ def exceptions_from_error(
|
|
|
838
813
|
exception_source = None
|
|
839
814
|
|
|
840
815
|
# Add any causing exceptions, if present.
|
|
841
|
-
should_suppress_context =
|
|
816
|
+
should_suppress_context = (
|
|
817
|
+
hasattr(exc_value, "__suppress_context__") and exc_value.__suppress_context__ # type: ignore[union-attr]
|
|
818
|
+
)
|
|
842
819
|
# Note: __suppress_context__ is True if the exception is raised with the `from` keyword.
|
|
843
820
|
if should_suppress_context:
|
|
844
821
|
# Explicitly chained exceptions (Like: raise NewException() from OriginalException())
|
|
@@ -862,7 +839,7 @@ def exceptions_from_error(
|
|
|
862
839
|
)
|
|
863
840
|
if has_implicit_causing_exception:
|
|
864
841
|
exception_source = "__context__"
|
|
865
|
-
|
|
842
|
+
causing_exception = exc_value.__context__ # type: ignore
|
|
866
843
|
|
|
867
844
|
if causing_exception:
|
|
868
845
|
# Some frameworks (e.g. FastAPI) wrap the causing exception in an
|
|
@@ -912,12 +889,11 @@ def exceptions_from_error(
|
|
|
912
889
|
|
|
913
890
|
|
|
914
891
|
def exceptions_from_error_tuple(
|
|
915
|
-
exc_info
|
|
916
|
-
client_options
|
|
917
|
-
mechanism
|
|
918
|
-
full_stack
|
|
919
|
-
):
|
|
920
|
-
# type: (...) -> List[Dict[str, Any]]
|
|
892
|
+
exc_info: ExcInfo,
|
|
893
|
+
client_options: Optional[Dict[str, Any]] = None,
|
|
894
|
+
mechanism: Optional[Dict[str, Any]] = None,
|
|
895
|
+
full_stack: Optional[list[dict[str, Any]]] = None,
|
|
896
|
+
) -> List[Dict[str, Any]]:
|
|
921
897
|
"""
|
|
922
898
|
Convert Python's exception information into Sentry's structured "exception" format in the event.
|
|
923
899
|
See https://develop.sentry.dev/sdk/data-model/event-payloads/exception/
|
|
@@ -946,16 +922,14 @@ def exceptions_from_error_tuple(
|
|
|
946
922
|
return exceptions
|
|
947
923
|
|
|
948
924
|
|
|
949
|
-
def to_string(value):
|
|
950
|
-
# type: (str) -> str
|
|
925
|
+
def to_string(value: Any) -> str:
|
|
951
926
|
try:
|
|
952
927
|
return str(value)
|
|
953
928
|
except UnicodeDecodeError:
|
|
954
929
|
return repr(value)[1:-1]
|
|
955
930
|
|
|
956
931
|
|
|
957
|
-
def iter_event_stacktraces(event):
|
|
958
|
-
# type: (Event) -> Iterator[Annotated[Dict[str, Any]]]
|
|
932
|
+
def iter_event_stacktraces(event: Event) -> Iterator[Annotated[Dict[str, Any]]]:
|
|
959
933
|
if "stacktrace" in event:
|
|
960
934
|
yield event["stacktrace"]
|
|
961
935
|
if "threads" in event:
|
|
@@ -968,8 +942,7 @@ def iter_event_stacktraces(event):
|
|
|
968
942
|
yield exception["stacktrace"]
|
|
969
943
|
|
|
970
944
|
|
|
971
|
-
def iter_event_frames(event):
|
|
972
|
-
# type: (Event) -> Iterator[Dict[str, Any]]
|
|
945
|
+
def iter_event_frames(event: Event) -> Iterator[Dict[str, Any]]:
|
|
973
946
|
for stacktrace in iter_event_stacktraces(event):
|
|
974
947
|
if isinstance(stacktrace, AnnotatedValue):
|
|
975
948
|
stacktrace = stacktrace.value or {}
|
|
@@ -978,8 +951,12 @@ def iter_event_frames(event):
|
|
|
978
951
|
yield frame
|
|
979
952
|
|
|
980
953
|
|
|
981
|
-
def handle_in_app(
|
|
982
|
-
|
|
954
|
+
def handle_in_app(
|
|
955
|
+
event: Event,
|
|
956
|
+
in_app_exclude: Optional[List[str]] = None,
|
|
957
|
+
in_app_include: Optional[List[str]] = None,
|
|
958
|
+
project_root: Optional[str] = None,
|
|
959
|
+
) -> Event:
|
|
983
960
|
for stacktrace in iter_event_stacktraces(event):
|
|
984
961
|
if isinstance(stacktrace, AnnotatedValue):
|
|
985
962
|
stacktrace = stacktrace.value or {}
|
|
@@ -994,8 +971,12 @@ def handle_in_app(event, in_app_exclude=None, in_app_include=None, project_root=
|
|
|
994
971
|
return event
|
|
995
972
|
|
|
996
973
|
|
|
997
|
-
def set_in_app_in_frames(
|
|
998
|
-
|
|
974
|
+
def set_in_app_in_frames(
|
|
975
|
+
frames: Any,
|
|
976
|
+
in_app_exclude: Optional[List[str]],
|
|
977
|
+
in_app_include: Optional[List[str]],
|
|
978
|
+
project_root: Optional[str] = None,
|
|
979
|
+
) -> Optional[Any]:
|
|
999
980
|
if not frames:
|
|
1000
981
|
return None
|
|
1001
982
|
|
|
@@ -1033,8 +1014,7 @@ def set_in_app_in_frames(frames, in_app_exclude, in_app_include, project_root=No
|
|
|
1033
1014
|
return frames
|
|
1034
1015
|
|
|
1035
1016
|
|
|
1036
|
-
def exc_info_from_error(error):
|
|
1037
|
-
# type: (Union[BaseException, ExcInfo]) -> ExcInfo
|
|
1017
|
+
def exc_info_from_error(error: Union[BaseException, ExcInfo]) -> ExcInfo:
|
|
1038
1018
|
if isinstance(error, tuple) and len(error) == 3:
|
|
1039
1019
|
exc_type, exc_value, tb = error
|
|
1040
1020
|
elif isinstance(error, BaseException):
|
|
@@ -1052,18 +1032,17 @@ def exc_info_from_error(error):
|
|
|
1052
1032
|
else:
|
|
1053
1033
|
raise ValueError("Expected Exception object to report, got %s!" % type(error))
|
|
1054
1034
|
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
# None or both not None.
|
|
1060
|
-
exc_info = cast(ExcInfo, exc_info)
|
|
1061
|
-
|
|
1062
|
-
return exc_info
|
|
1035
|
+
if exc_type is not None and exc_value is not None:
|
|
1036
|
+
return (exc_type, exc_value, tb)
|
|
1037
|
+
else:
|
|
1038
|
+
return (None, None, None)
|
|
1063
1039
|
|
|
1064
1040
|
|
|
1065
|
-
def merge_stack_frames(
|
|
1066
|
-
|
|
1041
|
+
def merge_stack_frames(
|
|
1042
|
+
frames: List[Dict[str, Any]],
|
|
1043
|
+
full_stack: List[Dict[str, Any]],
|
|
1044
|
+
client_options: Optional[Dict[str, Any]],
|
|
1045
|
+
) -> List[Dict[str, Any]]:
|
|
1067
1046
|
"""
|
|
1068
1047
|
Add the missing frames from full_stack to frames and return the merged list.
|
|
1069
1048
|
"""
|
|
@@ -1103,11 +1082,10 @@ def merge_stack_frames(frames, full_stack, client_options):
|
|
|
1103
1082
|
|
|
1104
1083
|
|
|
1105
1084
|
def event_from_exception(
|
|
1106
|
-
exc_info
|
|
1107
|
-
client_options
|
|
1108
|
-
mechanism
|
|
1109
|
-
):
|
|
1110
|
-
# type: (...) -> Tuple[Event, Dict[str, Any]]
|
|
1085
|
+
exc_info: Union[BaseException, ExcInfo],
|
|
1086
|
+
client_options: Optional[Dict[str, Any]] = None,
|
|
1087
|
+
mechanism: Optional[Dict[str, Any]] = None,
|
|
1088
|
+
) -> Tuple[Event, Dict[str, Any]]:
|
|
1111
1089
|
exc_info = exc_info_from_error(exc_info)
|
|
1112
1090
|
hint = event_hint_with_exc_info(exc_info)
|
|
1113
1091
|
|
|
@@ -1132,8 +1110,7 @@ def event_from_exception(
|
|
|
1132
1110
|
)
|
|
1133
1111
|
|
|
1134
1112
|
|
|
1135
|
-
def _module_in_list(name, items):
|
|
1136
|
-
# type: (Optional[str], Optional[List[str]]) -> bool
|
|
1113
|
+
def _module_in_list(name: Optional[str], items: Optional[List[str]]) -> bool:
|
|
1137
1114
|
if name is None:
|
|
1138
1115
|
return False
|
|
1139
1116
|
|
|
@@ -1147,8 +1124,7 @@ def _module_in_list(name, items):
|
|
|
1147
1124
|
return False
|
|
1148
1125
|
|
|
1149
1126
|
|
|
1150
|
-
def _is_external_source(abs_path):
|
|
1151
|
-
# type: (Optional[str]) -> bool
|
|
1127
|
+
def _is_external_source(abs_path: Optional[str]) -> bool:
|
|
1152
1128
|
# check if frame is in 'site-packages' or 'dist-packages'
|
|
1153
1129
|
if abs_path is None:
|
|
1154
1130
|
return False
|
|
@@ -1159,8 +1135,7 @@ def _is_external_source(abs_path):
|
|
|
1159
1135
|
return external_source
|
|
1160
1136
|
|
|
1161
1137
|
|
|
1162
|
-
def _is_in_project_root(abs_path, project_root):
|
|
1163
|
-
# type: (Optional[str], Optional[str]) -> bool
|
|
1138
|
+
def _is_in_project_root(abs_path: Optional[str], project_root: Optional[str]) -> bool:
|
|
1164
1139
|
if abs_path is None or project_root is None:
|
|
1165
1140
|
return False
|
|
1166
1141
|
|
|
@@ -1171,8 +1146,7 @@ def _is_in_project_root(abs_path, project_root):
|
|
|
1171
1146
|
return False
|
|
1172
1147
|
|
|
1173
1148
|
|
|
1174
|
-
def _truncate_by_bytes(string, max_bytes):
|
|
1175
|
-
# type: (str, int) -> str
|
|
1149
|
+
def _truncate_by_bytes(string: str, max_bytes: int) -> str:
|
|
1176
1150
|
"""
|
|
1177
1151
|
Truncate a UTF-8-encodable string to the last full codepoint so that it fits in max_bytes.
|
|
1178
1152
|
"""
|
|
@@ -1181,16 +1155,16 @@ def _truncate_by_bytes(string, max_bytes):
|
|
|
1181
1155
|
return truncated + "..."
|
|
1182
1156
|
|
|
1183
1157
|
|
|
1184
|
-
def _get_size_in_bytes(value):
|
|
1185
|
-
# type: (str) -> Optional[int]
|
|
1158
|
+
def _get_size_in_bytes(value: str) -> Optional[int]:
|
|
1186
1159
|
try:
|
|
1187
1160
|
return len(value.encode("utf-8"))
|
|
1188
1161
|
except (UnicodeEncodeError, UnicodeDecodeError):
|
|
1189
1162
|
return None
|
|
1190
1163
|
|
|
1191
1164
|
|
|
1192
|
-
def strip_string(
|
|
1193
|
-
|
|
1165
|
+
def strip_string(
|
|
1166
|
+
value: str, max_length: Optional[int] = None
|
|
1167
|
+
) -> Union[AnnotatedValue, str]:
|
|
1194
1168
|
if not value:
|
|
1195
1169
|
return value
|
|
1196
1170
|
|
|
@@ -1218,8 +1192,7 @@ def strip_string(value, max_length=None):
|
|
|
1218
1192
|
)
|
|
1219
1193
|
|
|
1220
1194
|
|
|
1221
|
-
def parse_version(version):
|
|
1222
|
-
# type: (str) -> Optional[Tuple[int, ...]]
|
|
1195
|
+
def parse_version(version: str) -> Optional[Tuple[int, ...]]:
|
|
1223
1196
|
"""
|
|
1224
1197
|
Parses a version string into a tuple of integers.
|
|
1225
1198
|
This uses the parsing loging from PEP 440:
|
|
@@ -1263,17 +1236,16 @@ def parse_version(version):
|
|
|
1263
1236
|
|
|
1264
1237
|
try:
|
|
1265
1238
|
release = pattern.match(version).groupdict()["release"] # type: ignore
|
|
1266
|
-
release_tuple = tuple(map(int, release.split(".")[:3]))
|
|
1239
|
+
release_tuple: Tuple[int, ...] = tuple(map(int, release.split(".")[:3]))
|
|
1267
1240
|
except (TypeError, ValueError, AttributeError):
|
|
1268
1241
|
return None
|
|
1269
1242
|
|
|
1270
1243
|
return release_tuple
|
|
1271
1244
|
|
|
1272
1245
|
|
|
1273
|
-
def _is_contextvars_broken():
|
|
1274
|
-
# type: () -> bool
|
|
1246
|
+
def _is_contextvars_broken() -> bool:
|
|
1275
1247
|
"""
|
|
1276
|
-
Returns whether gevent
|
|
1248
|
+
Returns whether gevent has patched the stdlib in a way where thread locals are now more "correct" than contextvars.
|
|
1277
1249
|
"""
|
|
1278
1250
|
try:
|
|
1279
1251
|
import gevent
|
|
@@ -1302,52 +1274,30 @@ def _is_contextvars_broken():
|
|
|
1302
1274
|
except ImportError:
|
|
1303
1275
|
pass
|
|
1304
1276
|
|
|
1305
|
-
try:
|
|
1306
|
-
import greenlet
|
|
1307
|
-
from eventlet.patcher import is_monkey_patched # type: ignore
|
|
1308
|
-
|
|
1309
|
-
greenlet_version = parse_version(greenlet.__version__)
|
|
1310
|
-
|
|
1311
|
-
if greenlet_version is None:
|
|
1312
|
-
logger.error(
|
|
1313
|
-
"Internal error in Sentry SDK: Could not parse Greenlet version from greenlet.__version__."
|
|
1314
|
-
)
|
|
1315
|
-
return False
|
|
1316
|
-
|
|
1317
|
-
if is_monkey_patched("thread") and greenlet_version < (0, 5):
|
|
1318
|
-
return True
|
|
1319
|
-
except ImportError:
|
|
1320
|
-
pass
|
|
1321
|
-
|
|
1322
1277
|
return False
|
|
1323
1278
|
|
|
1324
1279
|
|
|
1325
|
-
def _make_threadlocal_contextvars(local):
|
|
1326
|
-
# type: (type) -> type
|
|
1280
|
+
def _make_threadlocal_contextvars(local: type) -> type:
|
|
1327
1281
|
class ContextVar:
|
|
1328
1282
|
# Super-limited impl of ContextVar
|
|
1329
1283
|
|
|
1330
|
-
def __init__(self, name, default=None):
|
|
1331
|
-
# type: (str, Any) -> None
|
|
1284
|
+
def __init__(self, name: str, default: Optional[Any] = None) -> None:
|
|
1332
1285
|
self._name = name
|
|
1333
1286
|
self._default = default
|
|
1334
1287
|
self._local = local()
|
|
1335
1288
|
self._original_local = local()
|
|
1336
1289
|
|
|
1337
|
-
def get(self, default=None):
|
|
1338
|
-
# type: (Any) -> Any
|
|
1290
|
+
def get(self, default: Optional[Any] = None) -> Any:
|
|
1339
1291
|
return getattr(self._local, "value", default or self._default)
|
|
1340
1292
|
|
|
1341
|
-
def set(self, value):
|
|
1342
|
-
# type: (Any) -> Any
|
|
1293
|
+
def set(self, value: Any) -> Any:
|
|
1343
1294
|
token = str(random.getrandbits(64))
|
|
1344
1295
|
original_value = self.get()
|
|
1345
1296
|
setattr(self._original_local, token, original_value)
|
|
1346
1297
|
self._local.value = value
|
|
1347
1298
|
return token
|
|
1348
1299
|
|
|
1349
|
-
def reset(self, token):
|
|
1350
|
-
# type: (Any) -> None
|
|
1300
|
+
def reset(self, token: Any) -> None:
|
|
1351
1301
|
self._local.value = getattr(self._original_local, token)
|
|
1352
1302
|
# delete the original value (this way it works in Python 3.6+)
|
|
1353
1303
|
del self._original_local.__dict__[token]
|
|
@@ -1355,8 +1305,7 @@ def _make_threadlocal_contextvars(local):
|
|
|
1355
1305
|
return ContextVar
|
|
1356
1306
|
|
|
1357
1307
|
|
|
1358
|
-
def _get_contextvars():
|
|
1359
|
-
# type: () -> Tuple[bool, type]
|
|
1308
|
+
def _get_contextvars() -> Tuple[bool, type]:
|
|
1360
1309
|
"""
|
|
1361
1310
|
Figure out the "right" contextvars installation to use. Returns a
|
|
1362
1311
|
`contextvars.ContextVar`-like class with a limited API.
|
|
@@ -1391,10 +1340,9 @@ Please refer to https://docs.sentry.io/platforms/python/contextvars/ for more in
|
|
|
1391
1340
|
"""
|
|
1392
1341
|
|
|
1393
1342
|
|
|
1394
|
-
def qualname_from_function(func):
|
|
1395
|
-
# type: (Callable[..., Any]) -> Optional[str]
|
|
1343
|
+
def qualname_from_function(func: Callable[..., Any]) -> Optional[str]:
|
|
1396
1344
|
"""Return the qualified name of func. Works with regular function, lambda, partial and partialmethod."""
|
|
1397
|
-
func_qualname
|
|
1345
|
+
func_qualname: Optional[str] = None
|
|
1398
1346
|
|
|
1399
1347
|
# Python 2
|
|
1400
1348
|
try:
|
|
@@ -1435,8 +1383,7 @@ def qualname_from_function(func):
|
|
|
1435
1383
|
return func_qualname
|
|
1436
1384
|
|
|
1437
1385
|
|
|
1438
|
-
def transaction_from_function(func):
|
|
1439
|
-
# type: (Callable[..., Any]) -> Optional[str]
|
|
1386
|
+
def transaction_from_function(func: Callable[..., Any]) -> Optional[str]:
|
|
1440
1387
|
return qualname_from_function(func)
|
|
1441
1388
|
|
|
1442
1389
|
|
|
@@ -1454,19 +1401,16 @@ class TimeoutThread(threading.Thread):
|
|
|
1454
1401
|
waiting_time and raises a custom ServerlessTimeout exception.
|
|
1455
1402
|
"""
|
|
1456
1403
|
|
|
1457
|
-
def __init__(self, waiting_time, configured_timeout):
|
|
1458
|
-
# type: (float, int) -> None
|
|
1404
|
+
def __init__(self, waiting_time: float, configured_timeout: int) -> None:
|
|
1459
1405
|
threading.Thread.__init__(self)
|
|
1460
1406
|
self.waiting_time = waiting_time
|
|
1461
1407
|
self.configured_timeout = configured_timeout
|
|
1462
1408
|
self._stop_event = threading.Event()
|
|
1463
1409
|
|
|
1464
|
-
def stop(self):
|
|
1465
|
-
# type: () -> None
|
|
1410
|
+
def stop(self) -> None:
|
|
1466
1411
|
self._stop_event.set()
|
|
1467
1412
|
|
|
1468
|
-
def run(self):
|
|
1469
|
-
# type: () -> None
|
|
1413
|
+
def run(self) -> None:
|
|
1470
1414
|
|
|
1471
1415
|
self._stop_event.wait(self.waiting_time)
|
|
1472
1416
|
|
|
@@ -1487,8 +1431,7 @@ class TimeoutThread(threading.Thread):
|
|
|
1487
1431
|
)
|
|
1488
1432
|
|
|
1489
1433
|
|
|
1490
|
-
def to_base64(original):
|
|
1491
|
-
# type: (str) -> Optional[str]
|
|
1434
|
+
def to_base64(original: str) -> Optional[str]:
|
|
1492
1435
|
"""
|
|
1493
1436
|
Convert a string to base64, via UTF-8. Returns None on invalid input.
|
|
1494
1437
|
"""
|
|
@@ -1504,8 +1447,7 @@ def to_base64(original):
|
|
|
1504
1447
|
return base64_string
|
|
1505
1448
|
|
|
1506
1449
|
|
|
1507
|
-
def from_base64(base64_string):
|
|
1508
|
-
# type: (str) -> Optional[str]
|
|
1450
|
+
def from_base64(base64_string: str) -> Optional[str]:
|
|
1509
1451
|
"""
|
|
1510
1452
|
Convert a string from base64, via UTF-8. Returns None on invalid input.
|
|
1511
1453
|
"""
|
|
@@ -1529,8 +1471,12 @@ def from_base64(base64_string):
|
|
|
1529
1471
|
Components = namedtuple("Components", ["scheme", "netloc", "path", "query", "fragment"])
|
|
1530
1472
|
|
|
1531
1473
|
|
|
1532
|
-
def sanitize_url(
|
|
1533
|
-
|
|
1474
|
+
def sanitize_url(
|
|
1475
|
+
url: str,
|
|
1476
|
+
remove_authority: bool = True,
|
|
1477
|
+
remove_query_values: bool = True,
|
|
1478
|
+
split: bool = False,
|
|
1479
|
+
) -> Union[str, Components]:
|
|
1534
1480
|
"""
|
|
1535
1481
|
Removes the authority and query parameter values from a given URL.
|
|
1536
1482
|
"""
|
|
@@ -1576,8 +1522,7 @@ def sanitize_url(url, remove_authority=True, remove_query_values=True, split=Fal
|
|
|
1576
1522
|
ParsedUrl = namedtuple("ParsedUrl", ["url", "query", "fragment"])
|
|
1577
1523
|
|
|
1578
1524
|
|
|
1579
|
-
def parse_url(url, sanitize=True):
|
|
1580
|
-
# type: (str, bool) -> ParsedUrl
|
|
1525
|
+
def parse_url(url: str, sanitize: bool = True) -> ParsedUrl:
|
|
1581
1526
|
"""
|
|
1582
1527
|
Splits a URL into a url (including path), query and fragment. If sanitize is True, the query
|
|
1583
1528
|
parameters will be sanitized to remove sensitive data. The autority (username and password)
|
|
@@ -1604,11 +1549,11 @@ def parse_url(url, sanitize=True):
|
|
|
1604
1549
|
)
|
|
1605
1550
|
|
|
1606
1551
|
|
|
1607
|
-
def is_valid_sample_rate(rate, source):
|
|
1608
|
-
# type: (Any, str) -> bool
|
|
1552
|
+
def is_valid_sample_rate(rate: Any, source: str) -> Optional[float]:
|
|
1609
1553
|
"""
|
|
1610
1554
|
Checks the given sample rate to make sure it is valid type and value (a
|
|
1611
1555
|
boolean or a number between 0 and 1, inclusive).
|
|
1556
|
+
Returns the final float value to use if valid.
|
|
1612
1557
|
"""
|
|
1613
1558
|
|
|
1614
1559
|
# both booleans and NaN are instances of Real, so a) checking for Real
|
|
@@ -1620,7 +1565,7 @@ def is_valid_sample_rate(rate, source):
|
|
|
1620
1565
|
source=source, rate=rate, type=type(rate)
|
|
1621
1566
|
)
|
|
1622
1567
|
)
|
|
1623
|
-
return
|
|
1568
|
+
return None
|
|
1624
1569
|
|
|
1625
1570
|
# in case rate is a boolean, it will get cast to 1 if it's True and 0 if it's False
|
|
1626
1571
|
rate = float(rate)
|
|
@@ -1630,13 +1575,14 @@ def is_valid_sample_rate(rate, source):
|
|
|
1630
1575
|
source=source, rate=rate
|
|
1631
1576
|
)
|
|
1632
1577
|
)
|
|
1633
|
-
return
|
|
1578
|
+
return None
|
|
1634
1579
|
|
|
1635
|
-
return
|
|
1580
|
+
return rate
|
|
1636
1581
|
|
|
1637
1582
|
|
|
1638
|
-
def match_regex_list(
|
|
1639
|
-
|
|
1583
|
+
def match_regex_list(
|
|
1584
|
+
item: str, regex_list: Optional[List[str]] = None, substring_matching: bool = False
|
|
1585
|
+
) -> bool:
|
|
1640
1586
|
if regex_list is None:
|
|
1641
1587
|
return False
|
|
1642
1588
|
|
|
@@ -1651,8 +1597,7 @@ def match_regex_list(item, regex_list=None, substring_matching=False):
|
|
|
1651
1597
|
return False
|
|
1652
1598
|
|
|
1653
1599
|
|
|
1654
|
-
def is_sentry_url(client, url):
|
|
1655
|
-
# type: (sentry_sdk.client.BaseClient, str) -> bool
|
|
1600
|
+
def is_sentry_url(client: sentry_sdk.client.BaseClient, url: str) -> bool:
|
|
1656
1601
|
"""
|
|
1657
1602
|
Determines whether the given URL matches the Sentry DSN.
|
|
1658
1603
|
"""
|
|
@@ -1664,8 +1609,7 @@ def is_sentry_url(client, url):
|
|
|
1664
1609
|
)
|
|
1665
1610
|
|
|
1666
1611
|
|
|
1667
|
-
def _generate_installed_modules():
|
|
1668
|
-
# type: () -> Iterator[Tuple[str, str]]
|
|
1612
|
+
def _generate_installed_modules() -> Iterator[Tuple[str, str]]:
|
|
1669
1613
|
try:
|
|
1670
1614
|
from importlib import metadata
|
|
1671
1615
|
|
|
@@ -1693,21 +1637,18 @@ def _generate_installed_modules():
|
|
|
1693
1637
|
yield _normalize_module_name(info.key), info.version
|
|
1694
1638
|
|
|
1695
1639
|
|
|
1696
|
-
def _normalize_module_name(name):
|
|
1697
|
-
# type: (str) -> str
|
|
1640
|
+
def _normalize_module_name(name: str) -> str:
|
|
1698
1641
|
return name.lower()
|
|
1699
1642
|
|
|
1700
1643
|
|
|
1701
|
-
def _get_installed_modules():
|
|
1702
|
-
# type: () -> Dict[str, str]
|
|
1644
|
+
def _get_installed_modules() -> Dict[str, str]:
|
|
1703
1645
|
global _installed_modules
|
|
1704
1646
|
if _installed_modules is None:
|
|
1705
1647
|
_installed_modules = dict(_generate_installed_modules())
|
|
1706
1648
|
return _installed_modules
|
|
1707
1649
|
|
|
1708
1650
|
|
|
1709
|
-
def package_version(package):
|
|
1710
|
-
# type: (str) -> Optional[Tuple[int, ...]]
|
|
1651
|
+
def package_version(package: str) -> Optional[Tuple[int, ...]]:
|
|
1711
1652
|
installed_packages = _get_installed_modules()
|
|
1712
1653
|
version = installed_packages.get(package)
|
|
1713
1654
|
if version is None:
|
|
@@ -1716,43 +1657,35 @@ def package_version(package):
|
|
|
1716
1657
|
return parse_version(version)
|
|
1717
1658
|
|
|
1718
1659
|
|
|
1719
|
-
def reraise(
|
|
1720
|
-
|
|
1660
|
+
def reraise(
|
|
1661
|
+
tp: Optional[Type[BaseException]],
|
|
1662
|
+
value: Optional[BaseException],
|
|
1663
|
+
tb: Optional[Any] = None,
|
|
1664
|
+
) -> NoReturn:
|
|
1721
1665
|
assert value is not None
|
|
1722
1666
|
if value.__traceback__ is not tb:
|
|
1723
1667
|
raise value.with_traceback(tb)
|
|
1724
1668
|
raise value
|
|
1725
1669
|
|
|
1726
1670
|
|
|
1727
|
-
def _no_op(*_a, **_k):
|
|
1728
|
-
# type: (*Any, **Any) -> None
|
|
1729
|
-
"""No-op function for ensure_integration_enabled."""
|
|
1730
|
-
pass
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
1671
|
if TYPE_CHECKING:
|
|
1734
1672
|
|
|
1735
1673
|
@overload
|
|
1736
1674
|
def ensure_integration_enabled(
|
|
1737
|
-
integration
|
|
1738
|
-
original_function
|
|
1739
|
-
):
|
|
1740
|
-
# type: (...) -> Callable[[Callable[P, R]], Callable[P, R]]
|
|
1741
|
-
...
|
|
1675
|
+
integration: type[sentry_sdk.integrations.Integration],
|
|
1676
|
+
original_function: Callable[P, R],
|
|
1677
|
+
) -> Callable[[Callable[P, R]], Callable[P, R]]: ...
|
|
1742
1678
|
|
|
1743
1679
|
@overload
|
|
1744
1680
|
def ensure_integration_enabled(
|
|
1745
|
-
integration
|
|
1746
|
-
):
|
|
1747
|
-
# type: (...) -> Callable[[Callable[P, None]], Callable[P, None]]
|
|
1748
|
-
...
|
|
1681
|
+
integration: type[sentry_sdk.integrations.Integration],
|
|
1682
|
+
) -> Callable[[Callable[P, None]], Callable[P, None]]: ...
|
|
1749
1683
|
|
|
1750
1684
|
|
|
1751
1685
|
def ensure_integration_enabled(
|
|
1752
|
-
integration
|
|
1753
|
-
original_function
|
|
1754
|
-
):
|
|
1755
|
-
# type: (...) -> Callable[[Callable[P, R]], Callable[P, R]]
|
|
1686
|
+
integration: type[sentry_sdk.integrations.Integration],
|
|
1687
|
+
original_function: Optional[Callable[P, R]] = None,
|
|
1688
|
+
) -> Callable[[Callable[P, R]], Callable[P, Optional[R]]]:
|
|
1756
1689
|
"""
|
|
1757
1690
|
Ensures a given integration is enabled prior to calling a Sentry-patched function.
|
|
1758
1691
|
|
|
@@ -1774,30 +1707,25 @@ def ensure_integration_enabled(
|
|
|
1774
1707
|
return my_function()
|
|
1775
1708
|
```
|
|
1776
1709
|
"""
|
|
1777
|
-
if TYPE_CHECKING:
|
|
1778
|
-
# Type hint to ensure the default function has the right typing. The overloads
|
|
1779
|
-
# ensure the default _no_op function is only used when R is None.
|
|
1780
|
-
original_function = cast(Callable[P, R], original_function)
|
|
1781
|
-
|
|
1782
|
-
def patcher(sentry_patched_function):
|
|
1783
|
-
# type: (Callable[P, R]) -> Callable[P, R]
|
|
1784
|
-
def runner(*args: "P.args", **kwargs: "P.kwargs"):
|
|
1785
|
-
# type: (...) -> R
|
|
1786
|
-
if sentry_sdk.get_client().get_integration(integration) is None:
|
|
1787
|
-
return original_function(*args, **kwargs)
|
|
1788
1710
|
|
|
1789
|
-
|
|
1711
|
+
def patcher(sentry_patched_function: Callable[P, R]) -> Callable[P, Optional[R]]:
|
|
1712
|
+
def runner(*args: P.args, **kwargs: P.kwargs) -> Optional[R]:
|
|
1713
|
+
if sentry_sdk.get_client().get_integration(integration) is not None:
|
|
1714
|
+
return sentry_patched_function(*args, **kwargs)
|
|
1715
|
+
elif original_function is not None:
|
|
1716
|
+
return original_function(*args, **kwargs)
|
|
1717
|
+
else:
|
|
1718
|
+
return None
|
|
1790
1719
|
|
|
1791
|
-
if original_function
|
|
1720
|
+
if original_function:
|
|
1721
|
+
return wraps(original_function)(runner)
|
|
1722
|
+
else:
|
|
1792
1723
|
return wraps(sentry_patched_function)(runner)
|
|
1793
1724
|
|
|
1794
|
-
return wraps(original_function)(runner)
|
|
1795
|
-
|
|
1796
1725
|
return patcher
|
|
1797
1726
|
|
|
1798
1727
|
|
|
1799
|
-
def now():
|
|
1800
|
-
# type: () -> float
|
|
1728
|
+
def now() -> float:
|
|
1801
1729
|
return time.perf_counter()
|
|
1802
1730
|
|
|
1803
1731
|
|
|
@@ -1808,23 +1736,21 @@ except ImportError:
|
|
|
1808
1736
|
|
|
1809
1737
|
# it's not great that the signatures are different, get_hub can't return None
|
|
1810
1738
|
# consider adding an if TYPE_CHECKING to change the signature to Optional[GeventHub]
|
|
1811
|
-
def get_gevent_hub(): # type: ignore[misc]
|
|
1812
|
-
# type: () -> Optional[GeventHub]
|
|
1739
|
+
def get_gevent_hub() -> Optional[GeventHub]: # type: ignore[misc]
|
|
1813
1740
|
return None
|
|
1814
1741
|
|
|
1815
|
-
def is_module_patched(mod_name):
|
|
1816
|
-
# type: (str) -> bool
|
|
1742
|
+
def is_module_patched(mod_name: str) -> bool:
|
|
1817
1743
|
# unable to import from gevent means no modules have been patched
|
|
1818
1744
|
return False
|
|
1819
1745
|
|
|
1820
1746
|
|
|
1821
|
-
def is_gevent():
|
|
1822
|
-
# type: () -> bool
|
|
1747
|
+
def is_gevent() -> bool:
|
|
1823
1748
|
return is_module_patched("threading") or is_module_patched("_thread")
|
|
1824
1749
|
|
|
1825
1750
|
|
|
1826
|
-
def get_current_thread_meta(
|
|
1827
|
-
|
|
1751
|
+
def get_current_thread_meta(
|
|
1752
|
+
thread: Optional[threading.Thread] = None,
|
|
1753
|
+
) -> Tuple[Optional[int], Optional[str]]:
|
|
1828
1754
|
"""
|
|
1829
1755
|
Try to get the id of the current thread, with various fall backs.
|
|
1830
1756
|
"""
|
|
@@ -1874,8 +1800,7 @@ def get_current_thread_meta(thread=None):
|
|
|
1874
1800
|
return None, None
|
|
1875
1801
|
|
|
1876
1802
|
|
|
1877
|
-
def _serialize_span_attribute(value):
|
|
1878
|
-
# type: (Any) -> Optional[AttributeValue]
|
|
1803
|
+
def _serialize_span_attribute(value: Any) -> Optional[AttributeValue]:
|
|
1879
1804
|
"""Serialize an object so that it's OTel-compatible and displays nicely in Sentry."""
|
|
1880
1805
|
# check for allowed primitives
|
|
1881
1806
|
if isinstance(value, (int, str, float, bool)):
|
|
@@ -1902,8 +1827,7 @@ def _serialize_span_attribute(value):
|
|
|
1902
1827
|
ISO_TZ_SEPARATORS = frozenset(("+", "-"))
|
|
1903
1828
|
|
|
1904
1829
|
|
|
1905
|
-
def datetime_from_isoformat(value):
|
|
1906
|
-
# type: (str) -> datetime
|
|
1830
|
+
def datetime_from_isoformat(value: str) -> datetime:
|
|
1907
1831
|
try:
|
|
1908
1832
|
result = datetime.fromisoformat(value)
|
|
1909
1833
|
except (AttributeError, ValueError):
|
|
@@ -1924,8 +1848,7 @@ def datetime_from_isoformat(value):
|
|
|
1924
1848
|
return result.astimezone(timezone.utc)
|
|
1925
1849
|
|
|
1926
1850
|
|
|
1927
|
-
def should_be_treated_as_error(ty, value):
|
|
1928
|
-
# type: (Any, Any) -> bool
|
|
1851
|
+
def should_be_treated_as_error(ty: Any, value: Any) -> bool:
|
|
1929
1852
|
if ty == SystemExit and hasattr(value, "code") and value.code in (0, None):
|
|
1930
1853
|
# https://docs.python.org/3/library/exceptions.html#SystemExit
|
|
1931
1854
|
return False
|
|
@@ -1933,8 +1856,7 @@ def should_be_treated_as_error(ty, value):
|
|
|
1933
1856
|
return True
|
|
1934
1857
|
|
|
1935
1858
|
|
|
1936
|
-
def http_client_status_to_breadcrumb_level(status_code):
|
|
1937
|
-
# type: (Optional[int]) -> str
|
|
1859
|
+
def http_client_status_to_breadcrumb_level(status_code: Optional[int]) -> str:
|
|
1938
1860
|
if status_code is not None:
|
|
1939
1861
|
if 500 <= status_code <= 599:
|
|
1940
1862
|
return "error"
|
|
@@ -1944,8 +1866,9 @@ def http_client_status_to_breadcrumb_level(status_code):
|
|
|
1944
1866
|
return "info"
|
|
1945
1867
|
|
|
1946
1868
|
|
|
1947
|
-
def set_thread_info_from_span(
|
|
1948
|
-
|
|
1869
|
+
def set_thread_info_from_span(
|
|
1870
|
+
data: Dict[str, Any], span: sentry_sdk.tracing.Span
|
|
1871
|
+
) -> None:
|
|
1949
1872
|
if span.get_attribute(SPANDATA.THREAD_ID) is not None:
|
|
1950
1873
|
data[SPANDATA.THREAD_ID] = span.get_attribute(SPANDATA.THREAD_ID)
|
|
1951
1874
|
if span.get_attribute(SPANDATA.THREAD_NAME) is not None:
|