8000 feat: Bottle integration (#300) · pythonthings/sentry-python@8e63b3f · GitHub
[go: up one dir, main page]

Skip to content

Commit 8e63b3f

Browse files
shenekuntitaker
authored andcommitted
feat: Bottle integration (getsentry#300)
Add Bottle integration based on Flask integration.
1 parent 8122005 commit 8e63b3f

File tree

5 files changed

+602
-2
lines changed

5 files changed

+602
-2
lines changed

sentry_sdk/integrations/bottle.py

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
from __future__ import absolute_import
2+
3+
from sentry_sdk.hub import Hub
4+
from sentry_sdk.utils import (
5+
capture_internal_exceptions,
6+
event_from_exception,
7+
transaction_from_function,
8+
)
9+
from sentry_sdk.integrations import Integration
10+
from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware
11+
from sentry_sdk.integrations._wsgi_common import RequestExtractor
12+
13+
if False:
14+
from sentry_sdk.integrations.wsgi import _ScopedResponse
15+
from typing import Any
16+
from typing import Dict
17+
from typing import Callable
18+
from typing import Optional
19+
from bottle import FileUpload, FormsDict, LocalRequest # type: ignore
20+
21+
from bottle import Bottle, Route, request as bottle_request # type: ignore
22+
23+
24+
class BottleIntegration(Integration):
25+
identifier = "bottle"
26+
27+
transaction_style = None
28+
29+
def __init__(self, transaction_style="endpoint"):
30+
# type: (str) -> None
31+
TRANSACTION_STYLE_VALUES = ("endpoint", "url")
32+
if transaction_style not in TRANSACTION_STYLE_VALUES:
33+
raise ValueError(
34+
"Invalid value for transaction_style: %s (must be in %s)"
35+
% (transaction_style, TRANSACTION_STYLE_VALUES)
36+
)
37+
self.transaction_style = transaction_style
38+
39+
@staticmethod
40+
def setup_once():
41+
# type: () -> None
42+
43+
# monkey patch method Bottle.__call__
44+
old_app = Bottle.__call__
45+
46+
def sentry_patched_wsgi_app(self, environ, start_response):
47+
# type: (Any, Dict[str, str], Callable) -> _ScopedResponse
48+
49+
h 8000 ub = Hub.current
50+
integration = hub.get_integration(BottleIntegration)
51+
if integration is None:
52+
return old_app(self, environ, start_response)
53+
54+
return SentryWsgiMiddleware(lambda *a, **kw: old_app(self, *a, **kw))(
55+
environ, start_response
56+
)
57+
58+
Bottle.__call__ = sentry_patched_wsgi_app # type: ignore
59+
60+
# monkey patch method Bottle._handle
61+
old_handle = Bottle._handle
62+
63+
def _patched_handle(self, environ):
64+
hub = Hub.current
65+
integration = hub.get_integration(BottleIntegration)
66+
if integration is None:
67+
return old_handle(self, environ)
68+
69+
# create new scope
70+
scope_manager = hub.push_scope()
71+
72+
with scope_manager:
73+
app = self
74+
with hub.configure_scope() as scope:
75+
scope._name = "bottle"
76+
scope.add_event_processor(
77+
_make_request_event_processor(app, bottle_request, integration)
78+
)
79+
res = old_handle(self, environ)
80+
81+
# scope cleanup
82+
return res
83+
84+
Bottle._handle = _patched_handle
85+
86+
# monkey patch method Route._make_callback
87+
old_make_callback = Route._make_callback
88+
89+
def patched_make_callback(self, *args, **kwargs):
90+
hub = Hub.current
91+
integration = hub.get_integration(BottleIntegration)
92+
prepared_callback = old_make_callback(self, *args, **kwargs)
93+
if integration is None:
94+
return prepared_callback
95+
96+
def wrapped_callback(*args, **kwargs):
97+
try:
98+
res = prepared_callback(*args, **kwargs)
99+
except Exception as exception:
100+
hub = Hub.current
101+
event, hint = event_from_exception(
102+
exception,
103+
client_options=hub.client.options,
104+
mechanism={"type": "bottle", "handled": False},
105+
)
106+
hub.capture_event(event, hint=hint)
107+
raise exception
108+
109+
return res
110+
111+
return wrapped_callback
112+
113+
Route._make_callback = patched_make_callback
114+
115+
116+
class BottleRequestExtractor(RequestExtractor):
117+
def env(self):
118+
# type: () -> Dict[str, str]
119+
return self.request.environ
120+
121+
def cookies(self):
122+
# type: () -> Dict[str, str]
123+
return self.request.cookies
124+
125+
def raw_data(self):
126+
# type: () -> bytes
127+
return self.request.body.read()
128+
129+
def form(self):
130+
# type: () -> FormsDict
131+
if self.is_json():
132+
return None
133+
return self.request.forms.decode()
134+
135+
def files(self):
136+
# type: () -> Optional[Dict[str, str]]
137+
if self.is_json():
138+
return None
139+
140+
return self.request.files
141+
142+
def size_of_file(self, file):
143+
# type: (FileUpload) -> int
144+
return file.content_length
145+
146+
147+
def _make_request_event_processor(app, request, integration):
148+
# type: (Bottle, LocalRequest, BottleIntegration) -> Callable
149+
def inner(event, hint):
150+
# type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any]
151+
152+
try:
153+
if integration.transaction_style == "endpoint":
154+
event["transaction"] = request.route.name or transaction_from_function(
155+
request.route.callback
156+
)
157+
elif integration.transaction_style == "url":
158+
event["transaction"] = request.route.rule # type: ignore
159+
except Exception:
160+
pass
161+
162+
with capture_internal_exceptions():
163+
BottleRequestExtractor(request).extract_into_event(event)
164+
165+
return event
166+
167+
return inner

setup.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222
zip_safe=False,
2323
license="BSD",
2424
install_requires=["urllib3", "certifi"],
25-
extras_require={"flask": ["flask>=0.8", "blinker>=1.1"]},
25+
extras_require={
26+
"flask": ["flask>=0.8", "blinker>=1.1"],
27+
"bottle": ["bottle>=0.12.13"],
28+
},
2629
classifiers=[
2730
"Development Status :: 5 - Production/Stable",
2831
"Environment :: Web Environment",

test-requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ pytest-xdist==1.23.0
44
tox==3.7.0
55
Werkzeug==0.14.1
66
pytest-localserver==0.4.1
7-
pytest-cov==2.6.0
7+
pytest-cov==2.6.0

0 commit comments

Comments
 (0)
0