Skip to content

Commit 5717f1b

Browse files
ref: Event Type (#2753)
Implements type hinting for Event via a TypedDict. This commit mainly adjusts type hints; however, there are also some minor code changes to make the code type-safe following the new changes. Some items in the Event could have their types expanded by being defined as TypedDicts themselves. These items have been indicated with TODO comments. Fixes GH-2357
1 parent 1a8db5e commit 5717f1b

33 files changed

+176
-96
lines changed

sentry_sdk/_types.py

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99

1010

1111
if TYPE_CHECKING:
12+
from collections.abc import MutableMapping
13+
14+
from datetime import datetime
15+
1216
from types import TracebackType
1317
from typing import Any
1418
from typing import Callable
@@ -19,13 +23,69 @@
1923
from typing import Tuple
2024
from typing import Type
2125
from typing import Union
22-
from typing_extensions import Literal
26+
from typing_extensions import Literal, TypedDict
27+
28+
# "critical" is an alias of "fatal" recognized by Relay
29+
LogLevelStr = Literal["fatal", "critical", "error", "warning", "info", "debug"]
30+
31+
Event = TypedDict(
32+
"Event",
33+
{
34+
"breadcrumbs": dict[
35+
Literal["values"], list[dict[str, Any]]
36+
], # TODO: We can expand on this type
37+
"check_in_id": str,
38+
"contexts": dict[str, dict[str, object]],
39+
"dist": str,
40+
"duration": Optional[float],
41+
"environment": str,
42+
"errors": list[dict[str, Any]], # TODO: We can expand on this type
43+
"event_id": str,
44+
"exception": dict[
45+
Literal["values"], list[dict[str, Any]]
46+
], # TODO: We can expand on this type
47+
"extra": MutableMapping[str, object],
48+
"fingerprint": list[str],
49+
"level": LogLevelStr,
50+
"logentry": Mapping[str, object],
51+
"logger": str,
52+
"measurements": dict[str, object],
53+
"message": str,
54+
"modules": dict[str, str],
55+
"monitor_config": Mapping[str, object],
56+
"monitor_slug": Optional[str],
57+
"platform": Literal["python"],
58+
"profile": object, # Should be sentry_sdk.profiler.Profile, but we can't import that here due to circular imports
59+
"release": str,
60+
"request": dict[str, object],
61+
"sdk": Mapping[str, object],
62+
"server_name": str,
63+
"spans": list[dict[str, object]],
64+
"stacktrace": dict[
65+
str, object
66+
], # We access this key in the code, but I am unsure whether we ever set it
67+
"start_timestamp": datetime,
68+
"status": Optional[str],
69+
"tags": MutableMapping[
70+
str, str
71+
], # Tags must be less than 200 characters each
72+
"threads": dict[
73+
Literal["values"], list[dict[str, Any]]
74+
], # TODO: We can expand on this type
75+
"timestamp": Optional[datetime], # Must be set before sending the event
76+
"transaction": str,
77+
"transaction_info": Mapping[str, Any], # TODO: We can expand on this type
78+
"type": Literal["check_in", "transaction"],
79+
"user": dict[str, object],
80+
"_metrics_summary": dict[str, object],
81+
},
82+
total=False,
83+
)
2384

2485
ExcInfo = Tuple[
2586
Optional[Type[BaseException]], Optional[BaseException], Optional[TracebackType]
2687
]
2788

28-
Event = Dict[str, Any]
2989
Hint = Dict[str, Any]
3090

3191
Breadcrumb = Dict[str, Any]

sentry_sdk/api.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
BreadcrumbHint,
2323
ExcInfo,
2424
MeasurementUnit,
25+
LogLevelStr,
2526
)
2627
from sentry_sdk.tracing import Span
2728

