Skip to content

Commit

Permalink
feat: Added basic API docs (getsentry#57)
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko authored Sep 7, 2018
1 parent 88b2d3a commit 500e070
Show file tree
Hide file tree
Showing 14 changed files with 142 additions and 70 deletions.
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,8 @@ tox-test:
lint:
@tox -e linters
.PHONY: lint

apidocs:
@pip install pdoc pygments
@pdoc --overwrite --html --html-dir build/apidocs sentry_sdk
.PHONY: apidocs
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ After initialization, you can capture exceptions like this:

You can create a scope to attach data to all events happening inside of it:

with sentry_sdk.get_current_hub().push_scope():
with sentry_sdk.Hub.current.push_scope():
with sentry_sdk.configure_scope() as scope:
scope.transaction = "my_view_name"
scope.set_tag("key", "value")
Expand Down
27 changes: 27 additions & 0 deletions sentry_sdk/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,33 @@
"""
The Sentry SDK is the new-style SDK for [sentry.io](https://sentry.io/). It implements
the unified API that all modern SDKs follow for Python 2.7 and 3.5 or later.
The user documentation can be found on [docs.sentry.io](https://docs.sentry.io/).
## Quickstart
The only thing to get going is to call `sentry_sdk.init()`. When not passed any
arguments the default options are used and the DSN is picked up from the `SENTRY_DSN`
environment variable. Otherwise the DSN can be passed with the `dsn` keyword
or first argument.
import sentry_sdk
sentry_sdk.init()
This initializes the default integrations which will automatically pick up any
uncaught exceptions. Additionally you can report arbitrary other exceptions:
try:
my_failing_function()
except Exception as e:
sentry_sdk.capture_exception(e)
"""
from .api import * # noqa
from .api import __all__ # noqa

# modules we consider public
__all__.append("integrations")

# Initialize the debug support after everything is loaded
from .debug import init_debug_support

Expand Down
40 changes: 20 additions & 20 deletions sentry_sdk/api.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
import inspect
from contextlib import contextmanager

from .hub import Hub
from .scope import Scope
from .utils import EventHint
from .transport import Transport, HttpTransport
from .client import Client, get_options
from .integrations import setup_integrations


__all__ = ["Hub", "Scope", "Client", "EventHint"]
__all__ = ["Hub", "Scope", "Client", "EventHint", "Transport", "HttpTransport"]


def public(f):
__all__.append(f.__name__)
return f


def hubmethod(f):
f.__doc__ = "%s\n\n%s" % (
"Alias for `Hub.%s`" % f.__name__,
inspect.getdoc(getattr(Hub, f.__name__)),
)
return public(f)


class _InitGuard(object):
def __init__(self, client):
self._client = client
Expand All @@ -32,7 +42,9 @@ def _init_on_hub(hub, args, kwargs):
options = get_options(*args, **kwargs)
client = Client(options)
hub.bind_client(client)
setup_integrations(options)
setup_integrations(
options["integrations"] or [], with_defaults=options["default_integrations"]
)
return _InitGuard(client)


Expand All @@ -52,41 +64,36 @@ def _init_on_current(*args, **kwargs):
return _init_on_hub(Hub.current, args, kwargs)


@public
@hubmethod
def capture_event(event, hint=None):
"""Alias for `Hub.current.capture_event`"""
hub = Hub.current
if hub is not None:
return hub.capture_event(event, hint)


@public
@hubmethod
def capture_message(message, level=None):
"""Alias for `Hub.current.capture_message`"""
hub = Hub.current
if hub is not None:
return hub.capture_message(message, level)


@public
@hubmethod
def capture_exception(error=None):
"""Alias for `Hub.current.capture_exception`"""
hub = Hub.current
if hub is not None:
return hub.capture_exception(error)


@public
@hubmethod
def add_breadcrumb(*args, **kwargs):
"""Alias for `Hub.current.add_breadcrumb`"""
hub = Hub.current
if hub is not None:
return hub.add_breadcrumb(*args, **kwargs)


@public
@hubmethod
def configure_scope(callback=None):
"""Alias for `Hub.current.configure_scope`"""
hub = Hub.current
if hub is not None:
return hub.configure_scope(callback)
Expand All @@ -99,15 +106,8 @@ def inner():
return inner()


@public
def get_current_hub():
"""Alias for `Hub.current`"""
return Hub.current


@public
@hubmethod
def last_event_id():
"""Alias for `Hub.last_event_id`"""
hub = Hub.current
if hub is not None:
return hub.last_event_id()
31 changes: 24 additions & 7 deletions sentry_sdk/hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def _internal_exceptions():
except Exception:
hub = Hub.current
if hub:
hub.capture_internal_exception(sys.exc_info())
hub._capture_internal_exception(sys.exc_info())


def _get_client_options():
Expand Down Expand Up @@ -124,7 +124,13 @@ def bind_client(self, new):
self._stack[-1] = (new, top[1])

def capture_event(self, event, hint=None):
"""Captures an event."""
"""Captures an event. The return value is the ID of the event.
The event is a dictionary following the Sentry v7/v8 protocol
specification. Optionally an `EventHint` object can be passed that
is used by processors to extract additional information from it.
Typically the event hint object would contain exception information.
"""
client, scope = self._stack[-1]
if client is not None:
rv = client.capture_event(event, hint, scope)
Expand All @@ -133,15 +139,22 @@ def capture_event(self, event, hint=None):
return rv

def capture_message(self, message, level=None):
"""Captures a message."""
"""Captures a message. The message is just a string. If no level
is provided the default level is `info`.
"""
if self.client is None:
return
if level is None:
level = "info"
return self.capture_event({"message": message, "level": level})

def capture_exception(self, error=None):
"""Captures an exception."""
"""Captures an exception.
The argument passed can be `None` in which case the last exception
will be reported, otherwise an exception object or an `exc_info`
tuple.
"""
client = self.client
if client is None:
return
Expand All @@ -156,15 +169,19 @@ def capture_exception(self, error=None):
try:
return self.capture_event(event, hint=hint)
except Exception:
self.capture_internal_exception(sys.exc_info())
self._capture_internal_exception(sys.exc_info())

def capture_internal_exception(self, exc_info):
def _capture_internal_exception(self, exc_info):
"""Capture an exception that is likely caused by a bug in the SDK
itself."""
logger.debug("Internal error in sentry_sdk", exc_info=exc_info)

def add_breadcrumb(self, crumb=None, hint=None, **kwargs):
"""Adds a breadcrumb."""
"""Adds a breadcrumb. The breadcrumbs are a dictionary with the
data as the sentry v7/v8 protocol expects. `hint` is an optional
value that can be used by `before_breadcrumb` to customize the
breadcrumbs that are emitted.
"""
client, scope = self._stack[-1]
if client is None:
logger.info("Dropped breadcrumb because no client bound")
Expand Down
39 changes: 26 additions & 13 deletions sentry_sdk/integrations/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""This package"""
from threading import Lock

from ..utils import logger
Expand All @@ -7,35 +8,47 @@
_installer_lock = Lock()


def _get_default_integrations():
def get_default_integrations():
"""Returns an iterator of default integration instances."""
from .logging import LoggingIntegration
from .excepthook import ExcepthookIntegration
from .dedupe import DedupeIntegration
from .atexit import AtexitIntegration

yield LoggingIntegration
yield ExcepthookIntegration
yield DedupeIntegration
yield AtexitIntegration
yield LoggingIntegration()
yield ExcepthookIntegration()
yield DedupeIntegration()
yield AtexitIntegration()


def setup_integrations(options):
integrations = list(options.get("integrations", None) or ())
default_integrations = options.get("default_integrations") or False

if default_integrations:
for cls in _get_default_integrations():
if not any(isinstance(x, cls) for x in integrations):
integrations.append(cls())
def setup_integrations(integrations, with_defaults=True):
"""Given a list of integration instances this installs them all. When
`with_defaults` is set to `True` then all default integrations are added
unless they were already provided before.
"""
integrations = list(integrations)
if with_defaults:
for instance in get_default_integrations():
if not any(isinstance(x, type(instance)) for x in integrations):
integrations.append(instance)

for integration in integrations:
integration()


class Integration(object):
"""Baseclass for all integrations."""

identifier = None
"""A unique identifying string for the integration. Integrations must
set this as a class attribute.
"""

def install(self):
"""An integration must implement all its code here. When the
`setup_integrations` function runs it will invoke this unless the
integration was already activated elsewhere.
"""
raise NotImplementedError()

def __call__(self):
Expand Down
13 changes: 7 additions & 6 deletions sentry_sdk/integrations/_wsgi.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import json
import sys

import sentry_sdk
from sentry_sdk import capture_exception
from sentry_sdk.hub import (
Hub,
_internal_exceptions,
_should_send_default_pii,
_get_client_options,
Expand Down Expand Up @@ -150,7 +151,7 @@ def get_client_ip(environ):


def run_wsgi_app(app, environ, start_response):
hub = sentry_sdk.get_current_hub()
hub = Hub.current
hub.push_scope()
with _internal_exceptions():
with hub.configure_scope() as scope:
Expand All @@ -160,7 +161,7 @@ def run_wsgi_app(app, environ, start_response):
rv = app(environ, start_response)
except Exception:
einfo = sys.exc_info()
sentry_sdk.capture_exception(einfo)
capture_exception(einfo)
hub.pop_scope_unsafe()
reraise(*einfo)

Expand All @@ -180,7 +181,7 @@ def __iter__(self):
self._response = iter(self._response)
except Exception:
einfo = sys.exc_info()
sentry_sdk.capture_exception(einfo)
capture_exception(einfo)
reraise(*einfo)
return self

Expand All @@ -191,7 +192,7 @@ def __next__(self):
raise
except Exception:
einfo = sys.exc_info()
sentry_sdk.capture_exception(einfo)
capture_exception(einfo)
reraise(*einfo)

def close(self):
Expand All @@ -201,7 +202,7 @@ def close(self):
self._response.close()
except Exception:
einfo = sys.exc_info()
sentry_sdk.capture_exception(einfo)
capture_exception(einfo)
reraise(*einfo)


Expand Down
5 changes: 5 additions & 0 deletions sentry_sdk/integrations/atexit.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@


def default_shutdown_callback(pending, timeout):
"""This is the default shutdown callback that is set on the options.
It prints out a message to stderr that informs the user that some events
are still pending and the process is waiting for them to flush out.
"""

def echo(msg):
sys.stderr.write(msg + "\n")

Expand Down
8 changes: 4 additions & 4 deletions sentry_sdk/integrations/celery.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from celery.signals import task_failure, task_prerun, task_postrun
from celery.exceptions import SoftTimeLimitExceeded

from sentry_sdk import get_current_hub
from sentry_sdk import Hub
from sentry_sdk.hub import _internal_exceptions

from . import Integration
Expand All @@ -24,7 +24,7 @@ def _process_failure_signal(self, sender, task_id, einfo, **kw):
if hasattr(sender, "throws") and isinstance(einfo.exception, sender.throws):
return

hub = get_current_hub()
hub = Hub.current
if isinstance(einfo.exception, SoftTimeLimitExceeded):
with hub.push_scope():
with hub.configure_scope() as scope:
Expand All @@ -40,10 +40,10 @@ def _process_failure_signal(self, sender, task_id, einfo, **kw):

def _handle_task_prerun(self, sender, task, **kw):
with _internal_exceptions():
hub = get_current_hub()
hub = Hub.current
hub.push_scope()
with hub.configure_scope() as scope:
scope.transaction = task.name

def _handle_task_postrun(self, sender, task_id, task, **kw):
get_current_hub().pop_scope_unsafe()
Hub.current.pop_scope_unsafe()
Loading

0 comments on commit 500e070

Please sign in to comment.