1
1
from __future__ import annotations
2
2
3
3
from collections .abc import Generator , Iterator , Sequence
4
+ from enum import StrEnum
4
5
from typing import Any
5
6
6
7
from sentry .grouping .utils import hash_from_values
@@ -37,7 +38,7 @@ class GroupingComponent:
37
38
38
39
def __init__ (
39
40
self ,
40
- id : str ,
41
+ id : GroupingComponentID ,
41
42
hint : str | None = None ,
42
43
contributes : bool | None = None ,
43
44
values : Sequence [str | GroupingComponent ] | None = None ,
@@ -49,7 +50,7 @@ def __init__(
49
50
self .hint = DEFAULT_HINTS .get (id )
50
51
self .contributes = contributes
51
52
self .variant_provider = variant_provider
52
- self .values : Sequence [str | GroupingComponent ] = []
53
+ self ._values : Sequence [str | int | GroupingComponent ] = []
53
54
54
55
self .update (
55
56
hint = hint ,
@@ -61,6 +62,22 @@ def __init__(
61
62
def name (self ) -> str | None :
62
63
return KNOWN_MAJOR_COMPONENT_NAMES .get (self .id )
63
64
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
+
64
81
@property
65
82
def description (self ) -> str :
66
83
items = []
@@ -127,7 +144,7 @@ def shallow_copy(self) -> GroupingComponent:
127
144
rv .values = list (self .values )
128
145
return rv
129
146
130
- def iter_values (self ) -> Generator [str | GroupingComponent ]:
147
+ def iter_values (self ) -> Generator [str | int | GroupingComponent ]:
131
148
"""Recursively walks the component and flattens it into a list of
132
149
values.
133
150
"""
@@ -165,4 +182,78 @@ def as_dict(self) -> dict[str, Any]:
165
182
return rv
166
183
167
184
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