Repository: getsentry/sentry
Pull request: ref(event_manager): Fix typing issues for event_manager
We want to make a lot of changes to event_manager and we need to have backend typing in place for the upcoming work.

Fixes https://github.com/getsentry/sentry/issues/52877

Problem statement:
Add typing for event_manager
Currently, we're not checking backend types for it. We're planning to improve the performance of the save_event code, thus, we should add typing before we keep making more changes.

After removing it from `pyproject.toml` we get the following:
```
make backend-typing
--> Running Python typing checks
mypy
src/sentry/event_manager.py:48: error: Module "sentry.attachments" does not explicitly export attribute "MissingAttachmentChunks"  [attr-defined]
src/sentry/event_manager.py:58: error: Module "sentry.eventtypes" does not explicitly export attribute "CspEvent"  [attr-defined]
src/sentry/event_manager.py:58: error: Module "sentry.eventtypes" does not explicitly export attribute "DefaultEvent"  [attr-defined]
src/sentry/event_manager.py:58: error: Module "sentry.eventtypes" does not explicitly export attribute "ErrorEvent"  [attr-defined]
src/sentry/event_manager.py:58: error: Module "sentry.eventtypes" does not explicitly export attribute "ExpectCTEvent"  [attr-defined]
src/sentry/event_manager.py:58: error: Module "sentry.eventtypes" does not explicitly export attribute "ExpectStapleEvent"  [attr-defined]
src/sentry/event_manager.py:58: error: Module "sentry.eventtypes" does not explicitly export attribute "HpkpEvent"  [attr-defined]
src/sentry/event_manager.py:58: error: Module "sentry.eventtypes" does not explicitly export attribute "TransactionEvent"  [attr-defined]
src/sentry/event_manager.py:1252: error: Module has no attribute "incr_multi"  [attr-defined]
src/sentry/event_manager.py:1255: error: Module has no attribute "record_multi"  [attr-defined]
src/sentry/event_manager.py:1258: error: Module has no attribute "record_frequency_multi"  [attr-defined]
src/sentry/event_manager.py:1930: error: Module has no attribute "refund"  [attr-defined]
src/sentry/event_manager.py:1967: error: Module has no attribute "refund"  [attr-defined]
src/sentry/event_manager.py:2091: error: Module has no attribute "refund"  [attr-defined]
src/sentry/event_manager.py:2158: error: Argument 1 to "BytesIO" has incompatible type "Callable[[CachedAttachment], Any]"; expected "Buffer"  [arg-type]
src/sentry/event_manager.py:2269: error: Argument 1 to "write_to_event" of "CalculatedHashes" has incompatible type "NodeData"; expected "Dict[str, Any]"  [arg-type]
Found 16 errors in 1 files (checked 4918 source files)
```

Hints from issue discussion:

Commit message: Implicit import

