Skip to content
This repository was archived by the owner on Oct 23, 2023. It is now read-only.

Commit 12bb737

Browse files
committed
Refactor DSN and transport configuration
Passing a transport via a scheme prefix is now deprecated in favor of explicit class loading: >>> Client(dsn, transport=HttpTransport) Additionally DSN-related configuration is now held in the RemoteConfig helper, which is bound on Client.remote. Fixes GH-587
1 parent 23bd045 commit 12bb737

File tree

22 files changed

+314
-403
lines changed

22 files changed

+314
-403
lines changed

CHANGES

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
Version 5.4.0
2+
-------------
3+
4+
* Binding transports via a scheme prefix on DSNs is now deprecated.
5+
* ``raven.conf.load`` has been removed.
6+
* Upstream-related configuration (such as url, project_id, and keys) is now contained in ``RemoteConfig``
7+
attached to ``Client.remote``
8+
19
Version 5.3.1
210
-------------
311

docs/config/index.rst

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,6 @@ It is composed of six important pieces:
6666

6767
* The project ID which the authenticated user is bound to.
6868

69-
.. note::
70-
71-
Protocol may also contain transporter type: gevent+http, gevent+https, twisted+http, tornado+http, eventlet+http, eventlet+https
72-
73-
For *Python 3.3+* also available: aiohttp+http and aiohttp+https
7469

7570
Client Arguments
7671
----------------

docs/transports/index.rst

Lines changed: 25 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ Transports
33

44
A transport is the mechanism in which Raven sends the HTTP request to the Sentry server. By default, Raven uses a threaded asynchronous transport, but you can easily adjust this by modifying your ``SENTRY_DSN`` value.
55

6-
Transport registration is done via the URL prefix, so for example, a synchronous transport is as simple as prefixing your ``SENTRY_DSN`` with the ``sync+`` value.
6+
Transport registration is done as part of the Client configuration:
7+
8+
.. code-block:: python
9+
10+
# Use the synchronous HTTP transport
11+
client = Client('http://public:secret@example.com/1', transport=HTTPTransport)
712
813
Options are passed to transports via the querystring.
914

@@ -25,82 +30,38 @@ For example, to increase the timeout and to disable SSL verification:
2530
SENTRY_DSN = 'http://public:secret@example.com/1?timeout=5&verify_ssl=0'
2631

2732

28-
aiohttp
29-
-------
30-
31-
Should only be used within a :pep:`3156` compatible event loops
32-
(*asyncio* itself and others).
33-
34-
::
35-
36-
SENTRY_DSN = 'aiohttp+http://public:secret@example.com/1'
37-
38-
Eventlet
39-
--------
40-
41-
Should only be used within an Eventlet IO loop.
42-
43-
::
44-
45-
SENTRY_DSN = 'eventlet+http://public:secret@example.com/1'
46-
47-
48-
Gevent
49-
------
50-
51-
Should only be used within a Gevent IO loop.
52-
53-
::
54-
55-
SENTRY_DSN = 'gevent+http://public:secret@example.com/1'
56-
57-
58-
Requests
59-
--------
60-
61-
Requires the ``requests`` library. Synchronous.
62-
63-
::
64-
65-
SENTRY_DSN = 'requests+http://public:secret@example.com/1'
66-
67-
68-
Sync
69-
----
70-
71-
A synchronous blocking transport.
72-
73-
::
74-
75-
SENTRY_DSN = 'sync+http://public:secret@example.com/1'
33+
Builtin Transports
34+
------------------
7635

36+
.. data:: sentry.transport.thread.ThreadedHTTPTransport
7737

78-
Threaded (Default)
79-
------------------
38+
The default transport. Manages a threaded worker for processing messages asynchronous.
8039

81-
Spawns an async worker for processing messages.
40+
.. data:: sentry.transport.http.HTTPTransport
8241

83-
::
42+
A synchronous blocking transport.
8443

85-
SENTRY_DSN = 'threaded+http://public:secret@example.com/1'
44+
.. data:: sentry.transport.aiohttp.AioHttpTransport
8645

46+
Should only be used within a :pep:`3156` compatible event loops
47+
(*asyncio* itself and others).
8748

88-
Tornado
89-
-------
49+
.. data:: sentry.transport.eventlet.EventletHTTPTransport
9050

91-
Should only be used within a Tornado IO loop.
51+
Should only be used within an Eventlet IO loop.
9252

93-
::
53+
.. data:: sentry.transport.gevent.GeventedHTTPTransport
9454

95-
SENTRY_DSN = 'tornado+http://public:secret@example.com/1'
55+
Should only be used within a Gevent IO loop.
9656

57+
.. data:: sentry.transport.requests.RequestsHTTPTransport
9758

98-
Twisted
99-
-------
59+
A synchronous transport which relies on the ``requests`` library.
10060

101-
Should only be used within a Twisted event loop.
61+
.. data:: sentry.transport.tornado.TornadoHTTPTransport
10262

103-
::
63+
Should only be used within a Tornado IO loop.
10464

105-
SENTRY_DSN = 'twisted+http://public:secret@example.com/1'
65+
.. data:: sentry.transport.twisted.TwistedHTTPTransport
10666

