-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ref(grouping): Add types to enhancement methods #87370
Changes from all commits
f91b871
d8e38c4
3bcdf2e
fb44d70
4eafd28
8c59917
a637739
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,7 +6,7 @@ | |
import zlib | ||
from collections import Counter | ||
from collections.abc import Sequence | ||
from typing import Any, Literal | ||
from typing import Any, Literal, NotRequired, TypedDict | ||
|
||
import msgpack | ||
import sentry_sdk | ||
|
@@ -23,7 +23,7 @@ | |
from .exceptions import InvalidEnhancerConfig | ||
from .matchers import create_match_frame | ||
from .parser import parse_enhancements | ||
from .rules import EnhancementRule | ||
from .rules import EnhancementRule, EnhancementRuleDict | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
@@ -131,14 +131,26 @@ def keep_profiling_rules(config: str) -> str: | |
return "\n".join(filtered_rules) | ||
|
||
|
||
class EnhancementsDict(TypedDict): | ||
id: str | None | ||
bases: list[str] | ||
latest: bool | ||
rules: NotRequired[list[EnhancementRuleDict]] | ||
|
||
|
||
class Enhancements: | ||
# NOTE: You must add a version to ``VERSIONS`` any time attributes are added | ||
# to this class, s.t. no enhancements lacking these attributes are loaded | ||
# from cache. | ||
# See ``GroupingConfigLoader._get_enhancements`` in src/sentry/grouping/api.py. | ||
|
||
def __init__( | ||
self, rules, rust_enhancements: RustEnhancements, version=None, bases=None, id=None | ||
self, | ||
rules: list[EnhancementRule], | ||
rust_enhancements: RustEnhancements, | ||
version: int | None = None, | ||
bases: list[str] | None = None, | ||
id: str | None = None, | ||
): | ||
self.id = id | ||
self.rules = rules | ||
|
@@ -279,8 +291,8 @@ def assemble_stacktrace_component( | |
|
||
return stacktrace_component | ||
|
||
def as_dict(self, with_rules=False): | ||
rv = { | ||
def as_dict(self, with_rules: bool = False) -> EnhancementsDict: | ||
rv: EnhancementsDict = { | ||
"id": self.id, | ||
"bases": self.bases, | ||
"latest": projectoptions.lookup_well_known_key( | ||
|
@@ -292,7 +304,8 @@ def as_dict(self, with_rules=False): | |
rv["rules"] = [x.as_dict() for x in self.rules] | ||
return rv | ||
|
||
def _to_config_structure(self): | ||
def _to_config_structure(self) -> list[Any]: | ||
# TODO: Can we switch this to a tuple so we can type it more exactly? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe a named tuple? from collections import namedtuple
ConfigStructure = namedtuple('ConfigStructure', ['version', 'bases', 'rules'])
return ConfigStructure(
version=self.version,
bases=self.bases,
rules=[x._to_config_structure(self.version) for x in self.rules],
) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup, that'd work. I'd rather leave it for a future PR, though, as my goal with this one is purely to add types, not change behavior. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Works for me. |
||
return [ | ||
self.version, | ||
self.bases, | ||
|
@@ -305,7 +318,11 @@ def dumps(self) -> str: | |
return base64.urlsafe_b64encode(compressed).decode("ascii").strip("=") | ||
|
||
@classmethod | ||
def _from_config_structure(cls, data, rust_enhancements: RustEnhancements) -> Enhancements: | ||
def _from_config_structure( | ||
cls, | ||
data: list[Any], | ||
rust_enhancements: RustEnhancements, | ||
) -> Enhancements: | ||
version, bases, rules = data | ||
if version not in VERSIONS: | ||
raise ValueError("Unknown version") | ||
|
@@ -317,7 +334,7 @@ def _from_config_structure(cls, data, rust_enhancements: RustEnhancements) -> En | |
) | ||
|
||
@classmethod | ||
def loads(cls, data) -> Enhancements: | ||
def loads(cls, data: str | bytes) -> Enhancements: | ||
if isinstance(data, str): | ||
data = data.encode("ascii", "ignore") | ||
padded = data + b"=" * (4 - (len(data) % 4)) | ||
|
@@ -337,7 +354,9 @@ def loads(cls, data) -> Enhancements: | |
|
||
@classmethod | ||
@sentry_sdk.tracing.trace | ||
def from_config_string(cls, s, bases=None, id=None) -> Enhancements: | ||
def from_config_string( | ||
cls, s: str, bases: list[str] | None = None, id: str | None = None | ||
) -> Enhancements: | ||
rust_enhancements = parse_rust_enhancements("config_string", s) | ||
|
||
rules = parse_enhancements(s) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use abstract types when within the signature (e.g. Sequence) and more specific ones when returning (e.g. list). Unless it gets on the way for whatever reason.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is definitely not the hill I want to die on, and I can switch it if you feel strongly. But before I go through and make a bunch of changes:
In a purely theoretical sense I guess I understand the principle, but in practice, for methods used purely internally, it always feels like overkill to me. Is there a strong argument in favor that I'm missing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I won't push hard on this. It is mostly as documented in the best practice document. I can understand the argument of internal usage.