Browse Source

chore(grouping): Add types to various grouping functions (#68568)

This adds types to a number of grouping-related functions, mostly because I was trying to figure out how they worked and knowing types makes that easier to reason about.
Katie Byers 11 months ago
parent
commit
c7ffe64a02

+ 8 - 3
src/sentry/eventstore/models.py

@@ -18,6 +18,7 @@ from django.utils.functional import cached_property
 from sentry import eventtypes
 from sentry.db.models import NodeData
 from sentry.grouping.result import CalculatedHashes
+from sentry.grouping.variants import BaseVariant, KeyedVariants
 from sentry.interfaces.base import Interface, get_interfaces
 from sentry.issues.grouptype import GroupCategory
 from sentry.issues.issue_occurrence import IssueOccurrence
@@ -374,7 +375,9 @@ class BaseEvent(metaclass=abc.ABCMeta):
             variants=[*flat_variants, *hierarchical_variants],
         )
 
-    def get_sorted_grouping_variants(self, force_config: StrategyConfiguration | None = None):
+    def get_sorted_grouping_variants(
+        self, force_config: StrategyConfiguration | None = None
+    ) -> tuple[KeyedVariants, KeyedVariants]:
         """Get grouping variants sorted into flat and hierarchical variants"""
         from sentry.grouping.api import sort_grouping_variants
 
@@ -382,7 +385,9 @@ class BaseEvent(metaclass=abc.ABCMeta):
         return sort_grouping_variants(variants)
 
     @staticmethod
-    def _hashes_from_sorted_grouping_variants(variants):
+    def _hashes_from_sorted_grouping_variants(
+        variants: KeyedVariants,
+    ) -> tuple[list[str], list[Any]]:
         """Create hashes from variants and filter out duplicates and None values"""
 
         from sentry.grouping.variants import ComponentVariant
@@ -421,7 +426,7 @@ class BaseEvent(metaclass=abc.ABCMeta):
         self,
         force_config: StrategyConfiguration | GroupingConfig | str | None = None,
         normalize_stacktraces: bool = False,
-    ):
+    ) -> dict[str, BaseVariant]:
         """
         This is similar to `get_hashes` but will instead return the
         grouping components for each variant in a dictionary.

+ 6 - 3
src/sentry/grouping/api.py

@@ -29,6 +29,7 @@ from sentry.grouping.variants import (
     ComponentVariant,
     CustomFingerprintVariant,
     FallbackVariant,
+    KeyedVariants,
     SaltedComponentVariant,
 )
 from sentry.models.grouphash import GroupHash
@@ -279,10 +280,12 @@ def apply_server_fingerprinting(event, config, allow_custom_title=True):
             event["_fingerprint_info"]["is_builtin"] = True
 
 
-def _get_calculated_grouping_variants_for_event(event, context):
+def _get_calculated_grouping_variants_for_event(
+    event: Event, context: GroupingContext
+) -> dict[str, GroupingComponent]:
     winning_strategy: str | None = None
     precedence_hint: str | None = None
-    per_variant_components: dict[str, list[BaseVariant]] = {}
+    per_variant_components: dict[str, list[GroupingComponent]] = {}
 
     for strategy in context.config.iter_strategies():
         # Defined in src/sentry/grouping/strategies/base.py
@@ -392,7 +395,7 @@ def get_grouping_variants_for_event(
     return rv
 
 
-def sort_grouping_variants(variants):
+def sort_grouping_variants(variants: dict[str, BaseVariant]) -> tuple[KeyedVariants, KeyedVariants]:
     """Sort a sequence of variants into flat and hierarchical variants"""
 
     flat_variants = []

+ 2 - 2
src/sentry/grouping/result.py

@@ -3,7 +3,7 @@ from dataclasses import dataclass
 from typing import Any, Optional, TypedDict
 
 from sentry.db.models import NodeData
-from sentry.grouping.variants import BaseVariant
+from sentry.grouping.variants import KeyedVariants
 from sentry.utils.safe import get_path, safe_execute, set_path
 
 EventMetadata = dict[str, Any]
@@ -100,7 +100,7 @@ class CalculatedHashes:
     hashes: Sequence[str]
     hierarchical_hashes: Sequence[str]
     tree_labels: Sequence[TreeLabel | None]
-    variants: list[tuple[str, BaseVariant]] | None = None
+    variants: KeyedVariants | None = None
 
     def write_to_event(self, event_data: NodeData) -> None:
         event_data["hashes"] = self.hashes

+ 11 - 7
src/sentry/grouping/variants.py

@@ -1,6 +1,7 @@
 from __future__ import annotations
 
 from sentry.grouping.utils import hash_from_values, is_default_fingerprint_var
+from sentry.types.misc import KeyedList
 
 
 class BaseVariant:
@@ -10,7 +11,7 @@ class BaseVariant:
     # This is true if `get_hash` does not return `None`.
     contributes = True
 
-    def get_hash(self):
+    def get_hash(self) -> str | None:
         return None
 
     @property
@@ -29,6 +30,9 @@ class BaseVariant:
         return f"<{self.__class__.__name__} {self.get_hash()!r} ({self.type})>"
 
 
+KeyedVariants = KeyedList[BaseVariant]
+
+
 class ChecksumVariant(BaseVariant):
     """A checksum variant returns a single hardcoded hash."""
 
@@ -44,7 +48,7 @@ class ChecksumVariant(BaseVariant):
             return "hashed legacy checksum"
         return "legacy checksum"
 
-    def get_hash(self):
+    def get_hash(self) -> str | None:
         return self.hash
 
 
@@ -52,7 +56,7 @@ class FallbackVariant(BaseVariant):
     id = "fallback"
     contributes = True
 
-    def get_hash(self):
+    def get_hash(self) -> str | None:
         return hash_from_values([])
 
 
@@ -75,7 +79,7 @@ class PerformanceProblemVariant(BaseVariant):
         self.event_performance_problem = event_performance_problem
         self.problem = event_performance_problem.problem
 
-    def get_hash(self):
+    def get_hash(self) -> str | None:
         return self.problem.fingerprint
 
     def _get_metadata_as_dict(self):
@@ -104,7 +108,7 @@ class ComponentVariant(BaseVariant):
     def contributes(self):
         return self.component.contributes
 
-    def get_hash(self):
+    def get_hash(self) -> str | None:
         return self.component.get_hash()
 
     def _get_metadata_as_dict(self):
@@ -146,7 +150,7 @@ class CustomFingerprintVariant(BaseVariant):
     def description(self):
         return "custom fingerprint"
 
-    def get_hash(self):
+    def get_hash(self) -> str | None:
         return hash_from_values(self.values)
 
     def _get_metadata_as_dict(self):
@@ -177,7 +181,7 @@ class SaltedComponentVariant(ComponentVariant):
     def description(self):
         return "modified " + self.component.description
 
-    def get_hash(self):
+    def get_hash(self) -> str | None:
         if not self.component.contributes:
             return None
         final_values = []

+ 8 - 0
src/sentry/types/misc.py

@@ -0,0 +1,8 @@
+from typing import TypeVar
+
+# This is meant to be a base type from which we can derive types useful when we have what is in
+# spirit a dictionary but is in practice a list of tuples. For example:
+#     KeyedInts = KeyedList[int]
+#     some_ints: KeyedInts = [("one", 1), ("two", 2)]
+T = TypeVar("T")
+KeyedList = list[tuple[str, T]]