67+
Should only be used within a Twisted event loop.

docs/usage.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ the optional DSN argument::
5555

5656
You should get something like the following, assuming you're configured everything correctly::
5757

58-
$ raven test sync+http://dd2c825ff9b1417d88a99573903ebf80:91631495b10b45f8a1cdbc492088da6a@localhost:9000/1
58+
$ raven test http://dd2c825ff9b1417d88a99573903ebf80:91631495b10b45f8a1cdbc492088da6a@localhost:9000/1
5959
Using DSN configuration:
60-
sync+http://dd2c825ff9b1417d88a99573903ebf80:91631495b10b45f8a1cdbc492088da6a@localhost:9000/1
60+
http://dd2c825ff9b1417d88a99573903ebf80:91631495b10b45f8a1cdbc492088da6a@localhost:9000/1
6161

6262
Client configuration:
6363
servers : ['http://localhost:9000/api/store/']

raven/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from raven.versioning import * # NOQA
1515

1616

17-
__all__ = ('VERSION', 'Client', 'load', 'get_version')
17+
__all__ = ('VERSION', 'Client', 'get_version')
1818

1919
try:
2020
VERSION = __import__('pkg_resources') \

raven/base.py

Lines changed: 37 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@
2424

2525
import raven
2626
from raven.conf import defaults
27+
from raven.conf.remote import RemoteConfig
2728
from raven.context import Context
2829
from raven.exceptions import APIError, RateLimited
2930
from raven.utils import six, json, get_versions, get_auth_header, merge_dicts
3031
from raven.utils.encoding import to_unicode
3132
from raven.utils.serializer import transform
3233
from raven.utils.stacks import get_stack_info, iter_stack_frames, get_culprit
33-
from raven.utils.urlparse import urlparse
3434
from raven.transport.registry import TransportRegistry, default_transports
3535

3636
__all__ = ('Client',)
@@ -117,7 +117,7 @@ class Client(object):
117117

118118
_registry = TransportRegistry(transports=default_transports)
119119

120-
def __init__(self, dsn=None, raise_send_errors=False, **options):
120+
def __init__(self, dsn=None, raise_send_errors=False, transport=None, **options):
121121
global Raven
122122

123123
o = options
@@ -134,8 +134,8 @@ def __init__(self, dsn=None, raise_send_errors=False, **options):
134134
self.error_logger = logging.getLogger('sentry.errors')
135135
self.uncaught_logger = logging.getLogger('sentry.errors.uncaught')
136136

137-
self.dsns = {}
138-
self.set_dsn(dsn, **options)
137+
self._transport_cache = {}
138+
self.set_dsn(dsn, transport)
139139

140140
self.include_paths = set(o.get('include_paths') or [])
141141
self.exclude_paths = set(o.get('exclude_paths') or [])
@@ -174,42 +174,27 @@ def __init__(self, dsn=None, raise_send_errors=False, **options):
174174

175175
self._context = Context()
176176

177-
def set_dsn(self, dsn=None, **options):
178-
o = options
179-
177+
def set_dsn(self, dsn=None, transport=None):
180178
if dsn is None and os.environ.get('SENTRY_DSN'):
181179
msg = "Configuring Raven from environment variable 'SENTRY_DSN'"
182180
self.logger.debug(msg)
183181
dsn = os.environ['SENTRY_DSN']
184182

185-
try:
186-
servers, public_key, secret_key, project, transport_options = self.dsns[dsn]
187-
except KeyError:
188-
if dsn:
189-
# TODO: should we validate other options weren't sent?
190-
urlparts = urlparse(dsn)
191-
self.logger.debug(
192-
"Configuring Raven for host: %s://%s:%s" % (urlparts.scheme,
193-
urlparts.netloc, urlparts.path))
194-
dsn_config = raven.load(dsn, transport_registry=self._registry)
195-
servers = dsn_config['SENTRY_SERVERS']
196-
project = dsn_config['SENTRY_PROJECT']
197-
public_key = dsn_config['SENTRY_PUBLIC_KEY']
198-
secret_key = dsn_config['SENTRY_SECRET_KEY']
199-
transport_options = dsn_config.get('SENTRY_TRANSPORT_OPTIONS', {})
183+
if dsn not in self._transport_cache:
184+
if dsn is None:
185+
result = RemoteConfig(transport=transport)
200186
else:
201-
servers = ()
202-
project = None
203-
public_key = None
204-
secret_key = None
205-
transport_options = {}
206-
self.dsns[dsn] = servers, public_key, secret_key, project, transport_options
207-
208-
self.servers = servers
209-
self.public_key = public_key
210-
self.secret_key = secret_key
211-
self.project = project or defaults.PROJECT
212-
self.transport_options = transport_options
187+
result = RemoteConfig.from_string(
188+
dsn,
189+
transport=transport,
190+
transport_registry=self._registry,
191+
)
192+
self._transport_cache[dsn] = result
193+
self.remote = result
194+
else:
195+
self.remote = self._transport_cache[dsn]
196+
197+
self.logger.debug("Configuring Raven for host: {0}".format(self.remote))
213198