Patch to review:
diff --git a/pyproject.toml b/pyproject.toml
index d8f99472ec0357..6b08fc5df7236b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -442,7 +442,6 @@ module = [
     "sentry.discover.endpoints.discover_key_transactions",
     "sentry.discover.endpoints.serializers",
     "sentry.discover.tasks",
-    "sentry.event_manager",
     "sentry.eventstore.base",
     "sentry.eventstore.compressor",
     "sentry.eventstore.models",
@@ -1157,7 +1156,6 @@ module = [
     "tests.sentry.eventstore.test_models",
     "tests.sentry.eventstream.kafka.test_protocol",
     "tests.sentry.eventstream.test_eventstream",
-    "tests.sentry.eventtypes.test_default",
     "tests.sentry.eventtypes.test_error",
     "tests.sentry.features.test_manager",
     "tests.sentry.grouping.test_enhancer",
diff --git a/src/sentry/attachments/__init__.py b/src/sentry/attachments/__init__.py
index 64ad58c27512e4..e8ebbcc3cd055a 100644
--- a/src/sentry/attachments/__init__.py
+++ b/src/sentry/attachments/__init__.py
@@ -1,4 +1,4 @@
-__all__ = ["attachment_cache", "CachedAttachment"]
+__all__ = ["attachment_cache", "CachedAttachment", "MissingAttachmentChunks"]
 
 from django.conf import settings
 
diff --git a/src/sentry/event_manager.py b/src/sentry/event_manager.py
index ff38a52cd1814d..de130fb42ca564 100644
--- a/src/sentry/event_manager.py
+++ b/src/sentry/event_manager.py
@@ -55,15 +55,8 @@
 from sentry.culprit import generate_culprit
 from sentry.dynamic_sampling import LatestReleaseBias, LatestReleaseParams
 from sentry.eventstore.processing import event_processing_store
-from sentry.eventtypes import (
-    CspEvent,
-    DefaultEvent,
-    ErrorEvent,
-    ExpectCTEvent,
-    ExpectStapleEvent,
-    HpkpEvent,
-    TransactionEvent,
-)
+from sentry.eventtypes import EventType
+from sentry.eventtypes.transaction import TransactionEvent
 from sentry.grouping.api import (
     BackgroundGroupingConfigLoader,
     GroupingConfig,
@@ -1249,13 +1242,15 @@ def _tsdb_record_all_metrics(jobs: Sequence[Job]) -> None:
             records.append((TSDBModel.users_affected_by_project, project_id, (user.tag_value,)))
 
         if incrs:
-            tsdb.incr_multi(incrs, timestamp=event.datetime, environment_id=environment.id)
+            tsdb.backend.incr_multi(incrs, timestamp=event.datetime, environment_id=environment.id)
 
         if records:
-            tsdb.record_multi(records, timestamp=event.datetime, environment_id=environment.id)
+            tsdb.backend.record_multi(
+                records, timestamp=event.datetime, environment_id=environment.id
+            )
 
         if frequencies:
-            tsdb.record_frequency_multi(frequencies, timestamp=event.datetime)
+            tsdb.backend.record_frequency_multi(frequencies, timestamp=event.datetime)
 
 
 @metrics.wraps("save_event.nodestore_save_many")
@@ -1427,17 +1422,6 @@ def _get_event_user_impl(
     return euser
 
 
-EventType = Union[
-    DefaultEvent,
-    ErrorEvent,
-    CspEvent,
-    HpkpEvent,
-    ExpectCTEvent,
-    ExpectStapleEvent,
-    TransactionEvent,
-]
-
-
 def get_event_type(data: Mapping[str, Any]) -> EventType:
     return eventtypes.get(data.get("type", "default"))()
 
@@ -1927,7 +1911,7 @@ def discard_event(job: Job, attachments: Sequence[Attachment]) -> None:
 
     project = job["event"].project
 
-    quotas.refund(
+    quotas.backend.refund(
         project,
         key=job["project_key"],
         timestamp=job["start_time"],
@@ -1964,7 +1948,7 @@ def discard_event(job: Job, attachments: Sequence[Attachment]) -> None:
         )
 
     if attachment_quantity:
-        quotas.refund(
+        quotas.backend.refund(
             project,
             key=job["project_key"],
             timestamp=job["start_time"],
@@ -2088,7 +2072,7 @@ def filter_attachments_for_group(attachments: list[Attachment], job: Job) -> lis
         cache.set(crashreports_key, max_crashreports, CRASH_REPORT_TIMEOUT)
 
     if refund_quantity:
-        quotas.refund(
+        quotas.backend.refund(
             project,
             key=job["project_key"],
             timestamp=job["start_time"],
@@ -2155,7 +2139,7 @@ def save_attachment(
         type=attachment.type,
         headers={"Content-Type": attachment.content_type},
     )
-    file.putfile(BytesIO(data), blob_size=settings.SENTRY_ATTACHMENT_BLOB_SIZE)
+    file.putfile(BytesIO(data), blob_size=settings.SENTRY_ATTACHMENT_BLOB_SIZE)  # type: ignore
 
     EventAttachment.objects.create(
         event_id=event_id,
@@ -2266,7 +2250,8 @@ def _calculate_event_grouping(
             event.data["grouping_config"] = get_grouping_config_dict_for_project(project)
             hashes = event.get_hashes()
 
-    hashes.write_to_event(event.data)
+    # Using cast to satisfy mypy
+    hashes.write_to_event(cast(Dict[str, Any], event.data))
     return hashes
 
 
diff --git a/src/sentry/eventtypes/__init__.py b/src/sentry/eventtypes/__init__.py
index 143f2e28eb93f2..c370aa787f3da2 100644
--- a/src/sentry/eventtypes/__init__.py
+++ b/src/sentry/eventtypes/__init__.py
@@ -1,3 +1,5 @@
+from typing import Union
+
 from .base import DefaultEvent
 from .error import ErrorEvent
 from .generic import GenericEvent
@@ -17,3 +19,13 @@
 
 get = default_manager.get
 register = default_manager.register
+
+EventType = Union[
+    DefaultEvent,
+    ErrorEvent,
+    CspEvent,
+    HpkpEvent,
+    ExpectCTEvent,
+    ExpectStapleEvent,
+    TransactionEvent,
+]
diff --git a/src/sentry/tsdb/__init__.py b/src/sentry/tsdb/__init__.py
index 1aa88203fec695..847cf864aa0f7a 100644
--- a/src/sentry/tsdb/__init__.py
+++ b/src/sentry/tsdb/__init__.py
@@ -5,6 +5,7 @@
 from .base import BaseTSDB
 from .dummy import DummyTSDB
 
-LazyServiceWrapper(
+backend = LazyServiceWrapper(
     BaseTSDB, settings.SENTRY_TSDB, settings.SENTRY_TSDB_OPTIONS, dangerous=[DummyTSDB]
-).expose(locals())
+)
+backend.expose(locals())
diff --git a/tests/sentry/eventtypes/test_default.py b/tests/sentry/eventtypes/test_default.py
index 5c9443de01af85..de36b5c3820f72 100644
--- a/tests/sentry/eventtypes/test_default.py
+++ b/tests/sentry/eventtypes/test_default.py
@@ -1,4 +1,4 @@
-from sentry.eventtypes import DefaultEvent
+from sentry.eventtypes.base import DefaultEvent
 from sentry.testutils import TestCase
 from sentry.testutils.silo import region_silo_test
 


Code review comments:
diff_hunk: @@ -2155,7 +2139,7 @@ def save_attachment(
         type=attachment.type,
         headers={"Content-Type": attachment.content_type},
     )
-    file.putfile(BytesIO(data), blob_size=settings.SENTRY_ATTACHMENT_BLOB_SIZE)
+    file.putfile(BytesIO(data), blob_size=settings.SENTRY_ATTACHMENT_BLOB_SIZE)  # type: ignore
path: src/sentry/event_manager.py
text: @user1:
(I think this got lost from the other review but want to make sure it gets addressed)

the problem here is the type alias above -- it shouldn't be `Type[Cached...]` ( don't remember the name and it's offscreen) -- the type alias should probably be removed entirely at that point since it'll just be `A = B` and just meaningless indirection

diff_hunk: @@ -2266,7 +2250,8 @@ def _calculate_event_grouping(
             event.data["grouping_config"] = get_grouping_config_dict_for_project(project)
             hashes = event.get_hashes()
 
-    hashes.write_to_event(event.data)
+    # Using cast to satisfy mypy
+    hashes.write_to_event(cast(Dict[str, Any], event.data))
path: src/sentry/event_manager.py
text: @user1:
the types should get updated here rather than `cast` -- `cast` and `type: ignore` should generally only be used for mypy bugs