@@ -91,7 +92,7 @@ def capture_event(
9192
@hubmethod
9293
def capture_message(
9394
message, # type: str
94-
level=None, # type: Optional[str]
95+
level=None, # type: Optional[LogLevelStr]
9596
scope=None, # type: Optional[Any]
9697
**scope_kwargs # type: Any
9798
):
@@ -189,7 +190,7 @@ def set_user(value):
189190

190191
@scopemethod
191192
def set_level(value):
192-
# type: (str) -> None
193+
# type: (LogLevelStr) -> None
193194
return Hub.current.scope.set_level(value)
194195

195196

sentry_sdk/client.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
try:
2+
from collections.abc import Mapping
3+
except ImportError:
4+
from collections import Mapping # type: ignore[attr-defined]
5+
16
from importlib import import_module
27
import os
38
import uuid
@@ -38,7 +43,7 @@
3843
from sentry_sdk.utils import ContextVar
3944
from sentry_sdk.sessions import SessionFlusher
4045
from sentry_sdk.envelope import Envelope
41-
from sentry_sdk.profiler import has_profiling_enabled, setup_profiler
46+
from sentry_sdk.profiler import has_profiling_enabled, Profile, setup_profiler
4247
from sentry_sdk.scrubber import EventScrubber
4348
from sentry_sdk.monitor import Monitor
4449
from sentry_sdk.spotlight import setup_spotlight
@@ -393,7 +398,7 @@ def _prepare_event(
393398

394399
for key in "release", "environment", "server_name", "dist":
395400
if event.get(key) is None and self.options[key] is not None:
396-
event[key] = text_type(self.options[key]).strip()
401+
event[key] = text_type(self.options[key]).strip() # type: ignore[literal-required]
397402
if event.get("sdk") is None:
398403
sdk_info = dict(SDK_INFO)
399404
sdk_info["integrations"] = sorted(self.integrations.keys())
@@ -567,7 +572,7 @@ def _update_session_from_event(
567572
errored = True
568573
for error in exceptions:
569574
mechanism = error.get("mechanism")
570-
if mechanism and mechanism.get("handled") is False:
575+
if isinstance(mechanism, Mapping) and mechanism.get("handled") is False:
571576
crashed = True
572577
break
573578

@@ -659,15 +664,15 @@ def capture_event(
659664
headers = {
660665
"event_id": event_opt["event_id"],
661666
"sent_at": format_timestamp(datetime_utcnow()),
662-
}
667+
} # type: dict[str, object]
663668

664669
if dynamic_sampling_context:
665670
headers["trace"] = dynamic_sampling_context
666671

667672
envelope = Envelope(headers=headers)
668673

669674
if is_transaction:
670-
if profile is not None:
675+
if isinstance(profile, Profile):
671676
envelope.add_profile(profile.to_json(event_opt, self.options))
672677
envelope.add_transaction(event_opt)
673678
elif is_checkin:

sentry_sdk/crons/api.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
if TYPE_CHECKING:
88
from typing import Any, Dict, Optional
9+
from sentry_sdk._types import Event
910

1011

1112
def _create_check_in_event(
@@ -15,7 +16,7 @@ def _create_check_in_event(
1516
duration_s=None,
1617
monitor_config=None,
1718
):
18-
# type: (Optional[str], Optional[str], Optional[str], Optional[float], Optional[Dict[str, Any]]) -> Dict[str, Any]
19+
# type: (Optional[str], Optional[str], Optional[str], Optional[float], Optional[Dict[str, Any]]) -> Event
1920
options = Hub.current.client.options if Hub.current.client else {}
2021
check_in_id = check_in_id or uuid.uuid4().hex # type: str
2122

@@ -27,7 +28,7 @@ def _create_check_in_event(
2728
"duration": duration_s,
2829
"environment": options.get("environment", None),
2930
"release": options.get("release", None),
30-
}
31+
} # type: Event
3132

3233
if monitor_config:
3334
check_in["monitor_config"] = monitor_config

sentry_sdk/hub.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
Breadcrumb,
4141
BreadcrumbHint,
4242
ExcInfo,
43+
LogLevelStr,
4344
)
4445
from sentry_sdk.consts import ClientConstructor
4546

@@ -335,7 +336,7 @@ def capture_event(self, event, hint=None, scope=None, **scope_kwargs):
335336
return last_event_id
336337

337338
def capture_message(self, message, level=None, scope=None, **scope_kwargs):
338-
# type: (str, Optional[str], Optional[Scope], Any) -> Optional[str]
339+
# type: (str, Optional[LogLevelStr], Optional[Scope], Any) -> Optional[str]
339340
"""
340341
Captures a message.
341342

sentry_sdk/integrations/_wsgi_common.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from typing import Dict
2323
from typing import Optional
2424
from typing import Union
25+
from sentry_sdk._types import Event
2526

2627

2728
SENSITIVE_ENV_KEYS = (
@@ -59,7 +60,7 @@ def __init__(self, request):
5960
self.request = request
6061

6162
def extract_into_event(self, event):
62-
# type: (Dict[str, Any]) -> None
63+
# type: (Event) -> None
6364
client = Hub.current.client
6465
if client is None:
6566
return

sentry_sdk/integrations/aiohttp.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,12 @@
4848
from aiohttp import TraceRequestStartParams, TraceRequestEndParams
4949
from types import SimpleNamespace
5050
from typing import Any
51-
from typing import Dict
5251
from typing import Optional
5352
from typing import Tuple
5453
from typing import Union
5554

5655
from sentry_sdk.utils import ExcInfo
57-
from sentry_sdk._types import EventProcessor
56+
from sentry_sdk._types import Event, EventProcessor
5857

5958

6059
TRANSACTION_STYLE_VALUES = ("handler_name", "method_and_path_pattern")
@@ -256,10 +255,10 @@ async def on_request_end(session, trace_config_ctx, params):
256255
def _make_request_processor(weak_request):
257256
# type: (weakref.ReferenceType[Request]) -> EventProcessor
258257
def aiohttp_processor(
259-
event, # type: Dict[str, Any]
260-
hint, # type: Dict[str, Tuple[type, BaseException, Any]]
258+
event, # type: Event
259+
hint, # type: dict[str, Tuple[type, BaseException, Any]]
261260
):
262-
# type: (...) -> Dict[str, Any]
261+
# type: (...) -> Event
263262
request = weak_request()
264263
if request is None:
265264
return event

sentry_sdk/integrations/ariadne.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from typing import Any, Dict, List, Optional
2424
from ariadne.types import GraphQLError, GraphQLResult, GraphQLSchema, QueryParser # type: ignore
2525
from graphql.language.ast import DocumentNode # type: ignore
26-
from sentry_sdk._types import EventProcessor
26+
from sentry_sdk._types import Event, EventProcessor
2727

2828

2929
class AriadneIntegration(Integration):
@@ -131,7 +131,7 @@ def _make_request_event_processor(data):
131131
"""Add request data and api_target to events."""
132132

133133
def inner(event, hint):
134-
# type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any]
134+
# type: (Event, dict[str, Any]) -> Event
135135
if not isinstance(data, dict):
136136
return event
137137

@@ -163,7 +163,7 @@ def _make_response_event_processor(response):
163163
"""Add response data to the event's response context."""
164164

165165
def inner(event, hint):
166-
# type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any]
166+
# type: (Event, dict[str, Any]) -> Event
167167
with capture_internal_exceptions():
168168
if _should_send_default_pii() and response.get("errors"):
169169
contexts = event.setdefault("contexts", {})

sentry_sdk/integrations/bottle.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ def _make_request_event_processor(app, request, integration):
200200
# type: (Bottle, LocalRequest, BottleIntegration) -> EventProcessor
201201

202202
def event_processor(event, hint):
203-
# type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any]
203+
# type: (Event, dict[str, Any]) -> Event
204204
_set_transaction_name_and_source(event, integration.transaction_style, request)
205205

206206
with capture_internal_exceptions():

sentry_sdk/integrations/django/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ def sentry_patched_get_response(self, request):
472472
def _make_wsgi_request_event_processor(weak_request, integration):
473473
# type: (Callable[[], WSGIRequest], DjangoIntegration) -> EventProcessor
474474
def wsgi_request_event_processor(event, hint):
475-
# type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any]
475+
# type: (Event, dict[str, Any]) -> Event
476476
# if the request is gone we are fine not logging the data from
477477
# it. This might happen if the processor is pushed away to
478478
# another thread.
@@ -570,7 +570,7 @@ def parsed_body(self):
570570

571571

572572
def _set_user_info(request, event):
573-
# type: (WSGIRequest, Dict[str, Any]) -> None
573+
# type: (WSGIRequest, Event) -> None
574574
user_info = event.setdefault("user", {})
575575

576576
user = getattr(request, "user", None)

0 commit comments

Comments
 (0)