Skip to content

Commit a42dd6e

Browse files
authored
ref: Refactor transport interface and fix hub init (#44)
1 parent e808656 commit a42dd6e

File tree

5 files changed

+93
-47
lines changed

5 files changed

+93
-47
lines changed

sentry_sdk/client.py

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,11 @@
1111
convert_types,
1212
handle_in_app,
1313
get_type_name,
14-
Dsn,
1514
)
16-
from .transport import Transport
15+
from .transport import make_transport
1716
from .consts import DEFAULT_OPTIONS, SDK_INFO
1817

1918

20-
NO_DSN = object()
21-
22-
2319
def get_options(*args, **kwargs):
2420
if args and (isinstance(args[0], string_types) or args[0] is None):
2521
dsn = args[0]
@@ -45,21 +41,8 @@ def get_options(*args, **kwargs):
4541

4642
class Client(object):
4743
def __init__(self, *args, **kwargs):
48-
options = get_options(*args, **kwargs)
49-
50-
dsn = options["dsn"]
51-
if dsn is not None:
52-
dsn = Dsn(dsn)
53-
54-
self.options = options
55-
self._transport = self.options.pop("transport")
56-
if self._transport is None and dsn is not None:
57-
self._transport = Transport(
58-
dsn=dsn,
59-
http_proxy=self.options.pop("http_proxy"),
60-
https_proxy=self.options.pop("https_proxy"),
61-
)
62-
self._transport.start()
44+
self.options = options = get_options(*args, **kwargs)
45+
self._transport = make_transport(options)
6346

6447
request_bodies = ("always", "never", "small", "medium")
6548
if options["request_bodies"] not in request_bodies:
@@ -69,18 +52,13 @@ def __init__(self, *args, **kwargs):
6952
)
7053
)
7154

55+
# XXX: we should probably only do this for the init()ed client
7256
atexit.register(self.close)
7357

7458
@property
7559
def dsn(self):
76-
"""The DSN that created this event."""
77-
if self._transport is not None:
78-
return self._transport.dsn
79-
80-
@classmethod
81-
def disabled(cls):
82-
"""Creates a guarnateed to be disabled client."""
83-
return cls(NO_DSN)
60+
"""Returns the configured dsn."""
61+
return self.options["dsn"]
8462

8563
def _prepare_event(self, event, hint, scope):
8664
if event.get("timestamp") is None:

sentry_sdk/hub.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,16 @@ def __init__(self, client_or_hub=None, scope=None):
8282
scope = Scope()
8383
self._stack = [(client, scope)]
8484
self._last_event_id = None
85+
self._old_hubs = []
8586

8687
def __enter__(self):
87-
return _HubManager(self)
88+
self._old_hubs.append(Hub.current)
89+
_local.set(self)
90+
return self
91+
92+
def __exit__(self, exc_type, exc_value, tb):
93+
old = self._old_hubs.pop()
94+
_local.set(old)
8895

