Skip to content

Commit 388b1b2

Browse files
committed
alternative to sub-classing groupedcomponent
1 parent 14cddb8 commit 388b1b2

File tree

1 file changed

+95
-4
lines changed

1 file changed

+95
-4
lines changed

src/sentry/grouping/component.py

+95-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
from collections.abc import Generator, Iterator, Sequence
4+
from enum import StrEnum
45
from typing import Any
56

67
from sentry.grouping.utils import hash_from_values
@@ -37,7 +38,7 @@ class GroupingComponent:
3738

3839
def __init__(
3940
self,
40-
id: str,
41+
id: GroupingComponentID,
4142
hint: str | None = None,
4243
contributes: bool | None = None,
4344
values: Sequence[str | GroupingComponent] | None = None,
@@ -49,7 +50,7 @@ def __init__(
4950
self.hint = DEFAULT_HINTS.get(id)
5051
self.contributes = contributes
5152
self.variant_provider = variant_provider
52-
self.values: Sequence[str | GroupingComponent] = []
53+
self._values: Sequence[str | int | GroupingComponent] = []
5354

5455
self.update(
5556
hint=hint,
@@ -61,6 +62,22 @@ def __init__(
6162
def name(self) -> str | None:
6263
return KNOWN_MAJOR_COMPONENT_NAMES.get(self.id)
6364

65+
@property
66+
def values(self) -> Sequence[str | int | GroupingComponent]:
67+
return self._values
68+
69+
@values.setter
70+
def values(self, values: Sequence[str | int | GroupingComponent]) -> None:
71+
for value in values:
72+
if isinstance(value, GroupingComponent):
73+
if value.id not in COMPONENT_VALUES[self.id]:
74+
raise ValueError(f"Invalid value for {self.id}: {value}")
75+
else:
76+
if type(value) not in ALLOWS_PRIMITIVES[self.id]:
77+
raise ValueError(f"Invalid value for {self.id}: {value}")
78+
79+
self._values = values
80+
6481
@property
6582
def description(self) -> str:
6683
items = []
@@ -127,7 +144,7 @@ def shallow_copy(self) -> GroupingComponent:
127144
rv.values = list(self.values)
128145
return rv
129146

130-
def iter_values(self) -> Generator[str | GroupingComponent]:
147+
def iter_values(self) -> Generator[str | int | GroupingComponent]:
131148
"""Recursively walks the component and flattens it into a list of
132149
values.
133150
"""
@@ -165,4 +182,78 @@ def as_dict(self) -> dict[str, Any]:
165182
return rv
166183

167184
def __repr__(self) -> str:
168-
return f"GroupingComponent({self.id!r}, hint={self.hint!r}, contributes={self.contributes!r}, values={self.values!r})"
185+
return f"{self.__class__.__name__}({self.id!r}, hint={self.hint!r}, contributes={self.contributes!r}, values={self.values!r})"
186+
187+
188+
class GroupingComponentID(StrEnum):
189+
MESSAGE = "message"
190+
EXCEPTION = "exception"
191+
CHAINED_EXCEPTION = "chained-exception"
192+
STACKTRACE = "stacktrace"
193+
THREADS = "threads"
194+
CSP = "csp"
195+
EXPECT_CT = "expect-ct"
196+
EXPECT_STAPLE = "expect-staple"
197+
HPKP = "hpkp"
198+
TEMPLATE = "template"
199+
FRAME = "frame"
200+
CONTEXT_LINE = "context-line"
201+
ERROR_TYPE = "type"
202+
ERROR_VALUE = "value"
203+
FILENAME = "filename"
204+
FUNCTION = "function"
205+
LINE_NUMBER = "lineno"
206+
MODULE = "module"
207+
NS_ERROR = "ns-error"
208+
SYMBOL = "symbol"
209+
HOSTNAME = "hostname"
210+
SALT = "salt"
211+
VIOLATION = "violation"
212+
URI = "uri"
213+
214+
215+
# Maps component IDs to allowed value types
216+
COMPONENT_VALUES: dict[GroupingComponentID, set[GroupingComponentID]] = {
217+
GroupingComponentID.EXCEPTION: {
218+
GroupingComponentID.ERROR_TYPE,
219+
GroupingComponentID.ERROR_VALUE,
220+
GroupingComponentID.NS_ERROR,
221+
GroupingComponentID.STACKTRACE,
222+
},
223+
GroupingComponentID.CHAINED_EXCEPTION: {GroupingComponentID.EXCEPTION},
224+
GroupingComponentID.STACKTRACE: {GroupingComponentID.FRAME},
225+
GroupingComponentID.THREADS: {GroupingComponentID.STACKTRACE},
226+
GroupingComponentID.CSP: {
227+
GroupingComponentID.SALT,
228+
GroupingComponentID.VIOLATION,
229+
GroupingComponentID.URI,
230+
},
231+
GroupingComponentID.EXPECT_CT: {GroupingComponentID.HOSTNAME, GroupingComponentID.SALT},
232+
GroupingComponentID.EXPECT_STAPLE: {GroupingComponentID.HOSTNAME, GroupingComponentID.SALT},
233+
GroupingComponentID.HPKP: {GroupingComponentID.HOSTNAME, GroupingComponentID.SALT},
234+
GroupingComponentID.TEMPLATE: {GroupingComponentID.CONTEXT_LINE, GroupingComponentID.FILENAME},
235+
GroupingComponentID.FRAME: {
236+
GroupingComponentID.CONTEXT_LINE,
237+
GroupingComponentID.FILENAME,
238+
GroupingComponentID.FUNCTION,
239+
GroupingComponentID.LINE_NUMBER,
240+
GroupingComponentID.MODULE,
241+
GroupingComponentID.SYMBOL,
242+
},
243+
}
244+
245+
ALLOWS_PRIMITIVES: dict[GroupingComponentID, set[type]] = {
246+
GroupingComponentID.CONTEXT_LINE: {str},
247+
GroupingComponentID.ERROR_TYPE: {str},
248+
GroupingComponentID.ERROR_VALUE: {str},
249+
GroupingComponentID.FILENAME: {str},
250+
GroupingComponentID.FUNCTION: {str},
251+
GroupingComponentID.LINE_NUMBER: {str},
252+
GroupingComponentID.MODULE: {str},
253+
GroupingComponentID.NS_ERROR: {str, int},
254+
GroupingComponentID.SYMBOL: {str},
255+
GroupingComponentID.HOSTNAME: {str},
256+
GroupingComponentID.SALT: {str},
257+
GroupingComponentID.VIOLATION: {str},
258+
GroupingComponentID.URI: {str},
259+
}

0 commit comments

Comments
 (0)