214199
@classmethod
215200
def register_scheme(cls, scheme, transport_class):
@@ -252,14 +237,6 @@ def get_ident(self, result):
252237
def get_handler(self, name):
253238
return self.module_cache[name](self)
254239

255-
def _get_public_dsn(self):
256-
url = urlparse(self.servers[0])
257-
netloc = url.hostname
258-
if url.port:
259-
netloc += ':%s' % url.port
260-
path = url.path.replace('api/%s/store/' % (self.project,), self.project)
261-
return '//%s@%s%s' % (self.public_key, netloc, path)
262-
263240
def get_public_dsn(self, scheme=None):
264241
"""
265242
Returns a public DSN which is consumable by raven-js
@@ -272,7 +249,7 @@ def get_public_dsn(self, scheme=None):
272249
"""
273250
if not self.is_enabled():
274251
return
275-
url = self._get_public_dsn()
252+
url = self.remote.get_public_dsn()
276253
if not scheme:
277254
return url
278255
return '%s:%s' % (scheme, url)
@@ -397,7 +374,7 @@ def build_msg(self, event_type, data=None, date=None,
397374
data['extra'][k] = self.transform(v)
398375

399376
# It's important date is added **after** we serialize
400-
data.setdefault('project', self.project)
377+
data.setdefault('project', self.remote.project)
401378
data.setdefault('timestamp', date or datetime.utcnow())
402379
data.setdefault('time_spent', time_spent)
403380
data.setdefault('event_id', event_id)
@@ -532,7 +509,7 @@ def is_enabled(self):
532509
Return a boolean describing whether the client should attempt to send
533510
events.
534511
"""
535-
return bool(self.servers)
512+
return self.remote.is_active()
536513

537514
def _successful_send(self):
538515
self.state.set_success()
@@ -590,9 +567,7 @@ def failed_send(e):
590567
self._failed_send(e, url, self.decode(data))
591568

592569
try:
593-
parsed = urlparse(url)
594-
transport = self._registry.get_transport(
595-
parsed, **self.transport_options)
570+
transport = self.remote.get_transport()
596571
if transport.async:
597572
transport.async_send(data, headers, self._successful_send,
598573
failed_send)
@@ -626,18 +601,22 @@ def send_encoded(self, message, auth_header=None, **kwargs):
626601
protocol=self.protocol_version,
627602
timestamp=timestamp,
628603
client=client_string,
629-
api_key=self.public_key,
630-
api_secret=self.secret_key,
604+
api_key=self.remote.public_key,
605+
api_secret=self.remote.secret_key,
631606
)
632607

633-
for url in self.servers:
634-
headers = {
635-
'User-Agent': client_string,
636-
'X-Sentry-Auth': auth_header,
637-
'Content-Type': 'application/octet-stream',
638-
}
639-
640-
self.send_remote(url=url, data=message, headers=headers)
608+
headers = {
609+
'User-Agent': client_string,
610+
'X-Sentry-Auth': auth_header,
611+
'Content-Type': 'application/octet-stream',
612+
}
613+
614+
self.send_remote(
615+
url=self.remote.store_endpoint,
616+
data=message,
617+
headers=headers,
618+
**kwargs
619+
)
641620

642621
def encode(self, data):
643622
"""

raven/conf/__init__.py

Lines changed: 1 addition & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@
88
from __future__ import absolute_import
99

1010
import logging
11-
from raven.utils.urlparse import urlparse
1211

13-
__all__ = ('load', 'setup_logging')
12+
__all__ = ['setup_logging']
1413

1514
EXCLUDE_LOGGER_DEFAULTS = (
1615
'raven',
@@ -21,42 +20,6 @@
2120
)
2221

2322

24-
# TODO (vng): this seems weirdly located in raven.conf. Seems like
25-
# it's really a part of raven.transport.TransportRegistry
26-
# Not quite sure what to do with this
27-
def load(dsn, scope=None, transport_registry=None):
28-
"""
29-
Parses a Sentry compatible DSN and loads it
30-
into the given scope.
31-
32-
>>> import raven
33-
34-
>>> dsn = 'https://public_key:secret_key@sentry.local/project_id'
35-
36-
>>> # Apply configuration to local scope
37-
>>> raven.load(dsn, locals())
38-
39-
>>> # Return DSN configuration
40-
>>> options = raven.load(dsn)
41-
"""
42-
43-
if not transport_registry:
44-
from raven.transport import TransportRegistry, default_transports
45-
transport_registry = TransportRegistry(default_transports)
46-
47-
url = urlparse(dsn)
48-
49-
if not transport_registry.supported_scheme(url.scheme):
50-
raise ValueError('Unsupported Sentry DSN scheme: %r' % url.scheme)
51-
52-
if scope is None:
53-
scope = {}
54-
scope_extras = transport_registry.compute_scope(url, scope)
55-
scope.update(scope_extras)
56-
57-
return scope
58-
59-
6023
def setup_logging(handler, exclude=EXCLUDE_LOGGER_DEFAULTS):
6124
"""
6225
Configures logging to pipe to Sentry.

0 commit comments

Comments
 (0)