sentry-relay 0.9.22__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sentry_relay-0.9.22/PKG-INFO +19 -0
- sentry_relay-0.9.22/README +0 -0
- sentry_relay-0.9.22/rustsrc.zip +0 -0
- sentry_relay-0.9.22/sentry_relay/__init__.py +23 -0
- sentry_relay-0.9.22/sentry_relay/auth.py +149 -0
- sentry_relay-0.9.22/sentry_relay/consts.py +142 -0
- sentry_relay-0.9.22/sentry_relay/exceptions.py +65 -0
- sentry_relay-0.9.22/sentry_relay/processing.py +381 -0
- sentry_relay-0.9.22/sentry_relay/py.typed +0 -0
- sentry_relay-0.9.22/sentry_relay/utils.py +113 -0
- sentry_relay-0.9.22/setup.py +126 -0
- sentry_relay-0.9.22/tests/test_auth.py +85 -0
- sentry_relay-0.9.22/tests/test_consts.py +51 -0
- sentry_relay-0.9.22/tests/test_processing.py +398 -0
- sentry_relay-0.9.22/tests/test_utils.py +10 -0
- sentry_relay-0.9.22/version.txt +1 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sentry-relay
|
|
3
|
+
Version: 0.9.22
|
|
4
|
+
Summary: A python library to access sentry relay functionality.
|
|
5
|
+
Author: Sentry
|
|
6
|
+
Author-email: hello@sentry.io
|
|
7
|
+
License: FSL-1.0-Apache-2.0
|
|
8
|
+
Platform: any
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: milksnake>=0.1.6
|
|
12
|
+
Dynamic: author
|
|
13
|
+
Dynamic: author-email
|
|
14
|
+
Dynamic: description-content-type
|
|
15
|
+
Dynamic: license
|
|
16
|
+
Dynamic: platform
|
|
17
|
+
Dynamic: requires-dist
|
|
18
|
+
Dynamic: requires-python
|
|
19
|
+
Dynamic: summary
|
|
File without changes
|
|
Binary file
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
__all__ = []
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _import_all():
|
|
5
|
+
import pkgutil
|
|
6
|
+
|
|
7
|
+
glob = globals()
|
|
8
|
+
for _, modname, _ in pkgutil.iter_modules(__path__):
|
|
9
|
+
if modname[:1] == "_":
|
|
10
|
+
continue
|
|
11
|
+
mod = __import__("sentry_relay.%s" % modname, glob, glob, ["__name__"])
|
|
12
|
+
if not hasattr(mod, "__all__"):
|
|
13
|
+
continue
|
|
14
|
+
__all__.extend(mod.__all__)
|
|
15
|
+
for name in mod.__all__:
|
|
16
|
+
obj = getattr(mod, name)
|
|
17
|
+
if hasattr(obj, "__module__"):
|
|
18
|
+
obj.__module__ = "sentry_relay"
|
|
19
|
+
glob[name] = obj
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
_import_all()
|
|
23
|
+
del _import_all
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import uuid
|
|
3
|
+
from typing import Callable, Any
|
|
4
|
+
|
|
5
|
+
from sentry_relay._lowlevel import lib
|
|
6
|
+
from sentry_relay.utils import (
|
|
7
|
+
RustObject,
|
|
8
|
+
encode_str,
|
|
9
|
+
decode_str,
|
|
10
|
+
decode_uuid,
|
|
11
|
+
rustcall,
|
|
12
|
+
make_buf,
|
|
13
|
+
)
|
|
14
|
+
from sentry_relay.exceptions import UnpackErrorBadSignature
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"PublicKey",
|
|
19
|
+
"SecretKey",
|
|
20
|
+
"generate_key_pair",
|
|
21
|
+
"create_register_challenge",
|
|
22
|
+
"validate_register_response",
|
|
23
|
+
"is_version_supported",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class PublicKey(RustObject):
|
|
28
|
+
__dealloc_func__ = lib.relay_publickey_free
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def parse(cls, string):
|
|
32
|
+
s = encode_str(string)
|
|
33
|
+
ptr = rustcall(lib.relay_publickey_parse, s)
|
|
34
|
+
return cls._from_objptr(ptr)
|
|
35
|
+
|
|
36
|
+
def verify(self, buf, sig, max_age=None):
|
|
37
|
+
buf = make_buf(buf)
|
|
38
|
+
sig = encode_str(sig)
|
|
39
|
+
if max_age is None:
|
|
40
|
+
return self._methodcall(lib.relay_publickey_verify, buf, sig)
|
|
41
|
+
return self._methodcall(lib.relay_publickey_verify_timestamp, buf, sig, max_age)
|
|
42
|
+
|
|
43
|
+
def unpack(
|
|
44
|
+
self,
|
|
45
|
+
buf,
|
|
46
|
+
sig,
|
|
47
|
+
max_age=None,
|
|
48
|
+
json_loads: Callable[[str | bytes], Any] = json.loads,
|
|
49
|
+
):
|
|
50
|
+
if not self.verify(buf, sig, max_age):
|
|
51
|
+
raise UnpackErrorBadSignature("invalid signature")
|
|
52
|
+
return json_loads(buf)
|
|
53
|
+
|
|
54
|
+
def __str__(self):
|
|
55
|
+
return decode_str(self._methodcall(lib.relay_publickey_to_string), free=True)
|
|
56
|
+
|
|
57
|
+
def __repr__(self):
|
|
58
|
+
return f"<{self.__class__.__name__} {str(self)!r}>"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class SecretKey(RustObject):
|
|
62
|
+
__dealloc_func__ = lib.relay_secretkey_free
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def parse(cls, string):
|
|
66
|
+
s = encode_str(string)
|
|
67
|
+
ptr = rustcall(lib.relay_secretkey_parse, s)
|
|
68
|
+
return cls._from_objptr(ptr)
|
|
69
|
+
|
|
70
|
+
def sign(self, value):
|
|
71
|
+
buf = make_buf(value)
|
|
72
|
+
return decode_str(self._methodcall(lib.relay_secretkey_sign, buf), free=True)
|
|
73
|
+
|
|
74
|
+
def pack(self, data):
|
|
75
|
+
# TODO(@anonrig): Look into separators requirement
|
|
76
|
+
packed = json.dumps(data, separators=(",", ":")).encode()
|
|
77
|
+
return packed, self.sign(packed)
|
|
78
|
+
|
|
79
|
+
def __str__(self):
|
|
80
|
+
return decode_str(self._methodcall(lib.relay_secretkey_to_string), free=True)
|
|
81
|
+
|
|
82
|
+
def __repr__(self):
|
|
83
|
+
return f"<{self.__class__.__name__} {str(self)!r}>"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def generate_key_pair():
|
|
87
|
+
rv = rustcall(lib.relay_generate_key_pair)
|
|
88
|
+
return (
|
|
89
|
+
SecretKey._from_objptr(rv.secret_key),
|
|
90
|
+
PublicKey._from_objptr(rv.public_key),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def generate_relay_id():
|
|
95
|
+
return decode_uuid(rustcall(lib.relay_generate_relay_id))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def create_register_challenge(
|
|
99
|
+
data,
|
|
100
|
+
signature,
|
|
101
|
+
secret,
|
|
102
|
+
max_age=60,
|
|
103
|
+
json_loads: Callable[[str | bytes], Any] = json.loads,
|
|
104
|
+
):
|
|
105
|
+
challenge_json = rustcall(
|
|
106
|
+
lib.relay_create_register_challenge,
|
|
107
|
+
make_buf(data),
|
|
108
|
+
encode_str(signature),
|
|
109
|
+
encode_str(secret),
|
|
110
|
+
max_age,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
challenge = json_loads(decode_str(challenge_json, free=True))
|
|
114
|
+
return {
|
|
115
|
+
"relay_id": uuid.UUID(challenge["relay_id"]),
|
|
116
|
+
"token": challenge["token"],
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def validate_register_response(
|
|
121
|
+
data,
|
|
122
|
+
signature,
|
|
123
|
+
secret,
|
|
124
|
+
max_age=60,
|
|
125
|
+
json_loads: Callable[[str | bytes], Any] = json.loads,
|
|
126
|
+
):
|
|
127
|
+
response_json = rustcall(
|
|
128
|
+
lib.relay_validate_register_response,
|
|
129
|
+
make_buf(data),
|
|
130
|
+
encode_str(signature),
|
|
131
|
+
encode_str(secret),
|
|
132
|
+
max_age,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
response = json_loads(decode_str(response_json, free=True))
|
|
136
|
+
return {
|
|
137
|
+
"relay_id": uuid.UUID(response["relay_id"]),
|
|
138
|
+
"token": response["token"],
|
|
139
|
+
"public_key": response["public_key"],
|
|
140
|
+
"version": response["version"],
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def is_version_supported(version):
|
|
145
|
+
"""
|
|
146
|
+
Checks if the provided Relay version is still compatible with this library. The version can be
|
|
147
|
+
``None``, in which case a legacy Relay is assumed.
|
|
148
|
+
"""
|
|
149
|
+
return rustcall(lib.relay_version_supported, encode_str(version or ""))
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from enum import IntEnum
|
|
3
|
+
|
|
4
|
+
from sentry_relay._lowlevel import lib
|
|
5
|
+
from sentry_relay.utils import decode_str, encode_str
|
|
6
|
+
|
|
7
|
+
__all__ = ["DataCategory", "SPAN_STATUS_CODE_TO_NAME", "SPAN_STATUS_NAME_TO_CODE"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DataCategory(IntEnum):
|
|
11
|
+
# See _check_generated below.
|
|
12
|
+
# begin generated
|
|
13
|
+
DEFAULT = 0
|
|
14
|
+
ERROR = 1
|
|
15
|
+
TRANSACTION = 2
|
|
16
|
+
SECURITY = 3
|
|
17
|
+
ATTACHMENT = 4
|
|
18
|
+
SESSION = 5
|
|
19
|
+
PROFILE = 6
|
|
20
|
+
REPLAY = 7
|
|
21
|
+
TRANSACTION_PROCESSED = 8
|
|
22
|
+
TRANSACTION_INDEXED = 9
|
|
23
|
+
MONITOR = 10
|
|
24
|
+
PROFILE_INDEXED = 11
|
|
25
|
+
SPAN = 12
|
|
26
|
+
MONITOR_SEAT = 13
|
|
27
|
+
USER_REPORT_V2 = 14
|
|
28
|
+
METRIC_BUCKET = 15
|
|
29
|
+
SPAN_INDEXED = 16
|
|
30
|
+
PROFILE_DURATION = 17
|
|
31
|
+
PROFILE_CHUNK = 18
|
|
32
|
+
METRIC_SECOND = 19
|
|
33
|
+
DO_NOT_USE_REPLAY_VIDEO = 20
|
|
34
|
+
UPTIME = 21
|
|
35
|
+
ATTACHMENT_ITEM = 22
|
|
36
|
+
LOG_ITEM = 23
|
|
37
|
+
LOG_BYTE = 24
|
|
38
|
+
PROFILE_DURATION_UI = 25
|
|
39
|
+
PROFILE_CHUNK_UI = 26
|
|
40
|
+
SEER_AUTOFIX = 27
|
|
41
|
+
SEER_SCANNER = 28
|
|
42
|
+
PREVENT_USER = 29
|
|
43
|
+
PREVENT_REVIEW = 30
|
|
44
|
+
SIZE_ANALYSIS = 31
|
|
45
|
+
INSTALLABLE_BUILD = 32
|
|
46
|
+
TRACE_METRIC = 33
|
|
47
|
+
SEER_USER = 34
|
|
48
|
+
UNKNOWN = -1
|
|
49
|
+
# end generated
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def parse(cls, name):
|
|
53
|
+
"""
|
|
54
|
+
Parses a `DataCategory` from its API name.
|
|
55
|
+
"""
|
|
56
|
+
category = DataCategory(lib.relay_data_category_parse(encode_str(name or "")))
|
|
57
|
+
if category == DataCategory.UNKNOWN:
|
|
58
|
+
return None # Unknown is a Rust-only value, replace with None
|
|
59
|
+
return category
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def from_event_type(cls, event_type):
|
|
63
|
+
"""
|
|
64
|
+
Parses a `DataCategory` from an event type.
|
|
65
|
+
"""
|
|
66
|
+
s = encode_str(event_type or "")
|
|
67
|
+
return DataCategory(lib.relay_data_category_from_event_type(s))
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def event_categories(cls):
|
|
71
|
+
"""
|
|
72
|
+
Returns categories that count as events, including transactions.
|
|
73
|
+
"""
|
|
74
|
+
return [
|
|
75
|
+
DataCategory.DEFAULT,
|
|
76
|
+
DataCategory.ERROR,
|
|
77
|
+
DataCategory.TRANSACTION,
|
|
78
|
+
DataCategory.SECURITY,
|
|
79
|
+
DataCategory.USER_REPORT_V2,
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def error_categories(cls):
|
|
84
|
+
"""
|
|
85
|
+
Returns categories that count as traditional error tracking events.
|
|
86
|
+
"""
|
|
87
|
+
return [DataCategory.DEFAULT, DataCategory.ERROR, DataCategory.SECURITY]
|
|
88
|
+
|
|
89
|
+
def api_name(self):
|
|
90
|
+
"""
|
|
91
|
+
Returns the API name of the given `DataCategory`.
|
|
92
|
+
"""
|
|
93
|
+
return decode_str(lib.relay_data_category_name(self.value), free=True)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _check_generated():
|
|
97
|
+
prefix = "RELAY_DATA_CATEGORY_"
|
|
98
|
+
|
|
99
|
+
attrs = {}
|
|
100
|
+
for attr in dir(lib):
|
|
101
|
+
if attr.startswith(prefix):
|
|
102
|
+
category_name = attr[len(prefix) :]
|
|
103
|
+
attrs[category_name] = getattr(lib, attr)
|
|
104
|
+
|
|
105
|
+
if attrs != DataCategory.__members__:
|
|
106
|
+
values = sorted(
|
|
107
|
+
attrs.items(), key=lambda kv: sys.maxsize if kv[1] == -1 else kv[1]
|
|
108
|
+
)
|
|
109
|
+
generated = "".join(f" {k} = {v}\n" for k, v in values)
|
|
110
|
+
raise AssertionError(
|
|
111
|
+
f"DataCategory enum does not match source!\n\n"
|
|
112
|
+
f"Paste this into `class DataCategory` in py/sentry_relay/consts.py:\n\n"
|
|
113
|
+
f"{generated}"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
_check_generated()
|
|
118
|
+
|
|
119
|
+
SPAN_STATUS_CODE_TO_NAME = {}
|
|
120
|
+
SPAN_STATUS_NAME_TO_CODE = {}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _make_span_statuses():
|
|
124
|
+
prefix = "RELAY_SPAN_STATUS_"
|
|
125
|
+
|
|
126
|
+
for attr in dir(lib):
|
|
127
|
+
if not attr.startswith(prefix):
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
status_name = attr[len(prefix) :].lower()
|
|
131
|
+
status_code = getattr(lib, attr)
|
|
132
|
+
|
|
133
|
+
SPAN_STATUS_CODE_TO_NAME[status_code] = status_name
|
|
134
|
+
SPAN_STATUS_NAME_TO_CODE[status_name] = status_code
|
|
135
|
+
|
|
136
|
+
# Legacy alias
|
|
137
|
+
SPAN_STATUS_NAME_TO_CODE["unknown_error"] = SPAN_STATUS_NAME_TO_CODE[
|
|
138
|
+
"internal_error"
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
_make_span_statuses()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
from sentry_relay._lowlevel import lib
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
__all__ = ["RelayError"]
|
|
7
|
+
exceptions_by_code = {}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RelayError(Exception):
|
|
11
|
+
code = None
|
|
12
|
+
|
|
13
|
+
def __init__(self, msg):
|
|
14
|
+
Exception.__init__(self)
|
|
15
|
+
self.message = msg
|
|
16
|
+
self.rust_info = None
|
|
17
|
+
|
|
18
|
+
def __str__(self):
|
|
19
|
+
rv = self.message
|
|
20
|
+
if self.rust_info is not None:
|
|
21
|
+
return f"{rv}\n\n{self.rust_info}"
|
|
22
|
+
return rv
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _make_error(error_name, base=RelayError, code=None):
|
|
26
|
+
class Exc(base):
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
Exc.__name__ = error_name
|
|
30
|
+
Exc.__qualname__ = error_name
|
|
31
|
+
if code is not None:
|
|
32
|
+
Exc.code = code
|
|
33
|
+
globals()[Exc.__name__] = Exc
|
|
34
|
+
__all__.append(Exc.__name__)
|
|
35
|
+
return Exc
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _get_error_base(error_name):
|
|
39
|
+
pieces = error_name.split("Error", 1)
|
|
40
|
+
if len(pieces) == 2 and pieces[0] and pieces[1]:
|
|
41
|
+
base_error_name = pieces[0] + "Error"
|
|
42
|
+
base_class = globals().get(base_error_name)
|
|
43
|
+
if base_class is None:
|
|
44
|
+
base_class = _make_error(base_error_name)
|
|
45
|
+
return base_class
|
|
46
|
+
return RelayError
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _make_exceptions():
|
|
50
|
+
prefix = "RELAY_ERROR_CODE_"
|
|
51
|
+
for attr in dir(lib):
|
|
52
|
+
if not attr.startswith(prefix):
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
error_name = attr[len(prefix) :].title().replace("_", "")
|
|
56
|
+
base = _get_error_base(error_name)
|
|
57
|
+
exc = _make_error(error_name, base=base, code=getattr(lib, attr))
|
|
58
|
+
exceptions_by_code[exc.code] = exc
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
_make_exceptions()
|
|
62
|
+
|
|
63
|
+
if TYPE_CHECKING:
|
|
64
|
+
# treat unknown attribute names as exception types
|
|
65
|
+
def __getattr__(name: str) -> type[RelayError]: ...
|