raidiantai-analytics 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,42 @@
1
+ """Send a tool's usage to Raidiant AI from a Python server, with a tool key.
2
+
3
+ Standard library only. Python 3.10 or later.
4
+ """
5
+
6
+ from ._client import (
7
+ ActorHandle,
8
+ Analytics,
9
+ CaptureDropped,
10
+ CaptureInvalid,
11
+ CaptureQueued,
12
+ CaptureResult,
13
+ CheckKeyFailed,
14
+ CheckKeyOk,
15
+ CheckKeyResult,
16
+ DrainResult,
17
+ Stats,
18
+ )
19
+ from ._diagnostics import Diagnostic
20
+ from ._identity import Actor, IdentityOk, IdentityRefused, email_identity
21
+ from ._version import PACKAGE_NAME, __version__
22
+
23
+ __all__ = [
24
+ "PACKAGE_NAME",
25
+ "Actor",
26
+ "ActorHandle",
27
+ "Analytics",
28
+ "CaptureDropped",
29
+ "CaptureInvalid",
30
+ "CaptureQueued",
31
+ "CaptureResult",
32
+ "CheckKeyFailed",
33
+ "CheckKeyOk",
34
+ "CheckKeyResult",
35
+ "Diagnostic",
36
+ "DrainResult",
37
+ "IdentityOk",
38
+ "IdentityRefused",
39
+ "Stats",
40
+ "__version__",
41
+ "email_identity",
42
+ ]
@@ -0,0 +1,218 @@
1
+ """Host admission: may this host value become wire text at all?
2
+
3
+ One iterative traversal that copies the host's value into a snapshot (each
4
+ member read once, through the base type's own methods, so no method a host
5
+ subclass overrides runs), admitting exactly None, bool, an int (not bool) with
6
+ |n| <= 2^53, a finite float, a str with no high-then-low surrogate pair, a
7
+ list or tuple, and a dict whose keys are all str. int, float and str
8
+ subclasses are admitted and copied as their base type. Everything else is
9
+ refused as `invalid_event` at its path. Two representability limits apply
10
+ during the same walk: nesting depth (the event is depth 1) and a running size
11
+ count L, a strict lower bound on the canonical text's UTF-8 bytes, refused as
12
+ `event_too_large` at "" once it passes the ceiling.
13
+
14
+ L accounting, in traversal order (pre-order, an object's key visited just
15
+ before its member, members in ECMAScript key order): every value adds 1, plus
16
+ its code points if it is a str; a container adds its member count when it is
17
+ entered, before any of its members are read; every key adds its code points.
18
+ Inside one value the checks run: size, then type, then depth, then the member
19
+ count. The snapshot holds only exact types, its dicts already in ECMAScript
20
+ key order.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import math
26
+ from dataclasses import dataclass
27
+ from typing import Any
28
+
29
+ from . import _text
30
+ from ._contract import CONTRACT
31
+
32
+ ADMISSION_SIZE_CEILING: int = CONTRACT["sdk_limits"]["ADMISSION_SIZE_CEILING"]
33
+ ADMISSION_DEPTH_CEILING: int = CONTRACT["sdk_limits"]["ADMISSION_DEPTH_CEILING"]
34
+ _MAX_SAFE = 2**53
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class AdmissionRefusal:
39
+ reason: str # "invalid_event" | "event_too_large"
40
+ path: str
41
+
42
+
43
+ @dataclass(frozen=True)
44
+ class Admitted:
45
+ value: Any
46
+ count: int
47
+
48
+
49
+ @dataclass(frozen=True)
50
+ class Refused:
51
+ refusal: AdmissionRefusal
52
+ count: int
53
+
54
+
55
+ class _Probe:
56
+ """The most recent traversal's final count, for tests that measure where the walk stopped."""
57
+
58
+ last_count = 0
59
+ strings_scanned = 0
60
+
61
+
62
+ admission_probe = _Probe()
63
+
64
+ # Work items. ("value", value, path, depth, setter); ("key", key); setter writes the copy.
65
+ _VALUE = 0
66
+ _KEY = 1
67
+
68
+
69
+ def admit(root: Any) -> Admitted | Refused:
70
+ """Admit and snapshot `root` (the whole event). Never recurses and never calls host code."""
71
+ count = 0
72
+ ceiling = ADMISSION_SIZE_CEILING
73
+ holder: list[Any] = [None]
74
+
75
+ def set_root(x: Any) -> None:
76
+ holder[0] = x
77
+
78
+ stack: list[tuple[Any, ...]] = [(_VALUE, root, "", 1, set_root)]
79
+
80
+ def too_large() -> Refused:
81
+ admission_probe.last_count = count
82
+ return Refused(AdmissionRefusal("event_too_large", ""), count)
83
+
84
+ def invalid(path: str) -> Refused:
85
+ admission_probe.last_count = count
86
+ return Refused(AdmissionRefusal("invalid_event", path), count)
87
+
88
+ while stack:
89
+ item = stack.pop()
90
+ if item[0] == _KEY:
91
+ # A key that is not a str, or a str holding a surrogate pair, is the
92
+ # holding object's fault: its path is the holder's.
93
+ _, k, holder_path = item
94
+ if k is _NOT_A_STR:
95
+ return invalid(holder_path)
96
+ n = len(k)
97
+ if n > ceiling - count:
98
+ count = ceiling + 1
99
+ return too_large()
100
+ count += n
101
+ admission_probe.strings_scanned += 1
102
+ if _text.has_surrogate_pair(k):
103
+ return invalid(holder_path)
104
+ continue
105
+
106
+ _, v, path, depth, setter = item
107
+ t = type(v)
108
+
109
+ # 1. Size.
110
+ count += 1
111
+ is_str = issubclass(t, str)
112
+ if is_str:
113
+ n = str.__len__(v)
114
+ if n > ceiling - count:
115
+ # Counted only up to one past the ceiling: the verdict is the same.
116
+ count = ceiling + 1
117
+ return too_large()
118
+ count += n
119
+ if count > ceiling:
120
+ return too_large()
121
+
122
+ # 2. Type.
123
+ if v is None or t is bool:
124
+ setter(v)
125
+ continue
126
+ if is_str:
127
+ s = v if t is str else str.__str__(v)
128
+ admission_probe.strings_scanned += 1
129
+ if _text.has_surrogate_pair(s):
130
+ return invalid(path)
131
+ setter(s)
132
+ continue
133
+ if issubclass(t, int):
134
+ i = v if t is int else int.__int__(v)
135
+ if i > _MAX_SAFE or i < -_MAX_SAFE:
136
+ return invalid(path)
137
+ setter(i)
138
+ continue
139
+ if issubclass(t, float):
140
+ f = v if t is float else float.__float__(v)
141
+ if not math.isfinite(f):
142
+ return invalid(path)
143
+ setter(f)
144
+ continue
145
+
146
+ is_list = issubclass(t, list)
147
+ if is_list or issubclass(t, tuple):
148
+ # 3. Depth.
149
+ if depth > ADMISSION_DEPTH_CEILING:
150
+ return invalid(path)
151
+ # 4. Member count, on entry.
152
+ length = list.__len__(v) if is_list else tuple.__len__(v)
153
+ count += length
154
+ if count > ceiling:
155
+ return too_large()
156
+ members = list.copy(v) if is_list else list(tuple.__iter__(v))
157
+ copy: list[Any] = [None] * len(members)
158
+ setter(copy)
159
+ for i in range(len(members) - 1, -1, -1):
160
+ stack.append((_VALUE, members[i], _text.child_path(path, i), depth + 1, _list_setter(copy, i)))
161
+ continue
162
+
163
+ if issubclass(t, dict):
164
+ if depth > ADMISSION_DEPTH_CEILING:
165
+ return invalid(path)
166
+ length = dict.__len__(v)
167
+ count += length
168
+ if count > ceiling:
169
+ return too_large()
170
+ pairs = [(_key_name(k), m) for k, m in dict.items(v)]
171
+ ordered = _text._es_key_order(pairs, name=lambda p: p[0])
172
+ obj: dict[str, Any] = {}
173
+ setter(obj)
174
+ for i in range(len(ordered) - 1, -1, -1):
175
+ k, m = ordered[i]
176
+ member_path = path if k is _NOT_A_STR else _text.child_path(path, k)
177
+ stack.append((_VALUE, m, member_path, depth + 1, _dict_setter(obj, k)))
178
+ stack.append((_KEY, k, path))
179
+ continue
180
+
181
+ return invalid(path)
182
+
183
+ admission_probe.last_count = count
184
+ return Admitted(holder[0], count)
185
+
186
+
187
+ class _NotAStr:
188
+ __slots__ = ()
189
+
190
+ def __repr__(self) -> str:
191
+ return "<not a str>"
192
+
193
+
194
+ _NOT_A_STR: Any = _NotAStr()
195
+
196
+
197
+ def _key_name(k: Any) -> Any:
198
+ t = type(k)
199
+ if t is str:
200
+ return k
201
+ if issubclass(t, str):
202
+ return str.__str__(k)
203
+ return _NOT_A_STR
204
+
205
+
206
+ def _list_setter(copy: list[Any], i: int) -> Any:
207
+ def set_item(x: Any) -> None:
208
+ copy[i] = x
209
+
210
+ return set_item
211
+
212
+
213
+ def _dict_setter(obj: dict[str, Any], k: Any) -> Any:
214
+ def set_member(x: Any) -> None:
215
+ if k is not _NOT_A_STR:
216
+ obj[k] = x
217
+
218
+ return set_member