sentry-relay 0.9.9__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.9/PKG-INFO +10 -0
- sentry-relay-0.9.9/README +0 -0
- sentry-relay-0.9.9/rustsrc.zip +0 -0
- sentry-relay-0.9.9/sentry_relay/__init__.py +23 -0
- sentry-relay-0.9.9/sentry_relay/auth.py +149 -0
- sentry-relay-0.9.9/sentry_relay/consts.py +133 -0
- sentry-relay-0.9.9/sentry_relay/exceptions.py +65 -0
- sentry-relay-0.9.9/sentry_relay/processing.py +373 -0
- sentry-relay-0.9.9/sentry_relay/py.typed +0 -0
- sentry-relay-0.9.9/sentry_relay/utils.py +113 -0
- sentry-relay-0.9.9/setup.py +121 -0
- sentry-relay-0.9.9/version.txt +1 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: sentry-relay
|
|
3
|
+
Version: 0.9.9
|
|
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
|
|
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,133 @@
|
|
|
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
|
+
# begin generated
|
|
12
|
+
DEFAULT = 0
|
|
13
|
+
ERROR = 1
|
|
14
|
+
TRANSACTION = 2
|
|
15
|
+
SECURITY = 3
|
|
16
|
+
ATTACHMENT = 4
|
|
17
|
+
SESSION = 5
|
|
18
|
+
PROFILE = 6
|
|
19
|
+
REPLAY = 7
|
|
20
|
+
TRANSACTION_PROCESSED = 8
|
|
21
|
+
TRANSACTION_INDEXED = 9
|
|
22
|
+
MONITOR = 10
|
|
23
|
+
PROFILE_INDEXED = 11
|
|
24
|
+
SPAN = 12
|
|
25
|
+
MONITOR_SEAT = 13
|
|
26
|
+
USER_REPORT_V2 = 14
|
|
27
|
+
METRIC_BUCKET = 15
|
|
28
|
+
SPAN_INDEXED = 16
|
|
29
|
+
PROFILE_DURATION = 17
|
|
30
|
+
PROFILE_CHUNK = 18
|
|
31
|
+
METRIC_SECOND = 19
|
|
32
|
+
DO_NOT_USE_REPLAY_VIDEO = 20
|
|
33
|
+
UPTIME = 21
|
|
34
|
+
ATTACHMENT_ITEM = 22
|
|
35
|
+
LOG_ITEM = 23
|
|
36
|
+
LOG_BYTE = 24
|
|
37
|
+
PROFILE_DURATION_UI = 25
|
|
38
|
+
PROFILE_CHUNK_UI = 26
|
|
39
|
+
SEER_AUTOFIX = 27
|
|
40
|
+
SEER_SCANNER = 28
|
|
41
|
+
UNKNOWN = -1
|
|
42
|
+
# end generated
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def parse(cls, name):
|
|
46
|
+
"""
|
|
47
|
+
Parses a `DataCategory` from its API name.
|
|
48
|
+
"""
|
|
49
|
+
category = DataCategory(lib.relay_data_category_parse(encode_str(name or "")))
|
|
50
|
+
if category == DataCategory.UNKNOWN:
|
|
51
|
+
return None # Unknown is a Rust-only value, replace with None
|
|
52
|
+
return category
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def from_event_type(cls, event_type):
|
|
56
|
+
"""
|
|
57
|
+
Parses a `DataCategory` from an event type.
|
|
58
|
+
"""
|
|
59
|
+
s = encode_str(event_type or "")
|
|
60
|
+
return DataCategory(lib.relay_data_category_from_event_type(s))
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def event_categories(cls):
|
|
64
|
+
"""
|
|
65
|
+
Returns categories that count as events, including transactions.
|
|
66
|
+
"""
|
|
67
|
+
return [
|
|
68
|
+
DataCategory.DEFAULT,
|
|
69
|
+
DataCategory.ERROR,
|
|
70
|
+
DataCategory.TRANSACTION,
|
|
71
|
+
DataCategory.SECURITY,
|
|
72
|
+
DataCategory.USER_REPORT_V2,
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def error_categories(cls):
|
|
77
|
+
"""
|
|
78
|
+
Returns categories that count as traditional error tracking events.
|
|
79
|
+
"""
|
|
80
|
+
return [DataCategory.DEFAULT, DataCategory.ERROR, DataCategory.SECURITY]
|
|
81
|
+
|
|
82
|
+
def api_name(self):
|
|
83
|
+
"""
|
|
84
|
+
Returns the API name of the given `DataCategory`.
|
|
85
|
+
"""
|
|
86
|
+
return decode_str(lib.relay_data_category_name(self.value), free=True)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _check_generated():
|
|
90
|
+
prefix = "RELAY_DATA_CATEGORY_"
|
|
91
|
+
|
|
92
|
+
attrs = {}
|
|
93
|
+
for attr in dir(lib):
|
|
94
|
+
if attr.startswith(prefix):
|
|
95
|
+
category_name = attr[len(prefix) :]
|
|
96
|
+
attrs[category_name] = getattr(lib, attr)
|
|
97
|
+
|
|
98
|
+
if attrs != DataCategory.__members__:
|
|
99
|
+
values = sorted(
|
|
100
|
+
attrs.items(), key=lambda kv: sys.maxsize if kv[1] == -1 else kv[1]
|
|
101
|
+
)
|
|
102
|
+
generated = "".join(f" {k} = {v}\n" for k, v in values)
|
|
103
|
+
raise AssertionError(
|
|
104
|
+
f"DataCategory enum does not match source!\n\n"
|
|
105
|
+
f"Paste this into `class DataCategory` in py/sentry_relay/consts.py:\n\n"
|
|
106
|
+
f"{generated}"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
_check_generated()
|
|
111
|
+
|
|
112
|
+
SPAN_STATUS_CODE_TO_NAME = {}
|
|
113
|
+
SPAN_STATUS_NAME_TO_CODE = {}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _make_span_statuses():
|
|
117
|
+
prefix = "RELAY_SPAN_STATUS_"
|
|
118
|
+
|
|
119
|
+
for attr in dir(lib):
|
|
120
|
+
if not attr.startswith(prefix):
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
status_name = attr[len(prefix) :].lower()
|
|
124
|
+
status_code = getattr(lib, attr)
|
|
125
|
+
|
|
126
|
+
SPAN_STATUS_CODE_TO_NAME[status_code] = status_name
|
|
127
|
+
SPAN_STATUS_NAME_TO_CODE[status_name] = status_code
|
|
128
|
+
|
|
129
|
+
# Legacy alias
|
|
130
|
+
SPAN_STATUS_NAME_TO_CODE["unknown_error"] = SPAN_STATUS_NAME_TO_CODE["unknown"]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
_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]: ...
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Callable, Any
|
|
5
|
+
|
|
6
|
+
from sentry_relay._lowlevel import lib, ffi
|
|
7
|
+
from sentry_relay.utils import (
|
|
8
|
+
encode_str,
|
|
9
|
+
decode_str,
|
|
10
|
+
rustcall,
|
|
11
|
+
RustObject,
|
|
12
|
+
attached_refs,
|
|
13
|
+
make_buf,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"split_chunks",
|
|
18
|
+
"meta_with_chunks",
|
|
19
|
+
"StoreNormalizer",
|
|
20
|
+
"GeoIpLookup",
|
|
21
|
+
"is_glob_match",
|
|
22
|
+
"is_codeowners_path_match",
|
|
23
|
+
"parse_release",
|
|
24
|
+
"validate_pii_selector",
|
|
25
|
+
"validate_pii_config",
|
|
26
|
+
"convert_datascrubbing_config",
|
|
27
|
+
"pii_strip_event",
|
|
28
|
+
"pii_selector_suggestions_from_event",
|
|
29
|
+
"VALID_PLATFORMS",
|
|
30
|
+
"validate_rule_condition",
|
|
31
|
+
"validate_sampling_condition",
|
|
32
|
+
"validate_sampling_configuration",
|
|
33
|
+
"normalize_project_config",
|
|
34
|
+
"normalize_cardinality_limit_config",
|
|
35
|
+
"normalize_global_config",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _init_valid_platforms() -> frozenset[str]:
|
|
40
|
+
size_out = ffi.new("uintptr_t *")
|
|
41
|
+
strings = rustcall(lib.relay_valid_platforms, size_out)
|
|
42
|
+
|
|
43
|
+
valid_platforms = []
|
|
44
|
+
for i in range(int(size_out[0])):
|
|
45
|
+
valid_platforms.append(decode_str(strings[i], free=True))
|
|
46
|
+
|
|
47
|
+
return frozenset(valid_platforms)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
VALID_PLATFORMS = _init_valid_platforms()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def split_chunks(
|
|
54
|
+
string,
|
|
55
|
+
remarks,
|
|
56
|
+
json_dumps: Callable[[Any], Any] = json.dumps,
|
|
57
|
+
json_loads: Callable[[str | bytes], Any] = json.loads,
|
|
58
|
+
):
|
|
59
|
+
json_chunks = rustcall(
|
|
60
|
+
lib.relay_split_chunks,
|
|
61
|
+
encode_str(string),
|
|
62
|
+
encode_str(json_dumps(remarks)),
|
|
63
|
+
)
|
|
64
|
+
return json_loads(decode_str(json_chunks, free=True))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def meta_with_chunks(data, meta):
|
|
68
|
+
if not isinstance(meta, dict):
|
|
69
|
+
return meta
|
|
70
|
+
|
|
71
|
+
result = {}
|
|
72
|
+
for key, item in meta.items():
|
|
73
|
+
if key == "" and isinstance(item, dict):
|
|
74
|
+
result[""] = item.copy()
|
|
75
|
+
if item.get("rem") and isinstance(data, str):
|
|
76
|
+
result[""]["chunks"] = split_chunks(data, item["rem"])
|
|
77
|
+
elif isinstance(data, dict):
|
|
78
|
+
result[key] = meta_with_chunks(data.get(key), item)
|
|
79
|
+
elif isinstance(data, list):
|
|
80
|
+
int_key = int(key)
|
|
81
|
+
val = data[int_key] if int_key < len(data) else None
|
|
82
|
+
result[key] = meta_with_chunks(val, item)
|
|
83
|
+
else:
|
|
84
|
+
result[key] = item
|
|
85
|
+
|
|
86
|
+
return result
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class GeoIpLookup(RustObject):
|
|
90
|
+
__dealloc_func__ = lib.relay_geoip_lookup_free
|
|
91
|
+
__slots__ = ("_path",)
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def from_path(cls, path):
|
|
95
|
+
if isinstance(path, str):
|
|
96
|
+
path = path.encode("utf-8")
|
|
97
|
+
rv = cls._from_objptr(rustcall(lib.relay_geoip_lookup_new, path))
|
|
98
|
+
rv._path = path
|
|
99
|
+
return rv
|
|
100
|
+
|
|
101
|
+
def __repr__(self):
|
|
102
|
+
return f"<GeoIpLookup {self._path!r}>"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class StoreNormalizer(RustObject):
|
|
106
|
+
__dealloc_func__ = lib.relay_store_normalizer_free
|
|
107
|
+
__init__ = object.__init__
|
|
108
|
+
__slots__ = ("__weakref__",)
|
|
109
|
+
|
|
110
|
+
def __new__(
|
|
111
|
+
cls, geoip_lookup=None, json_dumps: Callable[[Any], Any] = json.dumps, **config
|
|
112
|
+
):
|
|
113
|
+
config = json_dumps(config)
|
|
114
|
+
geoptr = geoip_lookup._get_objptr() if geoip_lookup is not None else ffi.NULL
|
|
115
|
+
rv = cls._from_objptr(
|
|
116
|
+
rustcall(lib.relay_store_normalizer_new, encode_str(config), geoptr)
|
|
117
|
+
)
|
|
118
|
+
if geoip_lookup is not None:
|
|
119
|
+
attached_refs[rv] = geoip_lookup
|
|
120
|
+
return rv
|
|
121
|
+
|
|
122
|
+
def normalize_event(
|
|
123
|
+
self,
|
|
124
|
+
event=None,
|
|
125
|
+
raw_event=None,
|
|
126
|
+
json_loads: Callable[[str | bytes], Any] = json.loads,
|
|
127
|
+
):
|
|
128
|
+
if raw_event is None:
|
|
129
|
+
raw_event = _serialize_event(event)
|
|
130
|
+
|
|
131
|
+
event = _encode_raw_event(raw_event)
|
|
132
|
+
rv = self._methodcall(lib.relay_store_normalizer_normalize_event, event)
|
|
133
|
+
return json_loads(decode_str(rv, free=True))
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _serialize_event(event):
|
|
137
|
+
# TODO(@anonrig): Look into ensure_ascii requirement
|
|
138
|
+
raw_event = json.dumps(event, ensure_ascii=False)
|
|
139
|
+
if isinstance(raw_event, str):
|
|
140
|
+
raw_event = raw_event.encode("utf-8", errors="replace")
|
|
141
|
+
return raw_event
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _encode_raw_event(raw_event):
|
|
145
|
+
event = encode_str(raw_event, mutable=True)
|
|
146
|
+
rustcall(lib.relay_translate_legacy_python_json, event)
|
|
147
|
+
return event
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def is_glob_match(
|
|
151
|
+
value,
|
|
152
|
+
pat,
|
|
153
|
+
double_star=False,
|
|
154
|
+
case_insensitive=False,
|
|
155
|
+
path_normalize=False,
|
|
156
|
+
allow_newline=False,
|
|
157
|
+
):
|
|
158
|
+
flags = 0
|
|
159
|
+
if double_star:
|
|
160
|
+
flags |= lib.GLOB_FLAGS_DOUBLE_STAR
|
|
161
|
+
if case_insensitive:
|
|
162
|
+
flags |= lib.GLOB_FLAGS_CASE_INSENSITIVE
|
|
163
|
+
# Since on the C side we're only working with bytes we need to lowercase the pattern
|
|
164
|
+
# and value here. This works with both bytes and unicode strings.
|
|
165
|
+
value = value.lower()
|
|
166
|
+
pat = pat.lower()
|
|
167
|
+
if path_normalize:
|
|
168
|
+
flags |= lib.GLOB_FLAGS_PATH_NORMALIZE
|
|
169
|
+
if allow_newline:
|
|
170
|
+
flags |= lib.GLOB_FLAGS_ALLOW_NEWLINE
|
|
171
|
+
|
|
172
|
+
if isinstance(value, str):
|
|
173
|
+
value = value.encode("utf-8")
|
|
174
|
+
return rustcall(lib.relay_is_glob_match, make_buf(value), encode_str(pat), flags)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def is_codeowners_path_match(value, pattern):
|
|
178
|
+
if isinstance(value, str):
|
|
179
|
+
value = value.encode("utf-8")
|
|
180
|
+
return rustcall(
|
|
181
|
+
lib.relay_is_codeowners_path_match, make_buf(value), encode_str(pattern)
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def validate_pii_selector(selector):
|
|
186
|
+
"""
|
|
187
|
+
Validate a PII selector spec. Used to validate datascrubbing safe fields.
|
|
188
|
+
"""
|
|
189
|
+
assert isinstance(selector, str)
|
|
190
|
+
raw_error = rustcall(lib.relay_validate_pii_selector, encode_str(selector))
|
|
191
|
+
error = decode_str(raw_error, free=True)
|
|
192
|
+
if error:
|
|
193
|
+
raise ValueError(error)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def validate_pii_config(config):
|
|
197
|
+
"""
|
|
198
|
+
Validate a PII config against the schema. Used in project options UI.
|
|
199
|
+
|
|
200
|
+
The parameter is a JSON-encoded string. We should pass the config through
|
|
201
|
+
as a string such that line numbers from the error message match with what
|
|
202
|
+
the user typed in.
|
|
203
|
+
"""
|
|
204
|
+
assert isinstance(config, str)
|
|
205
|
+
raw_error = rustcall(lib.relay_validate_pii_config, encode_str(config))
|
|
206
|
+
error = decode_str(raw_error, free=True)
|
|
207
|
+
if error:
|
|
208
|
+
raise ValueError(error)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def convert_datascrubbing_config(
|
|
212
|
+
config,
|
|
213
|
+
json_dumps: Callable[[Any], Any] = json.dumps,
|
|
214
|
+
json_loads: Callable[[str | bytes], Any] = json.loads,
|
|
215
|
+
):
|
|
216
|
+
"""
|
|
217
|
+
Convert an old datascrubbing config to the new PII config format.
|
|
218
|
+
"""
|
|
219
|
+
raw_config = encode_str(json_dumps(config))
|
|
220
|
+
raw_rv = rustcall(lib.relay_convert_datascrubbing_config, raw_config)
|
|
221
|
+
return json_loads(decode_str(raw_rv, free=True))
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def pii_strip_event(
|
|
225
|
+
config,
|
|
226
|
+
event,
|
|
227
|
+
json_dumps: Callable[[Any], Any] = json.dumps,
|
|
228
|
+
json_loads: Callable[[str | bytes], Any] = json.loads,
|
|
229
|
+
):
|
|
230
|
+
"""
|
|
231
|
+
Scrub an event using new PII stripping config.
|
|
232
|
+
"""
|
|
233
|
+
raw_config = encode_str(json_dumps(config))
|
|
234
|
+
raw_event = encode_str(json_dumps(event))
|
|
235
|
+
raw_rv = rustcall(lib.relay_pii_strip_event, raw_config, raw_event)
|
|
236
|
+
return json_loads(decode_str(raw_rv, free=True))
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def pii_selector_suggestions_from_event(
|
|
240
|
+
event,
|
|
241
|
+
json_dumps: Callable[[Any], Any] = json.dumps,
|
|
242
|
+
json_loads: Callable[[str | bytes], Any] = json.loads,
|
|
243
|
+
):
|
|
244
|
+
"""
|
|
245
|
+
Walk through the event and collect selectors that can be applied to it in a
|
|
246
|
+
PII config. This function is used in the UI to provide auto-completion of
|
|
247
|
+
selectors.
|
|
248
|
+
"""
|
|
249
|
+
raw_event = encode_str(json_dumps(event))
|
|
250
|
+
raw_rv = rustcall(lib.relay_pii_selector_suggestions_from_event, raw_event)
|
|
251
|
+
return json_loads(decode_str(raw_rv, free=True))
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def parse_release(release, json_loads: Callable[[str | bytes], Any] = json.loads):
|
|
255
|
+
"""Parses a release string into a dictionary of its components."""
|
|
256
|
+
return json_loads(
|
|
257
|
+
decode_str(rustcall(lib.relay_parse_release, encode_str(release)), free=True)
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def compare_version(a, b):
|
|
262
|
+
"""Compares two versions with each other and returns 1/0/-1."""
|
|
263
|
+
return rustcall(lib.relay_compare_versions, encode_str(a), encode_str(b))
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def validate_sampling_condition(condition):
|
|
267
|
+
"""
|
|
268
|
+
Deprecated legacy alias. Please use ``validate_rule_condition`` instead.
|
|
269
|
+
"""
|
|
270
|
+
return validate_rule_condition(condition)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def validate_rule_condition(condition):
|
|
274
|
+
"""
|
|
275
|
+
Validate a dynamic rule condition. Used by dynamic sampling, metric extraction, and metric
|
|
276
|
+
tagging.
|
|
277
|
+
|
|
278
|
+
:param condition: A string containing the condition encoded as JSON.
|
|
279
|
+
"""
|
|
280
|
+
assert isinstance(condition, str)
|
|
281
|
+
raw_error = rustcall(lib.relay_validate_rule_condition, encode_str(condition))
|
|
282
|
+
error = decode_str(raw_error, free=True)
|
|
283
|
+
if error:
|
|
284
|
+
raise ValueError(error)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def validate_sampling_configuration(condition):
|
|
288
|
+
"""
|
|
289
|
+
Validate the whole sampling configuration. Used in dynamic sampling serializer.
|
|
290
|
+
The parameter is a string containing the rules configuration as JSON.
|
|
291
|
+
"""
|
|
292
|
+
assert isinstance(condition, str)
|
|
293
|
+
raw_error = rustcall(
|
|
294
|
+
lib.relay_validate_sampling_configuration, encode_str(condition)
|
|
295
|
+
)
|
|
296
|
+
error = decode_str(raw_error, free=True)
|
|
297
|
+
if error:
|
|
298
|
+
raise ValueError(error)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def normalize_project_config(
|
|
302
|
+
config,
|
|
303
|
+
json_dumps: Callable[[Any], Any] = json.dumps,
|
|
304
|
+
json_loads: Callable[[str | bytes], Any] = json.loads,
|
|
305
|
+
):
|
|
306
|
+
"""Normalize a project config.
|
|
307
|
+
|
|
308
|
+
:param config: the project config to validate.
|
|
309
|
+
:param json_dumps: a function that stringifies python objects
|
|
310
|
+
:param json_loads: a function that parses and converts JSON strings
|
|
311
|
+
"""
|
|
312
|
+
serialized = json_dumps(config)
|
|
313
|
+
normalized = rustcall(lib.relay_normalize_project_config, encode_str(serialized))
|
|
314
|
+
rv = decode_str(normalized, free=True)
|
|
315
|
+
try:
|
|
316
|
+
return json_loads(rv)
|
|
317
|
+
except Exception:
|
|
318
|
+
# Catch all errors since json.loads implementation can change.
|
|
319
|
+
raise ValueError(rv)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def normalize_cardinality_limit_config(
|
|
323
|
+
config,
|
|
324
|
+
json_dumps: Callable[[Any], Any] = json.dumps,
|
|
325
|
+
json_loads: Callable[[str | bytes], Any] = json.loads,
|
|
326
|
+
):
|
|
327
|
+
"""Normalize the cardinality limit config.
|
|
328
|
+
|
|
329
|
+
Normalization consists of deserializing and serializing back the given
|
|
330
|
+
cardinality limit config. If deserializing fails, throw an exception. Note that even if
|
|
331
|
+
the roundtrip doesn't produce errors, the given config may differ from
|
|
332
|
+
normalized one.
|
|
333
|
+
|
|
334
|
+
:param config: the cardinality limit config to validate.
|
|
335
|
+
:param json_dumps: a function that stringifies python objects
|
|
336
|
+
:param json_loads: a function that parses and converts JSON strings
|
|
337
|
+
"""
|
|
338
|
+
serialized = json_dumps(config)
|
|
339
|
+
normalized = rustcall(
|
|
340
|
+
lib.normalize_cardinality_limit_config, encode_str(serialized)
|
|
341
|
+
)
|
|
342
|
+
rv = decode_str(normalized, free=True)
|
|
343
|
+
try:
|
|
344
|
+
return json_loads(rv)
|
|
345
|
+
except Exception:
|
|
346
|
+
# Catch all errors since json.loads implementation can change.
|
|
347
|
+
raise ValueError(rv)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def normalize_global_config(
|
|
351
|
+
config,
|
|
352
|
+
json_dumps: Callable[[Any], Any] = json.dumps,
|
|
353
|
+
json_loads: Callable[[str | bytes], Any] = json.loads,
|
|
354
|
+
):
|
|
355
|
+
"""Normalize the global config.
|
|
356
|
+
|
|
357
|
+
Normalization consists of deserializing and serializing back the given
|
|
358
|
+
global config. If deserializing fails, throw an exception. Note that even if
|
|
359
|
+
the roundtrip doesn't produce errors, the given config may differ from
|
|
360
|
+
normalized one.
|
|
361
|
+
|
|
362
|
+
:param config: the global config to validate.
|
|
363
|
+
:param json_dumps: a function that stringifies python objects
|
|
364
|
+
:param json_loads: a function that parses and converts JSON strings
|
|
365
|
+
"""
|
|
366
|
+
serialized = json_dumps(config)
|
|
367
|
+
normalized = rustcall(lib.relay_normalize_global_config, encode_str(serialized))
|
|
368
|
+
rv = decode_str(normalized, free=True)
|
|
369
|
+
try:
|
|
370
|
+
return json_loads(rv)
|
|
371
|
+
except Exception:
|
|
372
|
+
# Catch all errors since json.loads implementation can change.
|
|
373
|
+
raise ValueError(rv)
|
|
File without changes
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import uuid
|
|
5
|
+
import weakref
|
|
6
|
+
from sentry_relay._lowlevel import ffi, lib
|
|
7
|
+
from sentry_relay.exceptions import exceptions_by_code, RelayError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
attached_refs: weakref.WeakKeyDictionary[object, bytes]
|
|
11
|
+
attached_refs = weakref.WeakKeyDictionary()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
lib.relay_init()
|
|
15
|
+
os.environ["RUST_BACKTRACE"] = "1"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class _NoDict(type):
|
|
19
|
+
def __new__(cls, name, bases, d):
|
|
20
|
+
d.setdefault("__slots__", ())
|
|
21
|
+
return type.__new__(cls, name, bases, d)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def rustcall(func, *args):
|
|
25
|
+
"""Calls rust method and does some error handling."""
|
|
26
|
+
lib.relay_err_clear()
|
|
27
|
+
rv = func(*args)
|
|
28
|
+
err = lib.relay_err_get_last_code()
|
|
29
|
+
if not err:
|
|
30
|
+
return rv
|
|
31
|
+
msg = lib.relay_err_get_last_message()
|
|
32
|
+
cls = exceptions_by_code.get(err, RelayError)
|
|
33
|
+
exc = cls(decode_str(msg, free=True))
|
|
34
|
+
backtrace = decode_str(lib.relay_err_get_backtrace(), free=True)
|
|
35
|
+
if backtrace:
|
|
36
|
+
exc.rust_info = backtrace
|
|
37
|
+
raise exc
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class RustObject(metaclass=_NoDict):
|
|
41
|
+
__slots__ = ["_objptr", "_shared"]
|
|
42
|
+
__dealloc_func__ = None
|
|
43
|
+
|
|
44
|
+
def __init__(self):
|
|
45
|
+
raise TypeError("Cannot instanciate %r objects" % self.__class__.__name__)
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def _from_objptr(cls, ptr, shared=False):
|
|
49
|
+
rv = object.__new__(cls)
|
|
50
|
+
rv._objptr = ptr
|
|
51
|
+
rv._shared = shared
|
|
52
|
+
return rv
|
|
53
|
+
|
|
54
|
+
def _methodcall(self, func, *args):
|
|
55
|
+
return rustcall(func, self._get_objptr(), *args)
|
|
56
|
+
|
|
57
|
+
def _get_objptr(self):
|
|
58
|
+
if not self._objptr:
|
|
59
|
+
raise RuntimeError("Object is closed")
|
|
60
|
+
return self._objptr
|
|
61
|
+
|
|
62
|
+
def __del__(self):
|
|
63
|
+
if rustcall is None:
|
|
64
|
+
# Interpreter is shutting down and our memory management utils are
|
|
65
|
+
# gone. Just give up, the process is going away anyway.
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
if self._objptr is None or self._shared:
|
|
69
|
+
return
|
|
70
|
+
f = self.__class__.__dealloc_func__
|
|
71
|
+
if f is not None:
|
|
72
|
+
rustcall(f, self._objptr)
|
|
73
|
+
self._objptr = None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def decode_str(s, free=False):
|
|
77
|
+
"""Decodes a RelayStr"""
|
|
78
|
+
try:
|
|
79
|
+
if s.len == 0:
|
|
80
|
+
return ""
|
|
81
|
+
return ffi.unpack(s.data, s.len).decode("utf-8", "replace")
|
|
82
|
+
finally:
|
|
83
|
+
if free and s.owned:
|
|
84
|
+
lib.relay_str_free(ffi.addressof(s))
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def encode_str(s, mutable=False):
|
|
88
|
+
"""Encodes a RelayStr"""
|
|
89
|
+
rv = ffi.new("RelayStr *")
|
|
90
|
+
if isinstance(s, str):
|
|
91
|
+
s = s.encode("utf-8")
|
|
92
|
+
if mutable:
|
|
93
|
+
s = bytearray(s)
|
|
94
|
+
rv.data = ffi.from_buffer(s)
|
|
95
|
+
rv.len = len(s)
|
|
96
|
+
# we have to hold a weak reference here to ensure our string does not
|
|
97
|
+
# get collected before the string is used.
|
|
98
|
+
attached_refs[rv] = s
|
|
99
|
+
return rv
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def make_buf(value):
|
|
103
|
+
buf = memoryview(bytes(value))
|
|
104
|
+
rv = ffi.new("RelayBuf *")
|
|
105
|
+
rv.data = ffi.from_buffer(buf)
|
|
106
|
+
rv.len = len(buf)
|
|
107
|
+
attached_refs[rv] = buf
|
|
108
|
+
return rv
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def decode_uuid(value):
|
|
112
|
+
"""Decodes the given uuid value."""
|
|
113
|
+
return uuid.UUID(bytes=bytes(bytearray(ffi.unpack(value.data, 16))))
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import sys
|
|
4
|
+
import atexit
|
|
5
|
+
import shutil
|
|
6
|
+
import zipfile
|
|
7
|
+
import tempfile
|
|
8
|
+
import subprocess
|
|
9
|
+
from setuptools import setup, find_packages
|
|
10
|
+
from distutils.command.sdist import sdist
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_version_re = re.compile(r'(?m)^version\s*=\s*"(.*?)"\s*$')
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
DEBUG_BUILD = os.environ.get("RELAY_DEBUG") == "1"
|
|
17
|
+
|
|
18
|
+
with open("README", encoding="UTF-8") as f:
|
|
19
|
+
readme = f.read()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
if os.path.isfile("../relay-cabi/Cargo.toml"):
|
|
23
|
+
with open("../relay-cabi/Cargo.toml") as f:
|
|
24
|
+
match = _version_re.search(f.read())
|
|
25
|
+
assert match is not None
|
|
26
|
+
version = match[1]
|
|
27
|
+
else:
|
|
28
|
+
with open("version.txt") as f:
|
|
29
|
+
version = f.readline().strip()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def vendor_rust_deps():
|
|
33
|
+
subprocess.Popen(["scripts/git-archive-all", "py/rustsrc.zip"], cwd="..").wait()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def write_version():
|
|
37
|
+
with open("version.txt", "wb") as f:
|
|
38
|
+
f.write(("%s\n" % version).encode())
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class CustomSDist(sdist):
|
|
42
|
+
def run(self):
|
|
43
|
+
vendor_rust_deps()
|
|
44
|
+
write_version()
|
|
45
|
+
sdist.run(self)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def build_native(spec):
|
|
49
|
+
cmd = ["cargo", "build", "-p", "relay-cabi"]
|
|
50
|
+
if not DEBUG_BUILD:
|
|
51
|
+
cmd.extend(("--profile", "release-cabi"))
|
|
52
|
+
target = "release-cabi"
|
|
53
|
+
else:
|
|
54
|
+
target = "debug"
|
|
55
|
+
|
|
56
|
+
# Step 0: find rust sources
|
|
57
|
+
if not os.path.isfile("../relay-cabi/Cargo.toml"):
|
|
58
|
+
scratchpad = tempfile.mkdtemp()
|
|
59
|
+
|
|
60
|
+
@atexit.register
|
|
61
|
+
def delete_scratchpad():
|
|
62
|
+
try:
|
|
63
|
+
shutil.rmtree(scratchpad)
|
|
64
|
+
except OSError:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
zf = zipfile.ZipFile("rustsrc.zip")
|
|
68
|
+
zf.extractall(scratchpad)
|
|
69
|
+
rust_path = scratchpad + "/rustsrc"
|
|
70
|
+
else:
|
|
71
|
+
rust_path = ".."
|
|
72
|
+
scratchpad = None
|
|
73
|
+
|
|
74
|
+
# if the lib already built we replace the command
|
|
75
|
+
if os.environ.get("SKIP_RELAY_LIB_BUILD") is not None:
|
|
76
|
+
cmd = ["echo", "'Use pre-built library.'"]
|
|
77
|
+
|
|
78
|
+
# Step 1: build the rust library
|
|
79
|
+
build = spec.add_external_build(cmd=cmd, path=rust_path)
|
|
80
|
+
|
|
81
|
+
def find_dylib():
|
|
82
|
+
cargo_target = os.environ.get("CARGO_BUILD_TARGET")
|
|
83
|
+
if cargo_target:
|
|
84
|
+
in_path = f"target/{cargo_target}/{target}"
|
|
85
|
+
else:
|
|
86
|
+
in_path = "target/%s" % target
|
|
87
|
+
return build.find_dylib("relay_cabi", in_path=in_path)
|
|
88
|
+
|
|
89
|
+
rtld_flags = ["NOW"]
|
|
90
|
+
if sys.platform == "darwin":
|
|
91
|
+
rtld_flags.append("NODELETE")
|
|
92
|
+
spec.add_cffi_module(
|
|
93
|
+
module_path="sentry_relay._lowlevel",
|
|
94
|
+
dylib=find_dylib,
|
|
95
|
+
header_filename=lambda: build.find_header(
|
|
96
|
+
"relay.h", in_path="relay-cabi/include"
|
|
97
|
+
),
|
|
98
|
+
rtld_flags=rtld_flags,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
setup(
|
|
103
|
+
name="sentry-relay",
|
|
104
|
+
version=version,
|
|
105
|
+
packages=find_packages(),
|
|
106
|
+
author="Sentry",
|
|
107
|
+
license="FSL-1.0-Apache-2.0",
|
|
108
|
+
author_email="hello@sentry.io",
|
|
109
|
+
description="A python library to access sentry relay functionality.",
|
|
110
|
+
long_description=readme,
|
|
111
|
+
long_description_content_type="text/markdown",
|
|
112
|
+
include_package_data=True,
|
|
113
|
+
package_data={"sentry_relay": ["py.typed", "_lowlevel.pyi"]},
|
|
114
|
+
zip_safe=False,
|
|
115
|
+
platforms="any",
|
|
116
|
+
python_requires=">=3.10",
|
|
117
|
+
install_requires=["milksnake>=0.1.6"],
|
|
118
|
+
setup_requires=["milksnake>=0.1.6"],
|
|
119
|
+
milksnake_tasks=[build_native],
|
|
120
|
+
cmdclass={"sdist": CustomSDist}, # type: ignore
|
|
121
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.9.9
|