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

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

Potentially problematic release.


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

Files changed (157) hide show
  1. sentry_sdk/__init__.py +2 -0
  2. sentry_sdk/_compat.py +5 -12
  3. sentry_sdk/_init_implementation.py +7 -7
  4. sentry_sdk/_log_batcher.py +17 -29
  5. sentry_sdk/_lru_cache.py +7 -9
  6. sentry_sdk/_queue.py +2 -4
  7. sentry_sdk/_types.py +11 -18
  8. sentry_sdk/_werkzeug.py +5 -7
  9. sentry_sdk/ai/monitoring.py +44 -31
  10. sentry_sdk/ai/utils.py +3 -4
  11. sentry_sdk/api.py +75 -87
  12. sentry_sdk/attachments.py +10 -12
  13. sentry_sdk/client.py +137 -155
  14. sentry_sdk/consts.py +430 -174
  15. sentry_sdk/crons/api.py +16 -17
  16. sentry_sdk/crons/decorator.py +25 -27
  17. sentry_sdk/debug.py +4 -6
  18. sentry_sdk/envelope.py +46 -112
  19. sentry_sdk/feature_flags.py +9 -15
  20. sentry_sdk/integrations/__init__.py +24 -19
  21. sentry_sdk/integrations/_asgi_common.py +15 -18
  22. sentry_sdk/integrations/_wsgi_common.py +22 -33
  23. sentry_sdk/integrations/aiohttp.py +32 -30
  24. sentry_sdk/integrations/anthropic.py +42 -37
  25. sentry_sdk/integrations/argv.py +3 -4
  26. sentry_sdk/integrations/ariadne.py +16 -18
  27. sentry_sdk/integrations/arq.py +21 -29
  28. sentry_sdk/integrations/asgi.py +63 -37
  29. sentry_sdk/integrations/asyncio.py +14 -16
  30. sentry_sdk/integrations/atexit.py +6 -10
  31. sentry_sdk/integrations/aws_lambda.py +26 -36
  32. sentry_sdk/integrations/beam.py +10 -18
  33. sentry_sdk/integrations/boto3.py +18 -16
  34. sentry_sdk/integrations/bottle.py +25 -34
  35. sentry_sdk/integrations/celery/__init__.py +41 -61
  36. sentry_sdk/integrations/celery/beat.py +23 -27
  37. sentry_sdk/integrations/celery/utils.py +15 -17
  38. sentry_sdk/integrations/chalice.py +8 -10
  39. sentry_sdk/integrations/clickhouse_driver.py +21 -31
  40. sentry_sdk/integrations/cloud_resource_context.py +9 -16
  41. sentry_sdk/integrations/cohere.py +27 -33
  42. sentry_sdk/integrations/dedupe.py +5 -8
  43. sentry_sdk/integrations/django/__init__.py +57 -72
  44. sentry_sdk/integrations/django/asgi.py +26 -34
  45. sentry_sdk/integrations/django/caching.py +23 -19
  46. sentry_sdk/integrations/django/middleware.py +17 -20
  47. sentry_sdk/integrations/django/signals_handlers.py +11 -10
  48. sentry_sdk/integrations/django/templates.py +19 -16
  49. sentry_sdk/integrations/django/transactions.py +16 -11
  50. sentry_sdk/integrations/django/views.py +6 -10
  51. sentry_sdk/integrations/dramatiq.py +21 -21
  52. sentry_sdk/integrations/excepthook.py +10 -10
  53. sentry_sdk/integrations/executing.py +3 -4
  54. sentry_sdk/integrations/falcon.py +27 -42
  55. sentry_sdk/integrations/fastapi.py +13 -16
  56. sentry_sdk/integrations/flask.py +31 -38
  57. sentry_sdk/integrations/gcp.py +13 -16
  58. sentry_sdk/integrations/gnu_backtrace.py +4 -6
  59. sentry_sdk/integrations/gql.py +16 -17
  60. sentry_sdk/integrations/graphene.py +13 -12
  61. sentry_sdk/integrations/grpc/__init__.py +19 -1
  62. sentry_sdk/integrations/grpc/aio/server.py +15 -14
  63. sentry_sdk/integrations/grpc/client.py +19 -9
  64. sentry_sdk/integrations/grpc/consts.py +2 -0
  65. sentry_sdk/integrations/grpc/server.py +12 -8
  66. sentry_sdk/integrations/httpx.py +9 -12
  67. sentry_sdk/integrations/huey.py +13 -20
  68. sentry_sdk/integrations/huggingface_hub.py +18 -18
  69. sentry_sdk/integrations/langchain.py +203 -113
  70. sentry_sdk/integrations/launchdarkly.py +13 -10
  71. sentry_sdk/integrations/litestar.py +37 -35
  72. sentry_sdk/integrations/logging.py +52 -65
  73. sentry_sdk/integrations/loguru.py +127 -57
  74. sentry_sdk/integrations/modules.py +3 -4
  75. sentry_sdk/integrations/openai.py +100 -88
  76. sentry_sdk/integrations/openai_agents/__init__.py +49 -0
  77. sentry_sdk/integrations/openai_agents/consts.py +1 -0
  78. sentry_sdk/integrations/openai_agents/patches/__init__.py +4 -0
  79. sentry_sdk/integrations/openai_agents/patches/agent_run.py +152 -0
  80. sentry_sdk/integrations/openai_agents/patches/models.py +52 -0
  81. sentry_sdk/integrations/openai_agents/patches/runner.py +42 -0
  82. sentry_sdk/integrations/openai_agents/patches/tools.py +84 -0
  83. sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
  84. sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +20 -0
  85. sentry_sdk/integrations/openai_agents/spans/ai_client.py +46 -0
  86. sentry_sdk/integrations/openai_agents/spans/execute_tool.py +47 -0
  87. sentry_sdk/integrations/openai_agents/spans/handoff.py +24 -0
  88. sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +41 -0
  89. sentry_sdk/integrations/openai_agents/utils.py +201 -0
  90. sentry_sdk/integrations/openfeature.py +11 -6
  91. sentry_sdk/integrations/pure_eval.py +6 -10
  92. sentry_sdk/integrations/pymongo.py +13 -17
  93. sentry_sdk/integrations/pyramid.py +31 -36
  94. sentry_sdk/integrations/quart.py +23 -28
  95. sentry_sdk/integrations/ray.py +73 -64
  96. sentry_sdk/integrations/redis/__init__.py +7 -4
  97. sentry_sdk/integrations/redis/_async_common.py +25 -12
  98. sentry_sdk/integrations/redis/_sync_common.py +19 -13
  99. sentry_sdk/integrations/redis/modules/caches.py +17 -8
  100. sentry_sdk/integrations/redis/modules/queries.py +9 -8
  101. sentry_sdk/integrations/redis/rb.py +3 -2
  102. sentry_sdk/integrations/redis/redis.py +4 -4
  103. sentry_sdk/integrations/redis/redis_cluster.py +21 -13
  104. sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +3 -2
  105. sentry_sdk/integrations/redis/utils.py +23 -24
  106. sentry_sdk/integrations/rq.py +13 -16
  107. sentry_sdk/integrations/rust_tracing.py +9 -6
  108. sentry_sdk/integrations/sanic.py +34 -46
  109. sentry_sdk/integrations/serverless.py +22 -27
  110. sentry_sdk/integrations/socket.py +27 -15
  111. sentry_sdk/integrations/spark/__init__.py +1 -0
  112. sentry_sdk/integrations/spark/spark_driver.py +45 -83
  113. sentry_sdk/integrations/spark/spark_worker.py +7 -11
  114. sentry_sdk/integrations/sqlalchemy.py +22 -19
  115. sentry_sdk/integrations/starlette.py +86 -90
  116. sentry_sdk/integrations/starlite.py +28 -34
  117. sentry_sdk/integrations/statsig.py +5 -4
  118. sentry_sdk/integrations/stdlib.py +28 -24
  119. sentry_sdk/integrations/strawberry.py +62 -49
  120. sentry_sdk/integrations/sys_exit.py +7 -11
  121. sentry_sdk/integrations/threading.py +12 -14
  122. sentry_sdk/integrations/tornado.py +28 -32
  123. sentry_sdk/integrations/trytond.py +4 -3
  124. sentry_sdk/integrations/typer.py +8 -6
  125. sentry_sdk/integrations/unleash.py +5 -4
  126. sentry_sdk/integrations/wsgi.py +47 -46
  127. sentry_sdk/logger.py +41 -10
  128. sentry_sdk/monitor.py +16 -28
  129. sentry_sdk/opentelemetry/consts.py +11 -4
  130. sentry_sdk/opentelemetry/contextvars_context.py +26 -16
  131. sentry_sdk/opentelemetry/propagator.py +38 -21
  132. sentry_sdk/opentelemetry/sampler.py +51 -34
  133. sentry_sdk/opentelemetry/scope.py +36 -37
  134. sentry_sdk/opentelemetry/span_processor.py +48 -58
  135. sentry_sdk/opentelemetry/tracing.py +58 -14
  136. sentry_sdk/opentelemetry/utils.py +186 -194
  137. sentry_sdk/profiler/continuous_profiler.py +108 -97
  138. sentry_sdk/profiler/transaction_profiler.py +70 -97
  139. sentry_sdk/profiler/utils.py +11 -15
  140. sentry_sdk/scope.py +251 -273
  141. sentry_sdk/scrubber.py +22 -26
  142. sentry_sdk/serializer.py +40 -54
  143. sentry_sdk/session.py +44 -61
  144. sentry_sdk/sessions.py +35 -49
  145. sentry_sdk/spotlight.py +15 -21
  146. sentry_sdk/tracing.py +121 -187
  147. sentry_sdk/tracing_utils.py +104 -122
  148. sentry_sdk/transport.py +131 -157
  149. sentry_sdk/utils.py +232 -309
  150. sentry_sdk/worker.py +16 -28
  151. {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/METADATA +3 -3
  152. sentry_sdk-3.0.0a3.dist-info/RECORD +168 -0
  153. {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/WHEEL +1 -1
  154. sentry_sdk-3.0.0a1.dist-info/RECORD +0 -154
  155. {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/entry_points.txt +0 -0
  156. {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/licenses/LICENSE +0 -0
  157. {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/top_level.txt +0 -0
sentry_sdk/scrubber.py CHANGED
@@ -1,14 +1,15 @@
1
+ from __future__ import annotations
1
2
  from sentry_sdk.utils import (
2
3
  capture_internal_exceptions,
3
4
  AnnotatedValue,
4
5
  iter_event_frames,
5
6
  )
6
7
 
7
- from typing import TYPE_CHECKING, cast, List, Dict
8
+ from typing import TYPE_CHECKING
8
9
 
9
10
  if TYPE_CHECKING:
11
+ from typing import List, Optional
10
12
  from sentry_sdk._types import Event
11
- from typing import Optional
12
13
 
13
14
 
14
15
  DEFAULT_DENYLIST = [
@@ -60,9 +61,12 @@ DEFAULT_PII_DENYLIST = [
60
61
 
61
62
  class EventScrubber:
62
63
  def __init__(
63
- self, denylist=None, recursive=False, send_default_pii=False, pii_denylist=None
64
- ):
65
- # type: (Optional[List[str]], bool, bool, Optional[List[str]]) -> None
64
+ self,
65
+ denylist: Optional[List[str]] = None,
66
+ recursive: bool = False,
67
+ send_default_pii: bool = False,
68
+ pii_denylist: Optional[List[str]] = None,
69
+ ) -> None:
66
70
  """
67
71
  A scrubber that goes through the event payload and removes sensitive data configured through denylists.
68
72
 
@@ -82,8 +86,7 @@ class EventScrubber:
82
86
  self.denylist = [x.lower() for x in self.denylist]
83
87
  self.recursive = recursive
84
88
 
85
- def scrub_list(self, lst):
86
- # type: (object) -> None
89
+ def scrub_list(self, lst: object) -> None:
87
90
  """
88
91
  If a list is passed to this method, the method recursively searches the list and any
89
92
  nested lists for any dictionaries. The method calls scrub_dict on all dictionaries
@@ -97,8 +100,7 @@ class EventScrubber:
97
100
  self.scrub_dict(v) # no-op unless v is a dict
98
101
  self.scrub_list(v) # no-op unless v is a list
99
102
 
100
- def scrub_dict(self, d):
101
- # type: (object) -> None
103
+ def scrub_dict(self, d: object) -> None:
102
104
  """
103
105
  If a dictionary is passed to this method, the method scrubs the dictionary of any
104
106
  sensitive data. The method calls itself recursively on any nested dictionaries (
@@ -117,8 +119,7 @@ class EventScrubber:
117
119
  self.scrub_dict(v) # no-op unless v is a dict
118
120
  self.scrub_list(v) # no-op unless v is a list
119
121
 
120
- def scrub_request(self, event):
121
- # type: (Event) -> None
122
+ def scrub_request(self, event: Event) -> None:
122
123
  with capture_internal_exceptions():
123
124
  if "request" in event:
124
125
  if "headers" in event["request"]:
@@ -128,20 +129,17 @@ class EventScrubber:
128
129
  if "data" in event["request"]:
129
130
  self.scrub_dict(event["request"]["data"])
130
131
 
131
- def scrub_extra(self, event):
132
- # type: (Event) -> None
132
+ def scrub_extra(self, event: Event) -> None:
133
133
  with capture_internal_exceptions():
134
134
  if "extra" in event:
135
135
  self.scrub_dict(event["extra"])
136
136
 
137
- def scrub_user(self, event):
138
- # type: (Event) -> None
137
+ def scrub_user(self, event: Event) -> None:
139
138
  with capture_internal_exceptions():
140
139
  if "user" in event:
141
140
  self.scrub_dict(event["user"])
142
141
 
143
- def scrub_breadcrumbs(self, event):
144
- # type: (Event) -> None
142
+ def scrub_breadcrumbs(self, event: Event) -> None:
145
143
  with capture_internal_exceptions():
146
144
  if "breadcrumbs" in event:
147
145
  if (
@@ -152,23 +150,21 @@ class EventScrubber:
152
150
  if "data" in value:
153
151
  self.scrub_dict(value["data"])
154
152
 
155
- def scrub_frames(self, event):
156
- # type: (Event) -> None
153
+ def scrub_frames(self, event: Event) -> None:
157
154
  with capture_internal_exceptions():
158
155
  for frame in iter_event_frames(event):
159
156
  if "vars" in frame:
160
157
  self.scrub_dict(frame["vars"])
161
158
 
162
- def scrub_spans(self, event):
163
- # type: (Event) -> None
159
+ def scrub_spans(self, event: Event) -> None:
164
160
  with capture_internal_exceptions():
165
161
  if "spans" in event:
166
- for span in cast(List[Dict[str, object]], event["spans"]):
167
- if "data" in span:
168
- self.scrub_dict(span["data"])
162
+ if not isinstance(event["spans"], AnnotatedValue):
163
+ for span in event["spans"]:
164
+ if "data" in span:
165
+ self.scrub_dict(span["data"])
169
166
 
170
- def scrub_event(self, event):
171
- # type: (Event) -> None
167
+ def scrub_event(self, event: Event) -> None:
172
168
  self.scrub_request(event)
173
169
  self.scrub_extra(event)
174
170
  self.scrub_user(event)
sentry_sdk/serializer.py CHANGED
@@ -1,3 +1,4 @@
1
+ from __future__ import annotations
1
2
  import sys
2
3
  import math
3
4
  from collections.abc import Mapping, Sequence, Set
@@ -26,7 +27,7 @@ if TYPE_CHECKING:
26
27
  from typing import Type
27
28
  from typing import Union
28
29
 
29
- from sentry_sdk._types import NotImplementedType
30
+ from sentry_sdk._types import NotImplementedType, Event
30
31
 
31
32
  Span = Dict[str, Any]
32
33
 
@@ -55,29 +56,25 @@ MAX_DATABAG_BREADTH = 10
55
56
  CYCLE_MARKER = "<cyclic>"
56
57
 
57
58
 
58
- global_repr_processors = [] # type: List[ReprProcessor]
59
+ global_repr_processors: List[ReprProcessor] = []
59
60
 
60
61
 
61
- def add_global_repr_processor(processor):
62
- # type: (ReprProcessor) -> None
62
+ def add_global_repr_processor(processor: ReprProcessor) -> None:
63
63
  global_repr_processors.append(processor)
64
64
 
65
65
 
66
66
  class Memo:
67
67
  __slots__ = ("_ids", "_objs")
68
68
 
69
- def __init__(self):
70
- # type: () -> None
71
- self._ids = {} # type: Dict[int, Any]
72
- self._objs = [] # type: List[Any]
69
+ def __init__(self) -> None:
70
+ self._ids: Dict[int, Any] = {}
71
+ self._objs: List[Any] = []
73
72
 
74
- def memoize(self, obj):
75
- # type: (Any) -> ContextManager[bool]
73
+ def memoize(self, obj: Any) -> ContextManager[bool]:
76
74
  self._objs.append(obj)
77
75
  return self
78
76
 
79
- def __enter__(self):
80
- # type: () -> bool
77
+ def __enter__(self) -> bool:
81
78
  obj = self._objs[-1]
82
79
  if id(obj) in self._ids:
83
80
  return True
@@ -87,16 +84,14 @@ class Memo:
87
84
 
88
85
  def __exit__(
89
86
  self,
90
- ty, # type: Optional[Type[BaseException]]
91
- value, # type: Optional[BaseException]
92
- tb, # type: Optional[TracebackType]
93
- ):
94
- # type: (...) -> None
87
+ ty: Optional[Type[BaseException]],
88
+ value: Optional[BaseException],
89
+ tb: Optional[TracebackType],
90
+ ) -> None:
95
91
  self._ids.pop(id(self._objs.pop()), None)
96
92
 
97
93
 
98
- def serialize(event, **kwargs):
99
- # type: (Dict[str, Any], **Any) -> Dict[str, Any]
94
+ def serialize(event: Union[Dict[str, Any], Event], **kwargs: Any) -> Dict[str, Any]:
100
95
  """
101
96
  A very smart serializer that takes a dict and emits a json-friendly dict.
102
97
  Currently used for serializing the final Event and also prematurely while fetching the stack
@@ -117,18 +112,15 @@ def serialize(event, **kwargs):
117
112
 
118
113
  """
119
114
  memo = Memo()
120
- path = [] # type: List[Segment]
121
- meta_stack = [] # type: List[Dict[str, Any]]
115
+ path: List[Segment] = []
116
+ meta_stack: List[Dict[str, Any]] = []
122
117
 
123
- keep_request_bodies = (
124
- kwargs.pop("max_request_body_size", None) == "always"
125
- ) # type: bool
126
- max_value_length = kwargs.pop("max_value_length", None) # type: Optional[int]
118
+ keep_request_bodies: bool = kwargs.pop("max_request_body_size", None) == "always"
119
+ max_value_length: Optional[int] = kwargs.pop("max_value_length", None)
127
120
  is_vars = kwargs.pop("is_vars", False)
128
- custom_repr = kwargs.pop("custom_repr", None) # type: Callable[..., Optional[str]]
121
+ custom_repr: Callable[..., Optional[str]] = kwargs.pop("custom_repr", None)
129
122
 
130
- def _safe_repr_wrapper(value):
131
- # type: (Any) -> str
123
+ def _safe_repr_wrapper(value: Any) -> str:
132
124
  try:
133
125
  repr_value = None
134
126
  if custom_repr is not None:
@@ -137,8 +129,7 @@ def serialize(event, **kwargs):
137
129
  except Exception:
138
130
  return safe_repr(value)
139
131
 
140
- def _annotate(**meta):
141
- # type: (**Any) -> None
132
+ def _annotate(**meta: Any) -> None:
142
133
  while len(meta_stack) <= len(path):
143
134
  try:
144
135
  segment = path[len(meta_stack) - 1]
@@ -150,8 +141,7 @@ def serialize(event, **kwargs):
150
141
 
151
142
  meta_stack[-1].setdefault("", {}).update(meta)
152
143
 
153
- def _is_databag():
154
- # type: () -> Optional[bool]
144
+ def _is_databag() -> Optional[bool]:
155
145
  """
156
146
  A databag is any value that we need to trim.
157
147
  True for stuff like vars, request bodies, breadcrumbs and extra.
@@ -179,8 +169,7 @@ def serialize(event, **kwargs):
179
169
 
180
170
  return False
181
171
 
182
- def _is_request_body():
183
- # type: () -> Optional[bool]
172
+ def _is_request_body() -> Optional[bool]:
184
173
  try:
185
174
  if path[0] == "request" and path[1] == "data":
186
175
  return True
@@ -190,15 +179,14 @@ def serialize(event, **kwargs):
190
179
  return False
191
180
 
192
181
  def _serialize_node(
193
- obj, # type: Any
194
- is_databag=None, # type: Optional[bool]
195
- is_request_body=None, # type: Optional[bool]
196
- should_repr_strings=None, # type: Optional[bool]
197
- segment=None, # type: Optional[Segment]
198
- remaining_breadth=None, # type: Optional[Union[int, float]]
199
- remaining_depth=None, # type: Optional[Union[int, float]]
200
- ):
201
- # type: (...) -> Any
182
+ obj: Any,
183
+ is_databag: Optional[bool] = None,
184
+ is_request_body: Optional[bool] = None,
185
+ should_repr_strings: Optional[bool] = None,
186
+ segment: Optional[Segment] = None,
187
+ remaining_breadth: Optional[Union[int, float]] = None,
188
+ remaining_depth: Optional[Union[int, float]] = None,
189
+ ) -> Any:
202
190
  if segment is not None:
203
191
  path.append(segment)
204
192
 
@@ -227,22 +215,20 @@ def serialize(event, **kwargs):
227
215
  path.pop()
228
216
  del meta_stack[len(path) + 1 :]
229
217
 
230
- def _flatten_annotated(obj):
231
- # type: (Any) -> Any
218
+ def _flatten_annotated(obj: Any) -> Any:
232
219
  if isinstance(obj, AnnotatedValue):
233
220
  _annotate(**obj.metadata)
234
221
  obj = obj.value
235
222
  return obj
236
223
 
237
224
  def _serialize_node_impl(
238
- obj,
239
- is_databag,
240
- is_request_body,
241
- should_repr_strings,
242
- remaining_depth,
243
- remaining_breadth,
244
- ):
245
- # type: (Any, Optional[bool], Optional[bool], Optional[bool], Optional[Union[float, int]], Optional[Union[float, int]]) -> Any
225
+ obj: Any,
226
+ is_databag: Optional[bool],
227
+ is_request_body: Optional[bool],
228
+ should_repr_strings: Optional[bool],
229
+ remaining_depth: Optional[Union[float, int]],
230
+ remaining_breadth: Optional[Union[float, int]],
231
+ ) -> Any:
246
232
  if isinstance(obj, AnnotatedValue):
247
233
  should_repr_strings = False
248
234
  if should_repr_strings is None:
@@ -306,7 +292,7 @@ def serialize(event, **kwargs):
306
292
  # might mutate our dictionary while we're still iterating over it.
307
293
  obj = dict(obj.items())
308
294
 
309
- rv_dict = {} # type: Dict[str, Any]
295
+ rv_dict: Dict[str, Any] = {}
310
296
  i = 0
311
297
 
312
298
  for k, v in obj.items():
sentry_sdk/session.py CHANGED
@@ -1,3 +1,4 @@
1
+ from __future__ import annotations
1
2
  import uuid
2
3
  from datetime import datetime, timezone
3
4
 
@@ -6,23 +7,15 @@ from sentry_sdk.utils import format_timestamp
6
7
  from typing import TYPE_CHECKING
7
8
 
8
9
  if TYPE_CHECKING:
9
- from typing import Optional
10
- from typing import Union
11
- from typing import Any
12
- from typing import Dict
13
-
14
10
  from sentry_sdk._types import SessionStatus
11
+ from typing import Optional, Union, Any, Dict
15
12
 
16
13
 
17
- def _minute_trunc(ts):
18
- # type: (datetime) -> datetime
14
+ def _minute_trunc(ts: datetime) -> datetime:
19
15
  return ts.replace(second=0, microsecond=0)
20
16
 
21
17
 
22
- def _make_uuid(
23
- val, # type: Union[str, uuid.UUID]
24
- ):
25
- # type: (...) -> uuid.UUID
18
+ def _make_uuid(val: Union[str, uuid.UUID]) -> uuid.UUID:
26
19
  if isinstance(val, uuid.UUID):
27
20
  return val
28
21
  return uuid.UUID(val)
@@ -31,21 +24,20 @@ def _make_uuid(
31
24
  class Session:
32
25
  def __init__(
33
26
  self,
34
- sid=None, # type: Optional[Union[str, uuid.UUID]]
35
- did=None, # type: Optional[str]
36
- timestamp=None, # type: Optional[datetime]
37
- started=None, # type: Optional[datetime]
38
- duration=None, # type: Optional[float]
39
- status=None, # type: Optional[SessionStatus]
40
- release=None, # type: Optional[str]
41
- environment=None, # type: Optional[str]
42
- user_agent=None, # type: Optional[str]
43
- ip_address=None, # type: Optional[str]
44
- errors=None, # type: Optional[int]
45
- user=None, # type: Optional[Any]
46
- session_mode="application", # type: str
47
- ):
48
- # type: (...) -> None
27
+ sid: Optional[Union[str, uuid.UUID]] = None,
28
+ did: Optional[str] = None,
29
+ timestamp: Optional[datetime] = None,
30
+ started: Optional[datetime] = None,
31
+ duration: Optional[float] = None,
32
+ status: Optional[SessionStatus] = None,
33
+ release: Optional[str] = None,
34
+ environment: Optional[str] = None,
35
+ user_agent: Optional[str] = None,
36
+ ip_address: Optional[str] = None,
37
+ errors: Optional[int] = None,
38
+ user: Optional[Any] = None,
39
+ session_mode: str = "application",
40
+ ) -> None:
49
41
  if sid is None:
50
42
  sid = uuid.uuid4()
51
43
  if started is None:
@@ -53,14 +45,14 @@ class Session:
53
45
  if status is None:
54
46
  status = "ok"
55
47
  self.status = status
56
- self.did = None # type: Optional[str]
48
+ self.did: Optional[str] = None
57
49
  self.started = started
58
- self.release = None # type: Optional[str]
59
- self.environment = None # type: Optional[str]
60
- self.duration = None # type: Optional[float]
61
- self.user_agent = None # type: Optional[str]
62
- self.ip_address = None # type: Optional[str]
63
- self.session_mode = session_mode # type: str
50
+ self.release: Optional[str] = None
51
+ self.environment: Optional[str] = None
52
+ self.duration: Optional[float] = None
53
+ self.user_agent: Optional[str] = None
54
+ self.ip_address: Optional[str] = None
55
+ self.session_mode: str = session_mode
64
56
  self.errors = 0
65
57
 
66
58
  self.update(
@@ -77,26 +69,24 @@ class Session:
77
69
  )
78
70
 
79
71
  @property
80
- def truncated_started(self):
81
- # type: (...) -> datetime
72
+ def truncated_started(self) -> datetime:
82
73
  return _minute_trunc(self.started)
83
74
 
84
75
  def update(
85
76
  self,
86
- sid=None, # type: Optional[Union[str, uuid.UUID]]
87
- did=None, # type: Optional[str]
88
- timestamp=None, # type: Optional[datetime]
89
- started=None, # type: Optional[datetime]
90
- duration=None, # type: Optional[float]
91
- status=None, # type: Optional[SessionStatus]
92
- release=None, # type: Optional[str]
93
- environment=None, # type: Optional[str]
94
- user_agent=None, # type: Optional[str]
95
- ip_address=None, # type: Optional[str]
96
- errors=None, # type: Optional[int]
97
- user=None, # type: Optional[Any]
98
- ):
99
- # type: (...) -> None
77
+ sid: Optional[Union[str, uuid.UUID]] = None,
78
+ did: Optional[str] = None,
79
+ timestamp: Optional[datetime] = None,
80
+ started: Optional[datetime] = None,
81
+ duration: Optional[float] = None,
82
+ status: Optional[SessionStatus] = None,
83
+ release: Optional[str] = None,
84
+ environment: Optional[str] = None,
85
+ user_agent: Optional[str] = None,
86
+ ip_address: Optional[str] = None,
87
+ errors: Optional[int] = None,
88
+ user: Optional[Any] = None,
89
+ ) -> None:
100
90
  # If a user is supplied we pull some data form it
101
91
  if user:
102
92
  if ip_address is None:
@@ -129,19 +119,13 @@ class Session:
129
119
  if status is not None:
130
120
  self.status = status
131
121
 
132
- def close(
133
- self, status=None # type: Optional[SessionStatus]
134
- ):
135
- # type: (...) -> Any
122
+ def close(self, status: Optional[SessionStatus] = None) -> Any:
136
123
  if status is None and self.status == "ok":
137
124
  status = "exited"
138
125
  if status is not None:
139
126
  self.update(status=status)
140
127
 
141
- def get_json_attrs(
142
- self, with_user_info=True # type: Optional[bool]
143
- ):
144
- # type: (...) -> Any
128
+ def get_json_attrs(self, with_user_info: bool = True) -> Any:
145
129
  attrs = {}
146
130
  if self.release is not None:
147
131
  attrs["release"] = self.release
@@ -154,15 +138,14 @@ class Session:
154
138
  attrs["user_agent"] = self.user_agent
155
139
  return attrs
156
140
 
157
- def to_json(self):
158
- # type: (...) -> Any
159
- rv = {
141
+ def to_json(self) -> Any:
142
+ rv: Dict[str, Any] = {
160
143
  "sid": str(self.sid),
161
144
  "init": True,
162
145
  "started": format_timestamp(self.started),
163
146
  "timestamp": format_timestamp(self.timestamp),
164
147
  "status": self.status,
165
- } # type: Dict[str, Any]
148
+ }
166
149
  if self.errors:
167
150
  rv["errors"] = self.errors
168
151
  if self.did is not None:
sentry_sdk/sessions.py CHANGED
@@ -1,6 +1,6 @@
1
+ from __future__ import annotations
1
2
  import os
2
- import time
3
- from threading import Thread, Lock
3
+ from threading import Thread, Lock, Event
4
4
  from contextlib import contextmanager
5
5
 
6
6
  import sentry_sdk
@@ -11,16 +11,17 @@ from sentry_sdk.utils import format_timestamp
11
11
  from typing import TYPE_CHECKING
12
12
 
13
13
  if TYPE_CHECKING:
14
- from typing import Any
15
- from typing import Callable
16
- from typing import Dict
17
- from typing import Generator
18
- from typing import List
19
- from typing import Optional
14
+ from typing import (
15
+ Any,
16
+ Callable,
17
+ Dict,
18
+ List,
19
+ Optional,
20
+ Generator,
21
+ )
20
22
 
21
23
 
22
- def _is_auto_session_tracking_enabled(scope):
23
- # type: (sentry_sdk.Scope) -> bool
24
+ def _is_auto_session_tracking_enabled(scope: sentry_sdk.Scope) -> bool:
24
25
  """
25
26
  Utility function to find out if session tracking is enabled.
26
27
  """
@@ -34,8 +35,9 @@ def _is_auto_session_tracking_enabled(scope):
34
35
 
35
36
 
36
37
  @contextmanager
37
- def track_session(scope, session_mode="application"):
38
- # type: (sentry_sdk.Scope, str) -> Generator[None, None, None]
38
+ def track_session(
39
+ scope: sentry_sdk.Scope, session_mode: str = "application"
40
+ ) -> Generator[None, None, None]:
39
41
  """
40
42
  Start a new session in the provided scope, assuming session tracking is enabled.
41
43
  This is a no-op context manager if session tracking is not enabled.
@@ -55,30 +57,27 @@ TERMINAL_SESSION_STATES = ("exited", "abnormal", "crashed")
55
57
  MAX_ENVELOPE_ITEMS = 100
56
58
 
57
59
 
58
- def make_aggregate_envelope(aggregate_states, attrs):
59
- # type: (Any, Any) -> Any
60
+ def make_aggregate_envelope(aggregate_states: Any, attrs: Any) -> Any:
60
61
  return {"attrs": dict(attrs), "aggregates": list(aggregate_states.values())}
61
62
 
62
63
 
63
64
  class SessionFlusher:
64
65
  def __init__(
65
66
  self,
66
- capture_func, # type: Callable[[Envelope], None]
67
- flush_interval=60, # type: int
68
- ):
69
- # type: (...) -> None
67
+ capture_func: Callable[[Envelope], None],
68
+ flush_interval: int = 60,
69
+ ) -> None:
70
70
  self.capture_func = capture_func
71
71
  self.flush_interval = flush_interval
72
- self.pending_sessions = [] # type: List[Any]
73
- self.pending_aggregates = {} # type: Dict[Any, Any]
74
- self._thread = None # type: Optional[Thread]
72
+ self.pending_sessions: List[Any] = []
73
+ self.pending_aggregates: Dict[Any, Any] = {}
74
+ self._thread: Optional[Thread] = None
75
75
  self._thread_lock = Lock()
76
76
  self._aggregate_lock = Lock()
77
- self._thread_for_pid = None # type: Optional[int]
78
- self._running = True
77
+ self._thread_for_pid: Optional[int] = None
78
+ self.__shutdown_requested: Event = Event()
79
79
 
80
- def flush(self):
81
- # type: (...) -> None
80
+ def flush(self) -> None:
82
81
  pending_sessions = self.pending_sessions
83
82
  self.pending_sessions = []
84
83
 
@@ -104,8 +103,7 @@ class SessionFlusher:
104
103
  if len(envelope.items) > 0:
105
104
  self.capture_func(envelope)
106
105
 
107
- def _ensure_running(self):
108
- # type: (...) -> None
106
+ def _ensure_running(self) -> None:
109
107
  """
110
108
  Check that we have an active thread to run in, or create one if not.
111
109
 
@@ -119,12 +117,11 @@ class SessionFlusher:
119
117
  if self._thread_for_pid == os.getpid() and self._thread is not None:
120
118
  return None
121
119
 
122
- def _thread():
123
- # type: (...) -> None
124
- while self._running:
125
- time.sleep(self.flush_interval)
126
- if self._running:
127
- self.flush()
120
+ def _thread() -> None:
121
+ running = True
122
+ while running:
123
+ running = not self.__shutdown_requested.wait(self.flush_interval)
124
+ self.flush()
128
125
 
129
126
  thread = Thread(target=_thread)
130
127
  thread.daemon = True
@@ -133,7 +130,7 @@ class SessionFlusher:
133
130
  except RuntimeError:
134
131
  # Unfortunately at this point the interpreter is in a state that no
135
132
  # longer allows us to spawn a thread and we have to bail.
136
- self._running = False
133
+ self.__shutdown_requested.set()
137
134
  return None
138
135
 
139
136
  self._thread = thread
@@ -141,10 +138,7 @@ class SessionFlusher:
141
138
 
142
139
  return None
143
140
 
144
- def add_aggregate_session(
145
- self, session # type: Session
146
- ):
147
- # type: (...) -> None
141
+ def add_aggregate_session(self, session: Session) -> None:
148
142
  # NOTE on `session.did`:
149
143
  # the protocol can deal with buckets that have a distinct-id, however
150
144
  # in practice we expect the python SDK to have an extremely high cardinality
@@ -172,20 +166,12 @@ class SessionFlusher:
172
166
  else:
173
167
  state["exited"] = state.get("exited", 0) + 1
174
168
 
175
- def add_session(
176
- self, session # type: Session
177
- ):
178
- # type: (...) -> None
169
+ def add_session(self, session: Session) -> None:
179
170
  if session.session_mode == "request":
180
171
  self.add_aggregate_session(session)
181
172
  else:
182
173
  self.pending_sessions.append(session.to_json())
183
174
  self._ensure_running()
184
175
 
185
- def kill(self):
186
- # type: (...) -> None
187
- self._running = False
188
-
189
- def __del__(self):
190
- # type: (...) -> None
191
- self.kill()
176
+ def kill(self) -> None:
177
+ self.__shutdown_requested.set()