8996
def run(self, callback):
9097
"""Runs a callback in the context of the hub. Alternatively the

sentry_sdk/transport.py

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
from ._compat import queue
1616
from .consts import VERSION
17+
from .utils import Dsn
1718

1819
try:
1920
from urllib.request import getproxies
@@ -24,10 +25,10 @@
2425
logger = logging.getLogger(__name__)
2526

2627

27-
def _make_pool(dsn, http_proxy, https_proxy):
28-
proxy = https_proxy if dsn == "https" else http_proxy
28+
def _make_pool(parsed_dsn, http_proxy, https_proxy):
29+
proxy = https_proxy if parsed_dsn == "https" else http_proxy
2930
if not proxy:
30-
proxy = getproxies().get(dsn.scheme)
31+
proxy = getproxies().get(parsed_dsn.scheme)
3132

3233
opts = {"num_pools": 2, "cert_reqs": "CERT_REQUIRED", "ca_certs": certifi.where()}
3334

@@ -70,7 +71,7 @@ def send_event(pool, event, auth):
7071

7172

7273
def spawn_thread(transport):
73-
auth = transport.dsn.to_auth("sentry-python/%s" % VERSION)
74+
auth = transport.parsed_dsn.to_auth("sentry-python/%s" % VERSION)
7475

7576
def thread():
7677
disabled_until = None
@@ -104,10 +105,36 @@ def thread():
104105

105106

106107
class Transport(object):
107-
def __init__(self, dsn, http_proxy=None, https_proxy=None):
108-
self.dsn = dsn
108+
def __init__(self, options=None):
109+
self.options = options
110+
if options and options["dsn"]:
111+
self.parsed_dsn = Dsn(options["dsn"])
112+
else:
113+
self.parsed_dsn = None
114+
115+
def capture_event(self, event):
116+
raise NotImplementedError()
117+
118+
def close(self):
119+
pass
120+
121+
def drain_events(self, timeout):
122+
pass
123+
124+
def __del__(self):
125+
self.close()
126+
127+
128+
class HttpTransport(Transport):
129+
def __init__(self, options):
130+
Transport.__init__(self, options)
109131
self._queue = None
110-
self._pool = _make_pool(dsn, http_proxy=http_proxy, https_proxy=https_proxy)
132+
self._pool = _make_pool(
133+
self.parsed_dsn,
134+
http_proxy=options["http_proxy"],
135+
https_proxy=options["https_proxy"],
136+
)
137+
self.start()
111138

112139
def start(self):
113140
if self._queue is None:
@@ -139,3 +166,36 @@ def drain_events(self, timeout):
139166

140167
def __del__(self):
141168
self.close()
169+
170+
171+
class _FunctionTransport(Transport):
172+
def __init__(self, func):
173+
Transport.__init__(self)
174+
self._func = func
175+
176+
def capture_event(self, event):
177+
self._func(event)
178+
179+
180+
def make_transport(options):
181+
ref_transport = options["transport"]
182+
183+
# If no transport is given, we use the http transport class
184+
if ref_transport is None:
185+
transport_cls = HttpTransport
186+
else:
187+
try:
188+
issubclass(ref_transport, type)
189+
except TypeError:
190+
# if we are not a class but we are a callable, assume a
191+
# function that acts as capture_event
192+
if callable(ref_transport):
193+
return _FunctionTransport(ref_transport)
194+
# otherwise assume an object fulfilling the transport contract
195+
return ref_transport
196+
transport_cls = ref_transport
197+
198+
# if a transport class is given only instanciate it if the dsn is not
199+
# empty or None
200+
if options["dsn"]:
201+
return transport_cls(options)

tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import sentry_sdk
88
from sentry_sdk._compat import reraise
9-
from sentry_sdk.client import Transport
9+
from sentry_sdk.transport import Transport
1010

1111
SEMAPHORE = "./checkouts/semaphore/target/debug/semaphore"
1212

tests/test_client.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,14 @@
88
from sentry_sdk import Hub, Client, configure_scope, capture_message, add_breadcrumb
99
from sentry_sdk.hub import HubMeta
1010
from sentry_sdk.transport import Transport
11-
from sentry_sdk.utils import Dsn
12-
from sentry_sdk._compat import reraise
11+
from sentry_sdk._compat import reraise, text_type
1312

1413

1514
class EventCaptured(Exception):
1615
pass
1716

1817

1918
class _TestTransport(Transport):
20-
def __init__(self, *a, **kw):
21-
pass
22-
23-
def start(self):
24-
pass
25-
2619
def capture_event(self, event):
2720
raise EventCaptured()
2821

@@ -32,10 +25,18 @@ def test_transport_option(monkeypatch):
3225
dsn2 = "https://[email protected]/124"
3326
assert str(Client(dsn=dsn).dsn) == dsn
3427
assert Client().dsn is None
35-
assert str(Client(transport=Transport(Dsn(dsn2))).dsn) == dsn2
3628

3729
monkeypatch.setenv("SENTRY_DSN", dsn)
38-
assert str(Client(transport=Transport(Dsn(dsn2))).dsn) == dsn2
30+
transport = Transport({"dsn": dsn2})
31+
assert text_type(transport.parsed_dsn) == dsn2
32+
assert str(Client(transport=transport).dsn) == dsn
33+
34+
35+
def test_simple_transport():
36+
events = []
37+
with Hub(Client(transport=events.append)):
38+
capture_message("Hello World!")
39+
assert events[0]["message"] == "Hello World!"
3940

4041

4142
def test_ignore_errors():

0 commit comments

Comments
 (0)