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
@@ -0,0 +1,176 @@
1
+ import sys
2
+ import types
3
+ from functools import wraps
4
+
5
+ import sentry_sdk
6
+ from sentry_sdk.integrations import Integration
7
+ from sentry_sdk.integrations.logging import ignore_logger
8
+ from sentry_sdk.utils import (
9
+ capture_internal_exceptions,
10
+ ensure_integration_enabled,
11
+ event_from_exception,
12
+ reraise,
13
+ )
14
+
15
+ from typing import TYPE_CHECKING
16
+
17
+ if TYPE_CHECKING:
18
+ from typing import Any
19
+ from typing import Iterator
20
+ from typing import TypeVar
21
+ from typing import Callable
22
+
23
+ from sentry_sdk._types import ExcInfo
24
+
25
+ T = TypeVar("T")
26
+ F = TypeVar("F", bound=Callable[..., Any])
27
+
28
+
29
+ WRAPPED_FUNC = "_wrapped_{}_"
30
+ INSPECT_FUNC = "_inspect_{}" # Required format per apache_beam/transforms/core.py
31
+ USED_FUNC = "_sentry_used_"
32
+
33
+
34
+ class BeamIntegration(Integration):
35
+ identifier = "beam"
36
+
37
+ @staticmethod
38
+ def setup_once():
39
+ # type: () -> None
40
+ from apache_beam.transforms.core import DoFn, ParDo # type: ignore
41
+
42
+ ignore_logger("root")
43
+ ignore_logger("bundle_processor.create")
44
+
45
+ function_patches = ["process", "start_bundle", "finish_bundle", "setup"]
46
+ for func_name in function_patches:
47
+ setattr(
48
+ DoFn,
49
+ INSPECT_FUNC.format(func_name),
50
+ _wrap_inspect_call(DoFn, func_name),
51
+ )
52
+
53
+ old_init = ParDo.__init__
54
+
55
+ def sentry_init_pardo(self, fn, *args, **kwargs):
56
+ # type: (ParDo, Any, *Any, **Any) -> Any
57
+ # Do not monkey patch init twice
58
+ if not getattr(self, "_sentry_is_patched", False):
59
+ for func_name in function_patches:
60
+ if not hasattr(fn, func_name):
61
+ continue
62
+ wrapped_func = WRAPPED_FUNC.format(func_name)
63
+
64
+ # Check to see if inspect is set and process is not
65
+ # to avoid monkey patching process twice.
66
+ # Check to see if function is part of object for
67
+ # backwards compatibility.
68
+ process_func = getattr(fn, func_name)
69
+ inspect_func = getattr(fn, INSPECT_FUNC.format(func_name))
70
+ if not getattr(inspect_func, USED_FUNC, False) and not getattr(
71
+ process_func, USED_FUNC, False
72
+ ):
73
+ setattr(fn, wrapped_func, process_func)
74
+ setattr(fn, func_name, _wrap_task_call(process_func))
75
+
76
+ self._sentry_is_patched = True
77
+ old_init(self, fn, *args, **kwargs)
78
+
79
+ ParDo.__init__ = sentry_init_pardo
80
+
81
+
82
+ def _wrap_inspect_call(cls, func_name):
83
+ # type: (Any, Any) -> Any
84
+
85
+ if not hasattr(cls, func_name):
86
+ return None
87
+
88
+ def _inspect(self):
89
+ # type: (Any) -> Any
90
+ """
91
+ Inspect function overrides the way Beam gets argspec.
92
+ """
93
+ wrapped_func = WRAPPED_FUNC.format(func_name)
94
+ if hasattr(self, wrapped_func):
95
+ process_func = getattr(self, wrapped_func)
96
+ else:
97
+ process_func = getattr(self, func_name)
98
+ setattr(self, func_name, _wrap_task_call(process_func))
99
+ setattr(self, wrapped_func, process_func)
100
+
101
+ # getfullargspec is deprecated in more recent beam versions and get_function_args_defaults
102
+ # (which uses Signatures internally) should be used instead.
103
+ try:
104
+ from apache_beam.transforms.core import get_function_args_defaults
105
+
106
+ return get_function_args_defaults(process_func)
107
+ except ImportError:
108
+ from apache_beam.typehints.decorators import getfullargspec # type: ignore
109
+
110
+ return getfullargspec(process_func)
111
+
112
+ setattr(_inspect, USED_FUNC, True)
113
+ return _inspect
114
+
115
+
116
+ def _wrap_task_call(func):
117
+ # type: (F) -> F
118
+ """
119
+ Wrap task call with a try catch to get exceptions.
120
+ """
121
+
122
+ @wraps(func)
123
+ def _inner(*args, **kwargs):
124
+ # type: (*Any, **Any) -> Any
125
+ try:
126
+ gen = func(*args, **kwargs)
127
+ except Exception:
128
+ raise_exception()
129
+
130
+ if not isinstance(gen, types.GeneratorType):
131
+ return gen
132
+ return _wrap_generator_call(gen)
133
+
134
+ setattr(_inner, USED_FUNC, True)
135
+ return _inner # type: ignore
136
+
137
+
138
+ @ensure_integration_enabled(BeamIntegration)
139
+ def _capture_exception(exc_info):
140
+ # type: (ExcInfo) -> None
141
+ """
142
+ Send Beam exception to Sentry.
143
+ """
144
+ client = sentry_sdk.get_client()
145
+
146
+ event, hint = event_from_exception(
147
+ exc_info,
148
+ client_options=client.options,
149
+ mechanism={"type": "beam", "handled": False},
150
+ )
151
+ sentry_sdk.capture_event(event, hint=hint)
152
+
153
+
154
+ def raise_exception():
155
+ # type: () -> None
156
+ """
157
+ Raise an exception.
158
+ """
159
+ exc_info = sys.exc_info()
160
+ with capture_internal_exceptions():
161
+ _capture_exception(exc_info)
162
+ reraise(*exc_info)
163
+
164
+
165
+ def _wrap_generator_call(gen):
166
+ # type: (Iterator[T]) -> Iterator[T]
167
+ """
168
+ Wrap the generator to handle any failures.
169
+ """
170
+ while True:
171
+ try:
172
+ yield next(gen)
173
+ except StopIteration:
174
+ break
175
+ except Exception:
176
+ raise_exception()
@@ -0,0 +1,137 @@
1
+ from functools import partial
2
+
3
+ import sentry_sdk
4
+ from sentry_sdk.consts import OP, SPANDATA
5
+ from sentry_sdk.integrations import _check_minimum_version, Integration, DidNotEnable
6
+ from sentry_sdk.tracing import Span
7
+ from sentry_sdk.utils import (
8
+ capture_internal_exceptions,
9
+ ensure_integration_enabled,
10
+ parse_url,
11
+ parse_version,
12
+ )
13
+
14
+ from typing import TYPE_CHECKING
15
+
16
+ if TYPE_CHECKING:
17
+ from typing import Any
18
+ from typing import Dict
19
+ from typing import Optional
20
+ from typing import Type
21
+
22
+ try:
23
+ from botocore import __version__ as BOTOCORE_VERSION # type: ignore
24
+ from botocore.client import BaseClient # type: ignore
25
+ from botocore.response import StreamingBody # type: ignore
26
+ from botocore.awsrequest import AWSRequest # type: ignore
27
+ except ImportError:
28
+ raise DidNotEnable("botocore is not installed")
29
+
30
+
31
+ class Boto3Integration(Integration):
32
+ identifier = "boto3"
33
+ origin = f"auto.http.{identifier}"
34
+
35
+ @staticmethod
36
+ def setup_once():
37
+ # type: () -> None
38
+ version = parse_version(BOTOCORE_VERSION)
39
+ _check_minimum_version(Boto3Integration, version, "botocore")
40
+
41
+ orig_init = BaseClient.__init__
42
+
43
+ def sentry_patched_init(self, *args, **kwargs):
44
+ # type: (Type[BaseClient], *Any, **Any) -> None
45
+ orig_init(self, *args, **kwargs)
46
+ meta = self.meta
47
+ service_id = meta.service_model.service_id.hyphenize()
48
+ meta.events.register(
49
+ "request-created",
50
+ partial(_sentry_request_created, service_id=service_id),
51
+ )
52
+ meta.events.register("after-call", _sentry_after_call)
53
+ meta.events.register("after-call-error", _sentry_after_call_error)
54
+
55
+ BaseClient.__init__ = sentry_patched_init
56
+
57
+
58
+ @ensure_integration_enabled(Boto3Integration)
59
+ def _sentry_request_created(service_id, request, operation_name, **kwargs):
60
+ # type: (str, AWSRequest, str, **Any) -> None
61
+ description = "aws.%s.%s" % (service_id, operation_name)
62
+ span = sentry_sdk.start_span(
63
+ op=OP.HTTP_CLIENT,
64
+ name=description,
65
+ origin=Boto3Integration.origin,
66
+ )
67
+
68
+ with capture_internal_exceptions():
69
+ parsed_url = parse_url(request.url, sanitize=False)
70
+ span.set_data("aws.request.url", parsed_url.url)
71
+ span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query)
72
+ span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment)
73
+
74
+ span.set_tag("aws.service_id", service_id)
75
+ span.set_tag("aws.operation_name", operation_name)
76
+ span.set_data(SPANDATA.HTTP_METHOD, request.method)
77
+
78
+ # We do it in order for subsequent http calls/retries be
79
+ # attached to this span.
80
+ span.__enter__()
81
+
82
+ # request.context is an open-ended data-structure
83
+ # where we can add anything useful in request life cycle.
84
+ request.context["_sentrysdk_span"] = span
85
+
86
+
87
+ def _sentry_after_call(context, parsed, **kwargs):
88
+ # type: (Dict[str, Any], Dict[str, Any], **Any) -> None
89
+ span = context.pop("_sentrysdk_span", None) # type: Optional[Span]
90
+
91
+ # Span could be absent if the integration is disabled.
92
+ if span is None:
93
+ return
94
+ span.__exit__(None, None, None)
95
+
96
+ body = parsed.get("Body")
97
+ if not isinstance(body, StreamingBody):
98
+ return
99
+
100
+ streaming_span = span.start_child(
101
+ op=OP.HTTP_CLIENT_STREAM,
102
+ name=span.description,
103
+ origin=Boto3Integration.origin,
104
+ )
105
+
106
+ orig_read = body.read
107
+ orig_close = body.close
108
+
109
+ def sentry_streaming_body_read(*args, **kwargs):
110
+ # type: (*Any, **Any) -> bytes
111
+ try:
112
+ ret = orig_read(*args, **kwargs)
113
+ if not ret:
114
+ streaming_span.finish()
115
+ return ret
116
+ except Exception:
117
+ streaming_span.finish()
118
+ raise
119
+
120
+ body.read = sentry_streaming_body_read
121
+
122
+ def sentry_streaming_body_close(*args, **kwargs):
123
+ # type: (*Any, **Any) -> None
124
+ streaming_span.finish()
125
+ orig_close(*args, **kwargs)
126
+
127
+ body.close = sentry_streaming_body_close
128
+
129
+
130
+ def _sentry_after_call_error(context, exception, **kwargs):
131
+ # type: (Dict[str, Any], Type[BaseException], **Any) -> None
132
+ span = context.pop("_sentrysdk_span", None) # type: Optional[Span]
133
+
134
+ # Span could be absent if the integration is disabled.
135
+ if span is None:
136
+ return
137
+ span.__exit__(type(exception), exception, None)
@@ -0,0 +1,221 @@
1
+ import functools
2
+
3
+ import sentry_sdk
4
+ from sentry_sdk.tracing import SOURCE_FOR_STYLE
5
+ from sentry_sdk.utils import (
6
+ capture_internal_exceptions,
7
+ ensure_integration_enabled,
8
+ event_from_exception,
9
+ parse_version,
10
+ transaction_from_function,
11
+ )
12
+ from sentry_sdk.integrations import (
13
+ Integration,
14
+ DidNotEnable,
15
+ _DEFAULT_FAILED_REQUEST_STATUS_CODES,
16
+ _check_minimum_version,
17
+ )
18
+ from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware
19
+ from sentry_sdk.integrations._wsgi_common import RequestExtractor
20
+
21
+ from typing import TYPE_CHECKING
22
+
23
+ if TYPE_CHECKING:
24
+ from collections.abc import Set
25
+
26
+ from sentry_sdk.integrations.wsgi import _ScopedResponse
27
+ from typing import Any
28
+ from typing import Dict
29
+ from typing import Callable
30
+ from typing import Optional
31
+ from bottle import FileUpload, FormsDict, LocalRequest # type: ignore
32
+
33
+ from sentry_sdk._types import EventProcessor, Event
34
+
35
+ try:
36
+ from bottle import (
37
+ Bottle,
38
+ HTTPResponse,
39
+ Route,
40
+ request as bottle_request,
41
+ __version__ as BOTTLE_VERSION,
42
+ )
43
+ except ImportError:
44
+ raise DidNotEnable("Bottle not installed")
45
+
46
+
47
+ TRANSACTION_STYLE_VALUES = ("endpoint", "url")
48
+
49
+
50
+ class BottleIntegration(Integration):
51
+ identifier = "bottle"
52
+ origin = f"auto.http.{identifier}"
53
+
54
+ transaction_style = ""
55
+
56
+ def __init__(
57
+ self,
58
+ transaction_style="endpoint", # type: str
59
+ *,
60
+ failed_request_status_codes=_DEFAULT_FAILED_REQUEST_STATUS_CODES, # type: Set[int]
61
+ ):
62
+ # type: (...) -> None
63
+
64
+ if transaction_style not in TRANSACTION_STYLE_VALUES:
65
+ raise ValueError(
66
+ "Invalid value for transaction_style: %s (must be in %s)"
67
+ % (transaction_style, TRANSACTION_STYLE_VALUES)
68
+ )
69
+ self.transaction_style = transaction_style
70
+ self.failed_request_status_codes = failed_request_status_codes
71
+
72
+ @staticmethod
73
+ def setup_once():
74
+ # type: () -> None
75
+ version = parse_version(BOTTLE_VERSION)
76
+ _check_minimum_version(BottleIntegration, version)
77
+
78
+ old_app = Bottle.__call__
79
+
80
+ @ensure_integration_enabled(BottleIntegration, old_app)
81
+ def sentry_patched_wsgi_app(self, environ, start_response):
82
+ # type: (Any, Dict[str, str], Callable[..., Any]) -> _ScopedResponse
83
+ middleware = SentryWsgiMiddleware(
84
+ lambda *a, **kw: old_app(self, *a, **kw),
85
+ span_origin=BottleIntegration.origin,
86
+ )
87
+
88
+ return middleware(environ, start_response)
89
+
90
+ Bottle.__call__ = sentry_patched_wsgi_app
91
+
92
+ old_handle = Bottle._handle
93
+
94
+ @functools.wraps(old_handle)
95
+ def _patched_handle(self, environ):
96
+ # type: (Bottle, Dict[str, Any]) -> Any
97
+ integration = sentry_sdk.get_client().get_integration(BottleIntegration)
98
+ if integration is None:
99
+ return old_handle(self, environ)
100
+
101
+ scope = sentry_sdk.get_isolation_scope()
102
+ scope._name = "bottle"
103
+ scope.add_event_processor(
104
+ _make_request_event_processor(self, bottle_request, integration)
105
+ )
106
+ res = old_handle(self, environ)
107
+
108
+ return res
109
+
110
+ Bottle._handle = _patched_handle
111
+
112
+ old_make_callback = Route._make_callback
113
+
114
+ @functools.wraps(old_make_callback)
115
+ def patched_make_callback(self, *args, **kwargs):
116
+ # type: (Route, *object, **object) -> Any
117
+ prepared_callback = old_make_callback(self, *args, **kwargs)
118
+
119
+ integration = sentry_sdk.get_client().get_integration(BottleIntegration)
120
+ if integration is None:
121
+ return prepared_callback
122
+
123
+ def wrapped_callback(*args, **kwargs):
124
+ # type: (*object, **object) -> Any
125
+ try:
126
+ res = prepared_callback(*args, **kwargs)
127
+ except Exception as exception:
128
+ _capture_exception(exception, handled=False)
129
+ raise exception
130
+
131
+ if (
132
+ isinstance(res, HTTPResponse)
133
+ and res.status_code in integration.failed_request_status_codes
134
+ ):
135
+ _capture_exception(res, handled=True)
136
+
137
+ return res
138
+
139
+ return wrapped_callback
140
+
141
+ Route._make_callback = patched_make_callback
142
+
143
+
144
+ class BottleRequestExtractor(RequestExtractor):
145
+ def env(self):
146
+ # type: () -> Dict[str, str]
147
+ return self.request.environ
148
+
149
+ def cookies(self):
150
+ # type: () -> Dict[str, str]
151
+ return self.request.cookies
152
+
153
+ def raw_data(self):
154
+ # type: () -> bytes
155
+ return self.request.body.read()
156
+
157
+ def form(self):
158
+ # type: () -> FormsDict
159
+ if self.is_json():
160
+ return None
161
+ return self.request.forms.decode()
162
+
163
+ def files(self):
164
+ # type: () -> Optional[Dict[str, str]]
165
+ if self.is_json():
166
+ return None
167
+
168
+ return self.request.files
169
+
170
+ def size_of_file(self, file):
171
+ # type: (FileUpload) -> int
172
+ return file.content_length
173
+
174
+
175
+ def _set_transaction_name_and_source(event, transaction_style, request):
176
+ # type: (Event, str, Any) -> None
177
+ name = ""
178
+
179
+ if transaction_style == "url":
180
+ try:
181
+ name = request.route.rule or ""
182
+ except RuntimeError:
183
+ pass
184
+
185
+ elif transaction_style == "endpoint":
186
+ try:
187
+ name = (
188
+ request.route.name
189
+ or transaction_from_function(request.route.callback)
190
+ or ""
191
+ )
192
+ except RuntimeError:
193
+ pass
194
+
195
+ event["transaction"] = name
196
+ event["transaction_info"] = {"source": SOURCE_FOR_STYLE[transaction_style]}
197
+
198
+
199
+ def _make_request_event_processor(app, request, integration):
200
+ # type: (Bottle, LocalRequest, BottleIntegration) -> EventProcessor
201
+
202
+ def event_processor(event, hint):
203
+ # type: (Event, dict[str, Any]) -> Event
204
+ _set_transaction_name_and_source(event, integration.transaction_style, request)
205
+
206
+ with capture_internal_exceptions():
207
+ BottleRequestExtractor(request).extract_into_event(event)
208
+
209
+ return event
210
+
211
+ return event_processor
212
+
213
+
214
+ def _capture_exception(exception, handled):
215
+ # type: (BaseException, bool) -> None
216
+ event, hint = event_from_exception(
217
+ exception,
218
+ client_options=sentry_sdk.get_client().options,
219
+ mechanism={"type": "bottle", "handled": handled},
220
+ )
221
+ sentry_sdk.capture_event(event, hint=hint)