Skip to content

Commit 8d669f6

Browse files
bluetechuntitaker
authored andcommitted
Improve typings some (getsentry#389)
* doc: Add a basic example Basic usage, mostly taken from the API docs. Refs getsentry#386. * fix: Don't dynamically compute __all__, to help mypy understand it sentry_sdk/__init__.py imports * from sentry_sdk.api. Mypy can't understand the `@public` trick in sentry_sdk.api, so it thinks that sentry_sdk.api's __all__ is [], so given code like this import sentry_sdk sentry_sdk.capture_message("Something went wrong!") mypy complains Help it by not using the `@public` trick. Fixes getsentry#386. * fix: Avoid incorrect warning in some case in Hub.get_integration(<class>) The type initial_client.integrations is Dict[str, something] so the integration name should be used in this check. * feat: Improve type annotations Enable some more strict mypy options and deal with the fallout. * wip * fix: Fix remaining linter errors * fix: Remove usage of ellipsis for Python 2
1 parent 733662d commit 8d669f6

27 files changed

+287
-91
lines changed

examples/basic.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import sentry_sdk
2+
from sentry_sdk.integrations.excepthook import ExcepthookIntegration
3+
from sentry_sdk.integrations.atexit import AtexitIntegration
4+
from sentry_sdk.integrations.dedupe import DedupeIntegration
5+
from sentry_sdk.integrations.stdlib import StdlibIntegration
6+
7+
8+
sentry_sdk.init(
9+
dsn="https://<key>@sentry.io/<project>",
10+
default_integrations=False,
11+
integrations=[
12+
ExcepthookIntegration(),
13+
AtexitIntegration(),
14+
DedupeIntegration(),
15+
StdlibIntegration(),
16+
],
17+
environment="Production",
18+
release="1.0.0",
19+
send_default_pii=False,
20+
max_breadcrumbs=5,
21+
)
22+
23+
with sentry_sdk.push_scope() as scope:
24+
scope.user = {"email": "john.doe@example.com"}
25+
scope.set_tag("page_locale", "de-at")
26+
scope.set_extra("request", {"id": "d5cf8a0fd85c494b9c6453c4fba8ab17"})
27+
scope.level = "warning"
28+
sentry_sdk.capture_message("Something went wrong!")
29+
30+
sentry_sdk.add_breadcrumb(category="auth", message="Authenticated user", level="info")
31+
32+
try:
33+
1 / 0
34+
except Exception as e:
35+
sentry_sdk.capture_exception(e)

mypy.ini

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,45 @@
11
[mypy]
22
allow_redefinition = True
3+
check_untyped_defs = True
4+
; disallow_any_decorated = True
5+
; disallow_any_explicit = True
6+
; disallow_any_expr = True
37
disallow_any_generics = True
8+
; disallow_any_unimported = True
9+
disallow_incomplete_defs = True
10+
; disallow_subclassing_any = True
11+
; disallow_untyped_calls = True
12+
disallow_untyped_decorators = True
13+
; disallow_untyped_defs = True
14+
no_implicit_optional = True
15+
strict_equality = True
16+
strict_optional = True
417
warn_redundant_casts = True
18+
; warn_return_any = True
19+
; warn_unused_configs = True
20+
; warn_unused_ignores = True
521

622
[mypy-sentry_sdk.integrations.*]
723
disallow_any_generics = False
824

925
[mypy-sentry_sdk.utils]
1026
disallow_any_generics = False
27+
28+
[mypy-django.*]
29+
ignore_missing_imports = True
30+
[mypy-pyramid.*]
31+
ignore_missing_imports = True
32+
[mypy-psycopg2.*]
33+
ignore_missing_imports = True
34+
[mypy-pytest.*]
35+
ignore_missing_imports = True
36+
[mypy-aiohttp.*]
37+
ignore_missing_imports = True
38+
[mypy-sanic.*]
39+
ignore_missing_imports = True
40+
[mypy-tornado.*]
41+
ignore_missing_imports = True
42+
[mypy-fakeredis.*]
43+
ignore_missing_imports = True
44+
[mypy-rq.*]
45+
ignore_missing_imports = True

sentry_sdk/api.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,33 +10,42 @@
1010
from typing import Optional
1111
from typing import overload
1212
from typing import Callable
13-
from typing import Dict
13+
from typing import TypeVar
1414
from contextlib import ContextManager
15+
16+
from sentry_sdk.utils import Event, Hint, Breadcrumb, BreadcrumbHint
17+
18+
F = TypeVar("F", bound=Callable[..., Any])
1519
else:
1620

1721
def overload(x):
1822
return x
1923

2024

21-
__all__ = []
22-
23-
24-
def public(f):
25-
__all__.append(f.__name__)
26-
return f
25+
__all__ = [
26+
"capture_event",
27+
"capture_message",
28+
"capture_exception",
29+
"add_breadcrumb",
30+
"configure_scope",
31+
"push_scope",
32+
"flush",
33+
"last_event_id",
34+
]
2735

2836

2937
def hubmethod(f):
38+
# type: (F) -> F
3039
f.__doc__ = "%s\n\n%s" % (
3140
"Alias for `Hub.%s`" % f.__name__,
3241
inspect.getdoc(getattr(Hub, f.__name__)),
3342
)
34-
return public(f)
43+
return f
3544

3645

3746
@hubmethod
3847
def capture_event(event, hint=None):
39-
# type: (Dict[str, Any], Dict[str, Any]) -> Optional[str]
48+
# type: (Event, Optional[Hint]) -> Optional[str]
4049
hub = Hub.current
4150
if hub is not None:
4251
return hub.capture_event(event, hint)
@@ -45,7 +54,7 @@ def capture_event(event, hint=None):
4554

4655
@hubmethod
4756
def capture_message(message, level=None):
48-
# type: (str, Optional[Any]) -> Optional[str]
57+
# type: (str, Optional[str]) -> Optional[str]
4958
hub = Hub.current
5059
if hub is not None:
5160
return hub.capture_message(message, level)
@@ -63,7 +72,7 @@ def capture_exception(error=None):
6372

6473
@hubmethod
6574
def add_breadcrumb(crumb=None, hint=None, **kwargs):
66-
# type: (Dict[str, Any], Dict[str, Any], **Any) -> None
75+
# type: (Optional[Breadcrumb], Optional[BreadcrumbHint], **Any) -> None
6776
hub = Hub.current
6877
if hub is not None:
6978
return hub.add_breadcrumb(crumb, hint, **kwargs)

sentry_sdk/client.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def get_options(*args, **kwargs):
4747
for key, value in iteritems(options):
4848
if key not in rv:
4949
raise TypeError("Unknown option %r" % (key,))
50-
rv[key] = value # type: ignore
50+
rv[key] = value
5151

5252
if rv["dsn"] is None:
5353
rv["dsn"] = os.environ.get("SENTRY_DSN")
@@ -108,9 +108,10 @@ def _prepare_event(
108108
hint = dict(hint or ()) # type: Hint
109109

110110
if scope is not None:
111-
event = scope.apply_to_event(event, hint)
112-
if event is None:
113-
return
111+
event_ = scope.apply_to_event(event, hint)
112+
if event_ is None:
113+
return None
114+
event = event_
114115

115116
if (
116117
self.options["attach_stacktrace"]
@@ -178,7 +179,7 @@ def _is_ignored_error(self, event, hint):
178179
if errcls == full_name or errcls == type_name:
179180
return True
180181
else:
181-
if issubclass(exc_info[0], errcls):
182+
if issubclass(exc_info[0], errcls): # type: ignore
182183
return True
183184

184185
return False
@@ -187,7 +188,7 @@ def _should_capture(
187188
self,
188189
event, # type: Event
189190
hint, # type: Hint
190-
scope=None, # type: Scope
191+
scope=None, # type: Optional[Scope]
191192
):
192193
# type: (...) -> bool
193194
if scope is not None and not scope._should_capture:
@@ -205,7 +206,7 @@ def _should_capture(
205206
return True
206207

207208
def capture_event(self, event, hint=None, scope=None):
208-
# type: (Dict[str, Any], Any, Scope) -> Optional[str]
209+
# type: (Dict[str, Any], Optional[Any], Optional[Scope]) -> Optional[str]
209210
"""Captures an event.
210211
211212
This takes the ready made event and an optional hint and scope. The
@@ -225,7 +226,7 @@ def capture_event(self, event, hint=None, scope=None):
225226
event["event_id"] = rv = uuid.uuid4().hex
226227
if not self._should_capture(event, hint, scope):
227228
return None
228-
event = self._prepare_event(event, hint, scope) # type: ignore
229+
event = self._prepare_event(event, hint, scope)
229230
if event is None:
230231
return None
231232
self.transport.capture_event(event)

sentry_sdk/consts.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"send_default_pii": bool,
3636
"http_proxy": Optional[str],
3737
"https_proxy": Optional[str],
38-
"ignore_errors": List[type],
38+
"ignore_errors": List[Union[type, str]],
3939
"request_bodies": str,
4040
"before_send": Optional[EventProcessor],
4141
"before_breadcrumb": Optional[BreadcrumbProcessor],

sentry_sdk/debug.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ def filter(self, record):
2020

2121

2222
def init_debug_support():
23+
# type: () -> None
2324
if not logger.handlers:
2425
configure_logger()
2526
configure_debug_hub()
2627

2728

2829
def configure_logger():
30+
# type: () -> None
2931
_handler = logging.StreamHandler(sys.stderr)
3032
_handler.setFormatter(logging.Formatter(" [sentry] %(levelname)s: %(message)s"))
3133
logger.addHandler(_handler)
@@ -34,6 +36,7 @@ def configure_logger():
3436

3537

3638
def configure_debug_hub():
39+
# type: () -> None
3740
def _get_debug_hub():
3841
return Hub.current
3942

sentry_sdk/hub.py

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
if False:
2020
from contextlib import ContextManager
21+
from sys import _OptExcInfo
2122

2223
from typing import Union
2324
from typing import Any
@@ -26,6 +27,7 @@
2627
from typing import List
2728
from typing import Callable
2829
from typing import Generator
30+
from typing import Type
2931
from typing import overload
3032

3133
from sentry_sdk.integrations import Integration
@@ -36,8 +38,8 @@ def overload(x):
3638
return x
3739

3840

39-
_local = ContextVar("sentry_current_hub")
40-
_initial_client = None
41+
_local = ContextVar("sentry_current_hub") # type: ignore
42+
_initial_client = None # type: Optional[weakref.ReferenceType[Client]]
4143

4244

4345
def _should_send_default_pii():
@@ -50,9 +52,11 @@ def _should_send_default_pii():
5052

5153
class _InitGuard(object):
5254
def __init__(self, client):
55+
# type: (Client) -> None
5356
self._client = client
5457

5558
def __enter__(self):
59+
# type: () -> _InitGuard
5660
return self
5761

5862
def __exit__(self, exc_type, exc_value, tb):
@@ -103,6 +107,7 @@ def __exit__(self, exc_type, exc_value, tb):
103107

104108
class _ScopeManager(object):
105109
def __init__(self, hub):
110+
# type: (Hub) -> None
106111
self._hub = hub
107112
self._original_len = len(hub._stack)
108113
self._layer = hub._stack[-1]
@@ -157,8 +162,13 @@ class Hub(with_metaclass(HubMeta)): # type: ignore
157162

158163
_stack = None # type: List[Tuple[Optional[Client], Scope]]
159164

165+
# Mypy doesn't pick up on the metaclass.
166+
if False:
167+
current = None # type: Hub
168+
main = None # type: Hub
169+
160170
def __init__(self, client_or_hub=None, scope=None):
161-
# type: (Union[Hub, Client], Optional[Any]) -> None
171+
# type: (Optional[Union[Hub, Client]], Optional[Any]) -> None
162172
if isinstance(client_or_hub, Hub):
163173
hub = client_or_hub
164174
client, other_scope = hub._stack[-1]
@@ -197,7 +207,7 @@ def run(self, callback):
197207
return callback()
198208

199209
def get_integration(self, name_or_class):
200-
# type: (Union[str, Integration]) -> Any
210+
# type: (Union[str, Type[Integration]]) -> Any
201211
"""Returns the integration for this hub by name or class. If there
202212
is no client bound or the client does not have that integration
203213
then `None` is returned.
@@ -218,14 +228,15 @@ def get_integration(self, name_or_class):
218228
if rv is not None:
219229
return rv
220230

221-
initial_client = _initial_client
222-
if initial_client is not None:
223-
initial_client = initial_client()
231+
if _initial_client is not None:
232+
initial_client = _initial_client()
233+
else:
234+
initial_client = None
224235

225236
if (
226237
initial_client is not None
227238
and initial_client is not client
228-
and initial_client.integrations.get(name_or_class) is not None
239+
and initial_client.integrations.get(integration_name) is not None
229240
):
230241
warning = (
231242
"Integration %r attempted to run but it was only "
@@ -254,7 +265,7 @@ def bind_client(self, new):
254265
self._stack[-1] = (new, top[1])
255266

256267
def capture_event(self, event, hint=None):
257-
# type: (Event, Hint) -> Optional[str]
268+
# type: (Event, Optional[Hint]) -> Optional[str]
258269
"""Captures an event. The return value is the ID of the event.
259270
260271
The event is a dictionary following the Sentry v7/v8 protocol
@@ -306,9 +317,10 @@ def capture_exception(self, error=None):
306317
return None
307318

308319
def _capture_internal_exception(self, exc_info):
320+
# type: (_OptExcInfo) -> Any
309321
"""Capture an exception that is likely caused by a bug in the SDK
310322
itself."""
311-
logger.error("Internal error in sentry_sdk", exc_info=exc_info)
323+
logger.error("Internal error in sentry_sdk", exc_info=exc_info) # type: ignore
312324

313325
def add_breadcrumb(self, crumb=None, hint=None, **kwargs):
314326
# type: (Optional[Breadcrumb], Optional[BreadcrumbHint], **Any) -> None

sentry_sdk/integrations/__init__.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,7 @@ def iter_default_integrations():
3333

3434
if isinstance(iter_default_integrations.__doc__, str):
3535
for import_string in import_strings:
36-
iter_default_integrations.__doc__ += "\n- `{}`".format( # type: ignore
37-
import_string
38-
)
36+
iter_default_integrations.__doc__ += "\n- `{}`".format(import_string)
3937

4038
return iter_default_integrations
4139

@@ -72,7 +70,7 @@ def setup_integrations(integrations, with_defaults=True):
7270
instance = integration_cls()
7371
integrations[instance.identifier] = instance
7472

75-
for identifier, integration in iteritems(integrations):
73+
for identifier, integration in iteritems(integrations): # type: ignore
7674
with _installer_lock:
7775
if identifier not in _installed_integrations:
7876
logger.debug(
@@ -113,6 +111,7 @@ class Integration(object):
113111

114112
@staticmethod
115113
def setup_once():
114+
# type: () -> None
116115
"""
117116
Initialize the integration.
118117

0 commit comments

Comments
 (0)