-
Notifications
You must be signed in to change notification settings - Fork 94
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
feat(py): Expose DataCategory via C-ABI #651
Changes from 1 commit
1862e99
809293c
6853f80
c6d209d
90c5aee
713ab33
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 |
---|---|---|
@@ -1,25 +1,98 @@ | ||
__all__ = ["SPAN_STATUS_CODE_TO_NAME", "SPAN_STATUS_NAME_TO_CODE"] | ||
|
||
|
||
SPAN_STATUS_CODE_TO_NAME = { | ||
0: "ok", | ||
1: "cancelled", | ||
2: "unknown_error", | ||
3: "invalid_argument", | ||
4: "deadline_exceeded", | ||
5: "not_found", | ||
6: "already_exists", | ||
7: "permission_denied", | ||
8: "resource_exhausted", | ||
9: "failed_precondition", | ||
10: "aborted", | ||
11: "out_of_range", | ||
12: "unimplemented", | ||
13: "internal_error", | ||
14: "unavailable", | ||
15: "data_loss", | ||
16: "unauthenticated", | ||
} | ||
|
||
SPAN_STATUS_NAME_TO_CODE = dict((v, k) for k, v in SPAN_STATUS_CODE_TO_NAME.items()) | ||
SPAN_STATUS_NAME_TO_CODE["unknown"] = SPAN_STATUS_NAME_TO_CODE["unknown_error"] | ||
from enum import IntEnum | ||
|
||
from sentry_relay._lowlevel import lib | ||
from sentry_relay.utils import decode_str, encode_str | ||
|
||
|
||
__all__ = ["DataCategory", "SPAN_STATUS_CODE_TO_NAME", "SPAN_STATUS_NAME_TO_CODE"] | ||
|
||
|
||
class BaseDataCategory(object): | ||
@classmethod | ||
def parse(cls, name): | ||
""" | ||
Parses a `DataCategory` from its API name. | ||
""" | ||
category = cls(lib.relay_data_category_parse(encode_str(name or ""))) | ||
if category == DataCategory.UNKNOWN: | ||
return None # Unknown is a Rust-only value, replace with None | ||
return category | ||
|
||
@classmethod | ||
def from_event_type(cls, event_type): | ||
""" | ||
Parses a `DataCategory` from an event type. | ||
""" | ||
s = encode_str(event_type or "") | ||
return cls(lib.relay_data_category_from_event_type(s)) | ||
|
||
@classmethod | ||
def event_categories(cls): | ||
""" | ||
Returns categories that count as events, including transactions. | ||
""" | ||
return frozenset( | ||
DataCategory.DEFAULT, | ||
DataCategory.ERROR, | ||
DataCategory.TRANSACTION, | ||
DataCategory.SECURITY, | ||
) | ||
|
||
@classmethod | ||
def error_categories(cls): | ||
""" | ||
Returns categories that count as traditional error tracking events. | ||
""" | ||
return frozenset( | ||
DataCategory.DEFAULT, DataCategory.ERROR, DataCategory.SECURITY | ||
) | ||
|
||
def api_name(self): | ||
""" | ||
Returns the API name of the given `DataCategory`. | ||
""" | ||
return decode_str(lib.relay_data_category_name(self.value), free=True) | ||
|
||
|
||
def _make_data_categories(): | ||
prefix = "RELAY_DATA_CATEGORY_" | ||
categories = {} | ||
|
||
for attr in dir(lib): | ||
if not attr.startswith(prefix): | ||
continue | ||
|
||
category_name = attr[len(prefix) :] | ||
categories[category_name] = getattr(lib, attr) | ||
|
||
data_categories = IntEnum( | ||
"DataCategory", categories, type=BaseDataCategory, module=__name__ | ||
) | ||
globals()[data_categories.__name__] = data_categories | ||
|
||
|
||
_make_data_categories() | ||
|
||
|
||
SPAN_STATUS_CODE_TO_NAME = {} | ||
SPAN_STATUS_NAME_TO_CODE = {} | ||
|
||
|
||
def _make_span_statuses(): | ||
prefix = "RELAY_SPAN_STATUS_" | ||
|
||
for attr in dir(lib): | ||
if not attr.startswith(prefix): | ||
continue | ||
|
||
status_name = attr[len(prefix) :].lower() | ||
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. I would say this is actually safe - per convention the constants must always match the default serialization. That said, if we ever diverge from this in the future (which we should not!) we can call into an API for this. 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. Yes this seems safe. |
||
status_code = getattr(lib, attr) | ||
|
||
SPAN_STATUS_CODE_TO_NAME[status_code] = status_name | ||
SPAN_STATUS_NAME_TO_CODE[status_name] = status_code | ||
|
||
# Legacy alias | ||
SPAN_STATUS_NAME_TO_CODE["unknown_error"] = SPAN_STATUS_NAME_TO_CODE["unknown"] | ||
|
||
|
||
_make_span_statuses() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
from sentry_relay import DataCategory, SPAN_STATUS_CODE_TO_NAME | ||
|
||
|
||
def test_parse_data_category(): | ||
assert DataCategory.parse("default") == DataCategory.DEFAULT | ||
assert DataCategory.parse("transaction") == DataCategory.TRANSACTION | ||
assert DataCategory.parse("") is None | ||
assert DataCategory.parse(None) is None | ||
assert DataCategory.parse("something completely different") is None | ||
|
||
|
||
def test_data_category_from_event_type(): | ||
assert DataCategory.from_event_type("transaction") == DataCategory.TRANSACTION | ||
# Special case! | ||
assert DataCategory.from_event_type("default") == DataCategory.ERROR | ||
# Anything unknown is coerced to "default", which is ERROR | ||
assert DataCategory.from_event_type("") == DataCategory.ERROR | ||
assert DataCategory.from_event_type(None) == DataCategory.ERROR | ||
|
||
|
||
def test_data_category_api_name(): | ||
assert DataCategory.ERROR.api_name() == "error" | ||
|
||
|
||
def test_span_mapping(): | ||
# This is a pure regression test to protect against accidental renames. | ||
assert SPAN_STATUS_CODE_TO_NAME == { | ||
0: "ok", | ||
1: "cancelled", | ||
2: "unknown", | ||
3: "invalid_argument", | ||
4: "deadline_exceeded", | ||
5: "not_found", | ||
6: "already_exists", | ||
7: "permission_denied", | ||
8: "resource_exhausted", | ||
9: "failed_precondition", | ||
10: "aborted", | ||
11: "out_of_range", | ||
12: "unimplemented", | ||
13: "internal_error", | ||
14: "unavailable", | ||
15: "data_loss", | ||
16: "unauthenticated", | ||
} |
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.
Note that this changes compared to Sentry, where
SECURITY
was not included:https://github.com/getsentry/sentry/blob/9f08305e09866c8bd6d0c24f5b0aabdd7dd6c59c/src/sentry/constants.py#L532-L533
This is mainly used by quotas, which will now include security events as originally intended.