Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/sentry_sdk/integrations/bottle.py: 10%
111 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1from __future__ import absolute_import
3from sentry_sdk.hub import Hub
4from sentry_sdk.tracing import SOURCE_FOR_STYLE
5from sentry_sdk.utils import (
6 capture_internal_exceptions,
7 event_from_exception,
8 transaction_from_function,
9)
10from sentry_sdk.integrations import Integration, DidNotEnable
11from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware
12from sentry_sdk.integrations._wsgi_common import RequestExtractor
14from sentry_sdk._types import MYPY
16if MYPY: 16 ↛ 17line 16 didn't jump to line 17, because the condition on line 16 was never true
17 from sentry_sdk.integrations.wsgi import _ScopedResponse
18 from typing import Any
19 from typing import Dict
20 from typing import Callable
21 from typing import Optional
22 from bottle import FileUpload, FormsDict, LocalRequest # type: ignore
24 from sentry_sdk._types import EventProcessor, Event
26try:
27 from bottle import (
28 Bottle,
29 Route,
30 request as bottle_request,
31 HTTPResponse,
32 __version__ as BOTTLE_VERSION,
33 )
34except ImportError:
35 raise DidNotEnable("Bottle not installed")
38TRANSACTION_STYLE_VALUES = ("endpoint", "url")
41class BottleIntegration(Integration):
42 identifier = "bottle"
44 transaction_style = ""
46 def __init__(self, transaction_style="endpoint"):
47 # type: (str) -> None
49 if transaction_style not in TRANSACTION_STYLE_VALUES:
50 raise ValueError(
51 "Invalid value for transaction_style: %s (must be in %s)"
52 % (transaction_style, TRANSACTION_STYLE_VALUES)
53 )
54 self.transaction_style = transaction_style
56 @staticmethod
57 def setup_once():
58 # type: () -> None
60 try:
61 version = tuple(map(int, BOTTLE_VERSION.replace("-dev", "").split(".")))
62 except (TypeError, ValueError):
63 raise DidNotEnable("Unparsable Bottle version: {}".format(version))
65 if version < (0, 12):
66 raise DidNotEnable("Bottle 0.12 or newer required.")
68 # monkey patch method Bottle.__call__
69 old_app = Bottle.__call__
71 def sentry_patched_wsgi_app(self, environ, start_response):
72 # type: (Any, Dict[str, str], Callable[..., Any]) -> _ScopedResponse
74 hub = Hub.current
75 integration = hub.get_integration(BottleIntegration)
76 if integration is None:
77 return old_app(self, environ, start_response)
79 return SentryWsgiMiddleware(lambda *a, **kw: old_app(self, *a, **kw))(
80 environ, start_response
81 )
83 Bottle.__call__ = sentry_patched_wsgi_app
85 # monkey patch method Bottle._handle
86 old_handle = Bottle._handle
88 def _patched_handle(self, environ):
89 # type: (Bottle, Dict[str, Any]) -> Any
90 hub = Hub.current
91 integration = hub.get_integration(BottleIntegration)
92 if integration is None:
93 return old_handle(self, environ)
95 # create new scope
96 scope_manager = hub.push_scope()
98 with scope_manager:
99 app = self
100 with hub.configure_scope() as scope:
101 scope._name = "bottle"
102 scope.add_event_processor(
103 _make_request_event_processor(app, bottle_request, integration)
104 )
105 res = old_handle(self, environ)
107 # scope cleanup
108 return res
110 Bottle._handle = _patched_handle
112 # monkey patch method Route._make_callback
113 old_make_callback = Route._make_callback
115 def patched_make_callback(self, *args, **kwargs):
116 # type: (Route, *object, **object) -> Any
117 hub = Hub.current
118 integration = hub.get_integration(BottleIntegration)
119 prepared_callback = old_make_callback(self, *args, **kwargs)
120 if integration is None:
121 return prepared_callback
123 # If an integration is there, a client has to be there.
124 client = hub.client # type: Any
126 def wrapped_callback(*args, **kwargs):
127 # type: (*object, **object) -> Any
129 try:
130 res = prepared_callback(*args, **kwargs)
131 except HTTPResponse:
132 raise
133 except Exception as exception:
134 event, hint = event_from_exception(
135 exception,
136 client_options=client.options,
137 mechanism={"type": "bottle", "handled": False},
138 )
139 hub.capture_event(event, hint=hint)
140 raise exception
142 return res
144 return wrapped_callback
146 Route._make_callback = patched_make_callback
149class BottleRequestExtractor(RequestExtractor):
150 def env(self):
151 # type: () -> Dict[str, str]
152 return self.request.environ
154 def cookies(self):
155 # type: () -> Dict[str, str]
156 return self.request.cookies
158 def raw_data(self):
159 # type: () -> bytes
160 return self.request.body.read()
162 def form(self):
163 # type: () -> FormsDict
164 if self.is_json():
165 return None
166 return self.request.forms.decode()
168 def files(self):
169 # type: () -> Optional[Dict[str, str]]
170 if self.is_json():
171 return None
173 return self.request.files
175 def size_of_file(self, file):
176 # type: (FileUpload) -> int
177 return file.content_length
180def _set_transaction_name_and_source(event, transaction_style, request):
181 # type: (Event, str, Any) -> None
182 name = ""
184 if transaction_style == "url":
185 name = request.route.rule or ""
187 elif transaction_style == "endpoint":
188 name = (
189 request.route.name
190 or transaction_from_function(request.route.callback)
191 or ""
192 )
194 event["transaction"] = name
195 event["transaction_info"] = {"source": SOURCE_FOR_STYLE[transaction_style]}
198def _make_request_event_processor(app, request, integration):
199 # type: (Bottle, LocalRequest, BottleIntegration) -> EventProcessor
201 def event_processor(event, hint):
202 # type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any]
203 _set_transaction_name_and_source(event, integration.transaction_style, request)
205 with capture_internal_exceptions():
206 BottleRequestExtractor(request).extract_into_event(event)
208 return event
210 return event_processor