iii-sdk 0.14.0.dev1__tar.gz → 0.15.0.dev1__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.
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/PKG-INFO +2 -2
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/pyproject.toml +5 -2
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/src/iii/__init__.py +41 -26
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/src/iii/iii.py +21 -43
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/src/iii/iii_constants.py +1 -20
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/src/iii/iii_types.py +0 -139
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/src/iii/types.py +0 -3
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_baggage_span_processor.py +1 -1
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_bridge.py +4 -4
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_context_propagation.py +1 -2
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_hold_process.py +6 -6
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_http_external_functions_integration.py +2 -2
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_iii_registration_dedup.py +7 -7
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_init_api.py +3 -3
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_logger_function_ids.py +1 -1
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_logger_otel.py +8 -10
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_payload.py +1 -1
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_rbac_workers.py +4 -5
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_register_function_args.py +2 -2
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_span_ops.py +1 -1
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_sync_api.py +3 -25
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_telemetry.py +18 -18
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_telemetry_exporters.py +2 -2
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_telemetry_types.py +1 -1
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_trace_helpers.py +1 -1
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_trigger_metadata.py +2 -18
- iii_sdk-0.15.0.dev1/tests/test_trigger_registration_error.py +57 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/uv.lock +1152 -1080
- iii_sdk-0.14.0.dev1/src/iii/baggage_span_processor.py +0 -42
- iii_sdk-0.14.0.dev1/src/iii/logger.py +0 -184
- iii_sdk-0.14.0.dev1/src/iii/payload.py +0 -92
- iii_sdk-0.14.0.dev1/src/iii/span_ops.py +0 -38
- iii_sdk-0.14.0.dev1/src/iii/telemetry.py +0 -571
- iii_sdk-0.14.0.dev1/src/iii/telemetry_exporters.py +0 -457
- iii_sdk-0.14.0.dev1/src/iii/telemetry_types.py +0 -46
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/.gitignore +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/README.md +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/src/iii/channels.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/src/iii/errors.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/src/iii/format_utils.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/src/iii/otel_worker_gauges.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/src/iii/state.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/src/iii/stream.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/src/iii/triggers.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/src/iii/utils.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/src/iii/worker_metrics.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/conftest.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_api_triggers.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_async_api.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_channel_close_delay.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_data_channels.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_errors.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_format_utils.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_healthcheck.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_invocation_exception.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_middleware.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_pubsub.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_queue_integration.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_state.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_stream_models.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_streams.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_streams_runtime_annotations.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_utils.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_worker_metadata.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_worker_metrics.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iii-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15.0.dev1
|
|
4
4
|
Summary: III SDK for Python
|
|
5
5
|
Project-URL: Homepage, https://github.com/iii-hq/iii
|
|
6
6
|
Project-URL: Repository, https://github.com/iii-hq/iii
|
|
@@ -14,8 +14,8 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
16
|
Requires-Python: >=3.10
|
|
17
|
+
Requires-Dist: iii-observability==0.13.0.dev1
|
|
17
18
|
Requires-Dist: opentelemetry-api>=1.25
|
|
18
|
-
Requires-Dist: opentelemetry-sdk>=1.25
|
|
19
19
|
Requires-Dist: pydantic>=2.0
|
|
20
20
|
Requires-Dist: websockets>=12.0
|
|
21
21
|
Provides-Extra: dev
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "iii-sdk"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.15.0.dev1"
|
|
8
8
|
description = "III SDK for Python"
|
|
9
9
|
authors = [{ name = "III" }]
|
|
10
10
|
license = { text = "Apache-2.0" }
|
|
@@ -23,13 +23,16 @@ dependencies = [
|
|
|
23
23
|
"websockets>=12.0",
|
|
24
24
|
"pydantic>=2.0",
|
|
25
25
|
"opentelemetry-api>=1.25",
|
|
26
|
-
"
|
|
26
|
+
"iii-observability==0.13.0.dev1",
|
|
27
27
|
]
|
|
28
28
|
|
|
29
29
|
[project.urls]
|
|
30
30
|
Homepage = "https://github.com/iii-hq/iii"
|
|
31
31
|
Repository = "https://github.com/iii-hq/iii"
|
|
32
32
|
|
|
33
|
+
[tool.uv.sources]
|
|
34
|
+
iii-observability = { path = "../observability", editable = true }
|
|
35
|
+
|
|
33
36
|
[project.optional-dependencies]
|
|
34
37
|
dev = [
|
|
35
38
|
"pytest>=8.0",
|
|
@@ -1,16 +1,41 @@
|
|
|
1
1
|
"""III SDK for Python."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from iii_observability import (
|
|
4
|
+
DEFAULT_ALLOWLIST,
|
|
5
|
+
REDACTED_PLACEHOLDER,
|
|
6
|
+
BaggageSpanProcessor,
|
|
7
|
+
Logger,
|
|
8
|
+
OtelConfig,
|
|
9
|
+
ReconnectionConfig,
|
|
10
|
+
current_span_id,
|
|
11
|
+
current_span_is_recording,
|
|
12
|
+
current_trace_id,
|
|
13
|
+
execute_traced_request,
|
|
14
|
+
extract_baggage,
|
|
15
|
+
extract_traceparent,
|
|
16
|
+
flush_otel,
|
|
17
|
+
init_otel,
|
|
18
|
+
inject_baggage,
|
|
19
|
+
inject_traceparent,
|
|
20
|
+
record_span_event,
|
|
21
|
+
redact,
|
|
22
|
+
redact_and_truncate,
|
|
23
|
+
resolve_max_bytes_from_env,
|
|
24
|
+
set_current_span_attribute,
|
|
25
|
+
set_current_span_error,
|
|
26
|
+
shutdown_otel,
|
|
27
|
+
with_span,
|
|
28
|
+
)
|
|
29
|
+
|
|
4
30
|
from .channels import ChannelReader, ChannelWriter
|
|
5
31
|
from .errors import IIIForbiddenError, IIIInvocationError, IIITimeoutError
|
|
6
32
|
from .format_utils import extract_request_format, extract_response_format, python_type_to_format
|
|
7
33
|
from .iii import TriggerAction, register_worker
|
|
8
|
-
from .iii_constants import FunctionRef, InitOptions,
|
|
34
|
+
from .iii_constants import FunctionRef, InitOptions, TelemetryOptions
|
|
9
35
|
from .iii_types import (
|
|
10
36
|
AuthInput,
|
|
11
37
|
AuthResult,
|
|
12
38
|
EnqueueResult,
|
|
13
|
-
FunctionInfo,
|
|
14
39
|
HttpAuthConfig,
|
|
15
40
|
HttpInvocationConfig,
|
|
16
41
|
MessageType,
|
|
@@ -23,7 +48,6 @@ from .iii_types import (
|
|
|
23
48
|
OnTriggerTypeRegistrationResult,
|
|
24
49
|
RegisterFunctionFormat,
|
|
25
50
|
RegisterFunctionMessage,
|
|
26
|
-
RegisterServiceInput,
|
|
27
51
|
RegisterTriggerInput,
|
|
28
52
|
RegisterTriggerMessage,
|
|
29
53
|
RegisterTriggerTypeInput,
|
|
@@ -31,22 +55,7 @@ from .iii_types import (
|
|
|
31
55
|
StreamChannelRef,
|
|
32
56
|
TriggerActionEnqueue,
|
|
33
57
|
TriggerActionVoid,
|
|
34
|
-
TriggerInfo,
|
|
35
58
|
TriggerRequest,
|
|
36
|
-
TriggerTypeInfo,
|
|
37
|
-
)
|
|
38
|
-
from .logger import Logger
|
|
39
|
-
from .payload import (
|
|
40
|
-
REDACTED_PLACEHOLDER,
|
|
41
|
-
redact,
|
|
42
|
-
redact_and_truncate,
|
|
43
|
-
resolve_max_bytes_from_env,
|
|
44
|
-
)
|
|
45
|
-
from .span_ops import (
|
|
46
|
-
current_span_is_recording,
|
|
47
|
-
record_span_event,
|
|
48
|
-
set_current_span_attribute,
|
|
49
|
-
set_current_span_error,
|
|
50
59
|
)
|
|
51
60
|
from .stream import (
|
|
52
61
|
IStream,
|
|
@@ -57,7 +66,6 @@ from .stream import (
|
|
|
57
66
|
StreamJoinLeaveTriggerConfig,
|
|
58
67
|
StreamTriggerConfig,
|
|
59
68
|
)
|
|
60
|
-
from .telemetry_types import OtelConfig
|
|
61
69
|
from .triggers import Trigger, TriggerConfig, TriggerHandler, TriggerTypeRef
|
|
62
70
|
from .types import (
|
|
63
71
|
ApiRequest,
|
|
@@ -76,13 +84,24 @@ __all__ = [
|
|
|
76
84
|
"BaggageSpanProcessor",
|
|
77
85
|
"DEFAULT_ALLOWLIST",
|
|
78
86
|
"REDACTED_PLACEHOLDER",
|
|
87
|
+
"current_span_id",
|
|
79
88
|
"current_span_is_recording",
|
|
89
|
+
"current_trace_id",
|
|
90
|
+
"execute_traced_request",
|
|
91
|
+
"extract_baggage",
|
|
92
|
+
"extract_traceparent",
|
|
93
|
+
"flush_otel",
|
|
94
|
+
"init_otel",
|
|
95
|
+
"inject_baggage",
|
|
96
|
+
"inject_traceparent",
|
|
80
97
|
"record_span_event",
|
|
81
|
-
"set_current_span_attribute",
|
|
82
|
-
"set_current_span_error",
|
|
83
98
|
"redact",
|
|
84
99
|
"redact_and_truncate",
|
|
85
100
|
"resolve_max_bytes_from_env",
|
|
101
|
+
"set_current_span_attribute",
|
|
102
|
+
"set_current_span_error",
|
|
103
|
+
"shutdown_otel",
|
|
104
|
+
"with_span",
|
|
86
105
|
# Channels
|
|
87
106
|
"ChannelReader",
|
|
88
107
|
"ChannelWriter",
|
|
@@ -110,13 +129,11 @@ __all__ = [
|
|
|
110
129
|
"OnTriggerTypeRegistrationResult",
|
|
111
130
|
# Message types
|
|
112
131
|
"EnqueueResult",
|
|
113
|
-
"FunctionInfo",
|
|
114
132
|
"HttpAuthConfig",
|
|
115
133
|
"HttpInvocationConfig",
|
|
116
134
|
"MessageType",
|
|
117
135
|
"RegisterFunctionFormat",
|
|
118
136
|
"RegisterFunctionMessage",
|
|
119
|
-
"RegisterServiceInput",
|
|
120
137
|
"RegisterTriggerInput",
|
|
121
138
|
"RegisterTriggerMessage",
|
|
122
139
|
"RegisterTriggerTypeInput",
|
|
@@ -124,9 +141,7 @@ __all__ = [
|
|
|
124
141
|
"StreamChannelRef",
|
|
125
142
|
"TriggerActionEnqueue",
|
|
126
143
|
"TriggerActionVoid",
|
|
127
|
-
"TriggerInfo",
|
|
128
144
|
"TriggerRequest",
|
|
129
|
-
"TriggerTypeInfo",
|
|
130
145
|
# Logger
|
|
131
146
|
"Logger",
|
|
132
147
|
# Triggers
|
|
@@ -14,6 +14,7 @@ from importlib.metadata import version
|
|
|
14
14
|
from typing import Any, Awaitable, Callable, Coroutine, TypeVar, cast
|
|
15
15
|
|
|
16
16
|
import websockets
|
|
17
|
+
from iii_observability import OtelConfig
|
|
17
18
|
from websockets.asyncio.client import ClientConnection
|
|
18
19
|
|
|
19
20
|
from .channels import ChannelReader, ChannelWriter
|
|
@@ -34,8 +35,6 @@ from .iii_types import (
|
|
|
34
35
|
RegisterFunctionFormat,
|
|
35
36
|
RegisterFunctionInput,
|
|
36
37
|
RegisterFunctionMessage,
|
|
37
|
-
RegisterServiceInput,
|
|
38
|
-
RegisterServiceMessage,
|
|
39
38
|
RegisterTriggerInput,
|
|
40
39
|
RegisterTriggerMessage,
|
|
41
40
|
RegisterTriggerTypeInput,
|
|
@@ -56,7 +55,6 @@ from .stream import (
|
|
|
56
55
|
StreamListInput,
|
|
57
56
|
StreamSetInput,
|
|
58
57
|
)
|
|
59
|
-
from .telemetry_types import OtelConfig
|
|
60
58
|
from .triggers import Trigger, TriggerConfig, TriggerHandler, TriggerTypeRef
|
|
61
59
|
from .types import Channel, RemoteFunctionData, RemoteTriggerTypeData, is_channel_ref
|
|
62
60
|
|
|
@@ -152,7 +150,6 @@ class III:
|
|
|
152
150
|
self._options = options or InitOptions()
|
|
153
151
|
self._ws: ClientConnection | None = None
|
|
154
152
|
self._functions: dict[str, RemoteFunctionData] = {}
|
|
155
|
-
self._services: dict[str, RegisterServiceMessage] = {}
|
|
156
153
|
self._pending: dict[str, _PendingInvocation] = {}
|
|
157
154
|
self._triggers: dict[str, RegisterTriggerMessage] = {}
|
|
158
155
|
self._trigger_types: dict[str, RemoteTriggerTypeData] = {}
|
|
@@ -227,7 +224,7 @@ class III:
|
|
|
227
224
|
from an async context.
|
|
228
225
|
"""
|
|
229
226
|
self._running = True
|
|
230
|
-
from .telemetry import attach_event_loop, init_otel
|
|
227
|
+
from iii_observability.telemetry import attach_event_loop, init_otel
|
|
231
228
|
|
|
232
229
|
loop = asyncio.get_running_loop()
|
|
233
230
|
otel_cfg: OtelConfig | None = None
|
|
@@ -283,7 +280,7 @@ class III:
|
|
|
283
280
|
|
|
284
281
|
self._set_connection_state("disconnected")
|
|
285
282
|
|
|
286
|
-
from .telemetry import shutdown_otel_async
|
|
283
|
+
from iii_observability.telemetry import shutdown_otel_async
|
|
287
284
|
|
|
288
285
|
await shutdown_otel_async()
|
|
289
286
|
|
|
@@ -344,8 +341,6 @@ class III:
|
|
|
344
341
|
# Re-register all (snapshot to avoid mutation from caller thread)
|
|
345
342
|
for trigger_type_data in list(self._trigger_types.values()):
|
|
346
343
|
await self._send(trigger_type_data.message)
|
|
347
|
-
for svc in list(self._services.values()):
|
|
348
|
-
await self._send(svc)
|
|
349
344
|
for function_data in list(self._functions.values()):
|
|
350
345
|
await self._send(function_data.message)
|
|
351
346
|
for trigger in list(self._triggers.values()):
|
|
@@ -443,6 +438,8 @@ class III:
|
|
|
443
438
|
)
|
|
444
439
|
elif msg_type == MessageType.REGISTER_TRIGGER.value:
|
|
445
440
|
asyncio.create_task(self._handle_trigger_registration(data))
|
|
441
|
+
elif msg_type == MessageType.TRIGGER_REGISTRATION_RESULT.value:
|
|
442
|
+
self._handle_trigger_registration_result(data)
|
|
446
443
|
elif msg_type == MessageType.WORKER_REGISTERED.value:
|
|
447
444
|
worker_id = data.get("worker_id", "")
|
|
448
445
|
self._worker_id = worker_id
|
|
@@ -502,7 +499,7 @@ class III:
|
|
|
502
499
|
tracer = trace.get_tracer("iii-python-sdk")
|
|
503
500
|
import os
|
|
504
501
|
|
|
505
|
-
from
|
|
502
|
+
from iii_observability import redact_and_truncate, resolve_max_bytes_from_env
|
|
506
503
|
|
|
507
504
|
trace_payloads = os.environ.get("III_DISABLE_TRACE_PAYLOADS", "").lower() not in (
|
|
508
505
|
"1",
|
|
@@ -706,6 +703,21 @@ class III:
|
|
|
706
703
|
}
|
|
707
704
|
)
|
|
708
705
|
|
|
706
|
+
def _handle_trigger_registration_result(self, data: dict[str, Any]) -> None:
|
|
707
|
+
error = data.get("error")
|
|
708
|
+
if not error:
|
|
709
|
+
return
|
|
710
|
+
|
|
711
|
+
trigger_id = data.get("id", "")
|
|
712
|
+
trigger_type = data.get("trigger_type", "")
|
|
713
|
+
message = error.get("message", "")
|
|
714
|
+
log.error(
|
|
715
|
+
"[iii] Trigger registration failed for %r (%s): %s",
|
|
716
|
+
trigger_id,
|
|
717
|
+
trigger_type,
|
|
718
|
+
message,
|
|
719
|
+
)
|
|
720
|
+
|
|
709
721
|
# Connection state management
|
|
710
722
|
|
|
711
723
|
def _set_connection_state(self, state: IIIConnectionState) -> None:
|
|
@@ -1014,40 +1026,6 @@ class III:
|
|
|
1014
1026
|
|
|
1015
1027
|
return FunctionRef(id=func_id, unregister=unregister)
|
|
1016
1028
|
|
|
1017
|
-
def register_service(self, service: RegisterServiceInput | dict[str, Any]) -> None:
|
|
1018
|
-
"""Register a logical service grouping with the engine.
|
|
1019
|
-
|
|
1020
|
-
Services provide an organisational hierarchy for functions. A
|
|
1021
|
-
service can optionally reference a ``parent_service_id`` to form
|
|
1022
|
-
a tree visible in the engine dashboard.
|
|
1023
|
-
|
|
1024
|
-
Note:
|
|
1025
|
-
Services are organizational groupings visible in the engine
|
|
1026
|
-
dashboard. They do not affect function invocation behavior.
|
|
1027
|
-
|
|
1028
|
-
Args:
|
|
1029
|
-
service: A ``RegisterServiceInput`` or dict with ``id`` and
|
|
1030
|
-
optional ``name``, ``description``, ``parent_service_id``.
|
|
1031
|
-
|
|
1032
|
-
Examples:
|
|
1033
|
-
>>> iii.register_service({"id": "payments", "description": "Payment processing"})
|
|
1034
|
-
>>> iii.register_service({
|
|
1035
|
-
... "id": "payments::refunds",
|
|
1036
|
-
... "description": "Refund sub-service",
|
|
1037
|
-
... "parent_service_id": "payments",
|
|
1038
|
-
... })
|
|
1039
|
-
"""
|
|
1040
|
-
if isinstance(service, dict):
|
|
1041
|
-
service = RegisterServiceInput(**service)
|
|
1042
|
-
msg = RegisterServiceMessage(
|
|
1043
|
-
id=service.id,
|
|
1044
|
-
name=service.name or service.id,
|
|
1045
|
-
description=service.description,
|
|
1046
|
-
parent_service_id=service.parent_service_id,
|
|
1047
|
-
)
|
|
1048
|
-
self._services[service.id] = msg
|
|
1049
|
-
self._send_if_connected(msg)
|
|
1050
|
-
|
|
1051
1029
|
def trigger(self, request: "dict[str, Any] | TriggerRequest") -> Any:
|
|
1052
1030
|
"""Invoke a remote function.
|
|
1053
1031
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from typing import Any, Callable, Literal
|
|
5
5
|
|
|
6
|
-
from
|
|
6
|
+
from iii_observability import OtelConfig, ReconnectionConfig
|
|
7
7
|
|
|
8
8
|
IIIConnectionState = Literal["disconnected", "connecting", "connected", "reconnecting", "failed"]
|
|
9
9
|
|
|
@@ -13,25 +13,6 @@ DEFAULT_INVOCATION_TIMEOUT_MS = 30000
|
|
|
13
13
|
MAX_QUEUE_SIZE = 1000
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
@dataclass
|
|
17
|
-
class ReconnectionConfig:
|
|
18
|
-
"""Configuration for WebSocket reconnection behavior.
|
|
19
|
-
|
|
20
|
-
Attributes:
|
|
21
|
-
initial_delay_ms: Starting delay in milliseconds. Default ``1000``.
|
|
22
|
-
max_delay_ms: Maximum delay cap in milliseconds. Default ``30000``.
|
|
23
|
-
backoff_multiplier: Exponential backoff multiplier. Default ``2.0``.
|
|
24
|
-
jitter_factor: Random jitter factor (0--1). Default ``0.3``.
|
|
25
|
-
max_retries: Maximum retry attempts. ``-1`` for infinite. Default ``-1``.
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
initial_delay_ms: int = 1000
|
|
29
|
-
max_delay_ms: int = 30000
|
|
30
|
-
backoff_multiplier: float = 2.0
|
|
31
|
-
jitter_factor: float = 0.3
|
|
32
|
-
max_retries: int = -1
|
|
33
|
-
|
|
34
|
-
|
|
35
16
|
DEFAULT_RECONNECTION_CONFIG = ReconnectionConfig()
|
|
36
17
|
|
|
37
18
|
|
|
@@ -148,25 +148,6 @@ class RegisterTriggerInput(BaseModel):
|
|
|
148
148
|
)
|
|
149
149
|
|
|
150
150
|
|
|
151
|
-
class RegisterServiceInput(BaseModel):
|
|
152
|
-
"""Input for registering a service (matches Node SDK's RegisterServiceInput).
|
|
153
|
-
|
|
154
|
-
Attributes:
|
|
155
|
-
id: Unique service identifier.
|
|
156
|
-
name: Human-readable service name.
|
|
157
|
-
description: Description of the service.
|
|
158
|
-
parent_service_id: ID of the parent service for hierarchical grouping.
|
|
159
|
-
"""
|
|
160
|
-
|
|
161
|
-
id: str = Field(description="Unique service identifier.")
|
|
162
|
-
name: str | None = Field(default=None, description="Human-readable service name.")
|
|
163
|
-
description: str | None = Field(default=None, description="Description of the service.")
|
|
164
|
-
parent_service_id: str | None = Field(
|
|
165
|
-
default=None,
|
|
166
|
-
description="ID of the parent service for hierarchical grouping.",
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
|
|
170
151
|
class RegisterTriggerMessage(BaseModel):
|
|
171
152
|
model_config = ConfigDict(populate_by_name=True)
|
|
172
153
|
|
|
@@ -178,16 +159,6 @@ class RegisterTriggerMessage(BaseModel):
|
|
|
178
159
|
message_type: MessageType = Field(default=MessageType.REGISTER_TRIGGER, alias="type")
|
|
179
160
|
|
|
180
161
|
|
|
181
|
-
class RegisterServiceMessage(BaseModel):
|
|
182
|
-
model_config = ConfigDict(populate_by_name=True)
|
|
183
|
-
|
|
184
|
-
id: str
|
|
185
|
-
name: str | None = None
|
|
186
|
-
description: str | None = None
|
|
187
|
-
parent_service_id: str | None = Field(default=None)
|
|
188
|
-
message_type: MessageType = Field(default=MessageType.REGISTER_SERVICE, alias="type")
|
|
189
|
-
|
|
190
|
-
|
|
191
162
|
class RegisterFunctionFormat(BaseModel):
|
|
192
163
|
"""Format definition for function parameters.
|
|
193
164
|
|
|
@@ -533,115 +504,6 @@ class UnregisterFunctionMessage(BaseModel):
|
|
|
533
504
|
message_type: MessageType = Field(default=MessageType.UNREGISTER_FUNCTION, alias="type")
|
|
534
505
|
|
|
535
506
|
|
|
536
|
-
class FunctionInfo(BaseModel):
|
|
537
|
-
"""Information about a registered function.
|
|
538
|
-
|
|
539
|
-
Attributes:
|
|
540
|
-
function_id: Unique identifier of the function.
|
|
541
|
-
description: Human-readable description.
|
|
542
|
-
request_format: Schema describing expected input (JSON Schema or custom format).
|
|
543
|
-
response_format: Schema describing expected output (JSON Schema or custom format).
|
|
544
|
-
metadata: Arbitrary metadata attached to the function.
|
|
545
|
-
"""
|
|
546
|
-
|
|
547
|
-
function_id: str = Field(description="Unique identifier of the function.")
|
|
548
|
-
description: str | None = Field(default=None, description="Human-readable description.")
|
|
549
|
-
request_format: dict[str, Any] | None = Field(
|
|
550
|
-
default=None, description="Schema describing expected input (JSON Schema or custom format)."
|
|
551
|
-
)
|
|
552
|
-
response_format: dict[str, Any] | None = Field(
|
|
553
|
-
default=None, description="Schema describing expected output (JSON Schema or custom format)."
|
|
554
|
-
)
|
|
555
|
-
metadata: dict[str, Any] | None = Field(
|
|
556
|
-
default=None, description="Arbitrary metadata attached to the function."
|
|
557
|
-
)
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
class TriggerInfo(BaseModel):
|
|
561
|
-
"""Information about a registered trigger.
|
|
562
|
-
|
|
563
|
-
Attributes:
|
|
564
|
-
id: Unique trigger identifier.
|
|
565
|
-
trigger_type: Type of trigger (e.g. ``http``, ``queue``, ``cron``).
|
|
566
|
-
function_id: ID of the function this trigger invokes.
|
|
567
|
-
config: Trigger-type-specific configuration.
|
|
568
|
-
metadata: Arbitrary metadata attached to the trigger.
|
|
569
|
-
"""
|
|
570
|
-
|
|
571
|
-
id: str = Field(description="Unique trigger identifier.")
|
|
572
|
-
trigger_type: str = Field(description="Type of trigger (e.g. ``http``, ``queue``, ``cron``).")
|
|
573
|
-
function_id: str = Field(description="ID of the function this trigger invokes.")
|
|
574
|
-
config: Any = Field(default=None, description="Trigger-type-specific configuration.")
|
|
575
|
-
metadata: dict[str, Any] | None = Field(
|
|
576
|
-
default=None, description="Arbitrary metadata attached to the trigger."
|
|
577
|
-
)
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
class TriggerTypeInfo(BaseModel):
|
|
581
|
-
"""Information about a registered trigger type.
|
|
582
|
-
|
|
583
|
-
Attributes:
|
|
584
|
-
id: Trigger type identifier (e.g. ``http``, ``cron``, ``queue``).
|
|
585
|
-
description: Human-readable description of the trigger type.
|
|
586
|
-
trigger_request_format: JSON Schema for the trigger configuration.
|
|
587
|
-
call_request_format: JSON Schema for the call request payload.
|
|
588
|
-
"""
|
|
589
|
-
|
|
590
|
-
id: str = Field(description="Trigger type identifier.")
|
|
591
|
-
description: str = Field(description="Human-readable description.")
|
|
592
|
-
trigger_request_format: Any | None = Field(
|
|
593
|
-
default=None, description="JSON Schema for trigger configuration."
|
|
594
|
-
)
|
|
595
|
-
call_request_format: Any | None = Field(
|
|
596
|
-
default=None, description="JSON Schema for the call request payload."
|
|
597
|
-
)
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
WorkerStatus = Literal["connected", "available", "busy", "disconnected"]
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
class WorkerInfo(BaseModel):
|
|
604
|
-
"""Information about a connected worker.
|
|
605
|
-
|
|
606
|
-
Attributes:
|
|
607
|
-
id: Engine-assigned unique worker ID.
|
|
608
|
-
name: Worker name from InitOptions.
|
|
609
|
-
runtime: SDK runtime (``python``, ``node``, ``rust``).
|
|
610
|
-
version: SDK version string.
|
|
611
|
-
os: Operating system identifier.
|
|
612
|
-
ip_address: Worker's IP address as seen by the engine.
|
|
613
|
-
status: Current status (``connected``, ``available``, ``busy``, ``disconnected``).
|
|
614
|
-
connected_at_ms: Connection timestamp in milliseconds since epoch.
|
|
615
|
-
function_count: Number of registered functions.
|
|
616
|
-
functions: List of registered function IDs.
|
|
617
|
-
active_invocations: Number of currently executing invocations.
|
|
618
|
-
"""
|
|
619
|
-
|
|
620
|
-
id: str = Field(description="Engine-assigned unique worker ID.")
|
|
621
|
-
name: str | None = Field(default=None, description="Worker name from InitOptions.")
|
|
622
|
-
runtime: str | None = Field(
|
|
623
|
-
default=None,
|
|
624
|
-
description="SDK runtime (``python``, ``node``, ``rust``).",
|
|
625
|
-
)
|
|
626
|
-
version: str | None = Field(default=None, description="SDK version string.")
|
|
627
|
-
os: str | None = Field(default=None, description="Operating system identifier.")
|
|
628
|
-
ip_address: str | None = Field(
|
|
629
|
-
default=None,
|
|
630
|
-
description="Worker's IP address as seen by the engine.",
|
|
631
|
-
)
|
|
632
|
-
status: WorkerStatus = Field(
|
|
633
|
-
description="Current status (``connected``, ``available``, ``busy``, ``disconnected``)."
|
|
634
|
-
)
|
|
635
|
-
connected_at_ms: int = Field(description="Connection timestamp in milliseconds since epoch.")
|
|
636
|
-
function_count: int = Field(description="Number of registered functions.")
|
|
637
|
-
functions: list[str] = Field(description="List of registered function IDs.")
|
|
638
|
-
active_invocations: int = Field(description="Number of currently executing invocations.")
|
|
639
|
-
isolation: str | None = Field(
|
|
640
|
-
default=None,
|
|
641
|
-
description="Self-reported isolation context (e.g. ``libkrun``, ``docker``, ``k8s``).",
|
|
642
|
-
)
|
|
643
|
-
|
|
644
|
-
|
|
645
507
|
class StreamChannelRef(BaseModel):
|
|
646
508
|
"""Reference to a streaming channel for worker-to-worker data transfer.
|
|
647
509
|
|
|
@@ -678,7 +540,6 @@ IIIMessage = (
|
|
|
678
540
|
| UnregisterFunctionMessage
|
|
679
541
|
| InvokeFunctionMessage
|
|
680
542
|
| InvocationResultMessage
|
|
681
|
-
| RegisterServiceMessage
|
|
682
543
|
| RegisterTriggerMessage
|
|
683
544
|
| RegisterTriggerTypeMessage
|
|
684
545
|
| UnregisterTriggerMessage
|
|
@@ -12,7 +12,6 @@ from pydantic import BaseModel, ConfigDict, Field
|
|
|
12
12
|
from .iii_types import (
|
|
13
13
|
HttpInvocationConfig,
|
|
14
14
|
RegisterFunctionMessage,
|
|
15
|
-
RegisterServiceInput,
|
|
16
15
|
RegisterTriggerInput,
|
|
17
16
|
RegisterTriggerTypeInput,
|
|
18
17
|
RegisterTriggerTypeMessage,
|
|
@@ -82,8 +81,6 @@ class IIIClient(Protocol):
|
|
|
82
81
|
|
|
83
82
|
def register_trigger(self, trigger: RegisterTriggerInput | dict[str, Any]) -> Trigger: ...
|
|
84
83
|
|
|
85
|
-
def register_service(self, service: RegisterServiceInput | dict[str, Any]) -> None: ...
|
|
86
|
-
|
|
87
84
|
def register_function(
|
|
88
85
|
self,
|
|
89
86
|
function_id: str,
|
|
@@ -10,7 +10,7 @@ from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
|
|
|
10
10
|
)
|
|
11
11
|
from opentelemetry.sdk.trace.sampling import ALWAYS_OFF
|
|
12
12
|
|
|
13
|
-
from
|
|
13
|
+
from iii_observability import (
|
|
14
14
|
DEFAULT_ALLOWLIST,
|
|
15
15
|
BaggageSpanProcessor,
|
|
16
16
|
)
|
|
@@ -4,7 +4,7 @@ import asyncio
|
|
|
4
4
|
|
|
5
5
|
import pytest
|
|
6
6
|
|
|
7
|
-
from iii import
|
|
7
|
+
from iii import TriggerAction
|
|
8
8
|
from iii.iii import III
|
|
9
9
|
|
|
10
10
|
|
|
@@ -22,7 +22,7 @@ async def wait_for(condition, timeout=5.0, interval=0.1):
|
|
|
22
22
|
async def test_connect_successfully(iii_client: III):
|
|
23
23
|
"""SDK connects to the engine and can list functions."""
|
|
24
24
|
result = iii_client.trigger({"function_id": "engine::functions::list", "payload": {}})
|
|
25
|
-
functions =
|
|
25
|
+
functions = result.get("functions", [])
|
|
26
26
|
assert isinstance(functions, list)
|
|
27
27
|
|
|
28
28
|
|
|
@@ -93,8 +93,8 @@ async def test_list_registered_functions(iii_client: III):
|
|
|
93
93
|
|
|
94
94
|
try:
|
|
95
95
|
result = iii_client.trigger({"function_id": "engine::functions::list", "payload": {}})
|
|
96
|
-
functions =
|
|
97
|
-
function_ids = [f
|
|
96
|
+
functions = result.get("functions", [])
|
|
97
|
+
function_ids = [f["function_id"] for f in functions]
|
|
98
98
|
|
|
99
99
|
assert "test.bridge.py.list.func1" in function_ids
|
|
100
100
|
assert "test.bridge.py.list.func2" in function_ids
|
|
@@ -7,8 +7,7 @@ import pytest
|
|
|
7
7
|
import iii.iii as iii_module
|
|
8
8
|
from iii.iii import III
|
|
9
9
|
from iii.iii_constants import InitOptions
|
|
10
|
-
from
|
|
11
|
-
from iii.telemetry_types import OtelConfig
|
|
10
|
+
from iii_observability import OtelConfig, init_otel, shutdown_otel
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
@pytest.fixture(autouse=True)
|
|
@@ -5,8 +5,8 @@ from iii.iii import III
|
|
|
5
5
|
|
|
6
6
|
def test_background_thread_is_not_daemon(monkeypatch) -> None:
|
|
7
7
|
"""The background event-loop thread must be non-daemon so it keeps the process alive."""
|
|
8
|
-
monkeypatch.setattr("
|
|
9
|
-
monkeypatch.setattr("
|
|
8
|
+
monkeypatch.setattr("iii_observability.telemetry.init_otel", lambda **kwargs: None)
|
|
9
|
+
monkeypatch.setattr("iii_observability.telemetry.attach_event_loop", lambda loop: None)
|
|
10
10
|
|
|
11
11
|
async def fake_do_connect(self: III) -> None:
|
|
12
12
|
return None
|
|
@@ -23,8 +23,8 @@ def test_background_thread_is_not_daemon(monkeypatch) -> None:
|
|
|
23
23
|
|
|
24
24
|
def test_shutdown_stops_background_thread(monkeypatch) -> None:
|
|
25
25
|
"""After shutdown(), the background thread should stop within a reasonable timeout."""
|
|
26
|
-
monkeypatch.setattr("
|
|
27
|
-
monkeypatch.setattr("
|
|
26
|
+
monkeypatch.setattr("iii_observability.telemetry.init_otel", lambda **kwargs: None)
|
|
27
|
+
monkeypatch.setattr("iii_observability.telemetry.attach_event_loop", lambda loop: None)
|
|
28
28
|
|
|
29
29
|
async def fake_do_connect(self: III) -> None:
|
|
30
30
|
return None
|
|
@@ -43,8 +43,8 @@ def test_shutdown_stops_background_thread(monkeypatch) -> None:
|
|
|
43
43
|
|
|
44
44
|
def test_shutdown_async_stops_background_thread(monkeypatch) -> None:
|
|
45
45
|
"""After shutdown_async(), the background thread should also stop."""
|
|
46
|
-
monkeypatch.setattr("
|
|
47
|
-
monkeypatch.setattr("
|
|
46
|
+
monkeypatch.setattr("iii_observability.telemetry.init_otel", lambda **kwargs: None)
|
|
47
|
+
monkeypatch.setattr("iii_observability.telemetry.attach_event_loop", lambda loop: None)
|
|
48
48
|
|
|
49
49
|
async def fake_do_connect(self: III) -> None:
|
|
50
50
|
return None
|
{iii_sdk-0.14.0.dev1 → iii_sdk-0.15.0.dev1}/tests/test_http_external_functions_integration.py
RENAMED
|
@@ -157,8 +157,8 @@ def _make_fake_ws_env(monkeypatch: pytest.MonkeyPatch) -> list[dict[str, Any]]:
|
|
|
157
157
|
return FakeWs()
|
|
158
158
|
|
|
159
159
|
monkeypatch.setattr(iii_module.websockets, "connect", fake_connect)
|
|
160
|
-
monkeypatch.setattr("
|
|
161
|
-
monkeypatch.setattr("
|
|
160
|
+
monkeypatch.setattr("iii_observability.telemetry.init_otel", lambda **kwargs: None)
|
|
161
|
+
monkeypatch.setattr("iii_observability.telemetry.attach_event_loop", lambda loop: None)
|
|
162
162
|
monkeypatch.setattr(iii_module.III, "_register_worker_metadata", lambda self: None)
|
|
163
163
|
return sent
|
|
164
164
|
|
|
@@ -17,7 +17,7 @@ def reset_otel():
|
|
|
17
17
|
# III.connect() calls init_otel() which sets global providers;
|
|
18
18
|
# reset them so subsequent test files start with a clean slate.
|
|
19
19
|
try:
|
|
20
|
-
from
|
|
20
|
+
from iii_observability import shutdown_otel
|
|
21
21
|
|
|
22
22
|
shutdown_otel()
|
|
23
23
|
except Exception:
|
|
@@ -73,8 +73,8 @@ def test_preconnect_registration_sent_once(monkeypatch: pytest.MonkeyPatch) -> N
|
|
|
73
73
|
return ws
|
|
74
74
|
|
|
75
75
|
monkeypatch.setattr(iii_module.websockets, "connect", fake_connect)
|
|
76
|
-
monkeypatch.setattr("
|
|
77
|
-
monkeypatch.setattr("
|
|
76
|
+
monkeypatch.setattr("iii_observability.telemetry.init_otel", lambda **kwargs: None)
|
|
77
|
+
monkeypatch.setattr("iii_observability.telemetry.attach_event_loop", lambda loop: None)
|
|
78
78
|
monkeypatch.setattr(iii_module.III, "_register_worker_metadata", lambda self: None)
|
|
79
79
|
|
|
80
80
|
client = III("ws://fake")
|
|
@@ -110,8 +110,8 @@ def test_reconnect_replays_durable_state_once_per_connection(
|
|
|
110
110
|
return ws
|
|
111
111
|
|
|
112
112
|
monkeypatch.setattr(iii_module.websockets, "connect", fake_connect)
|
|
113
|
-
monkeypatch.setattr("
|
|
114
|
-
monkeypatch.setattr("
|
|
113
|
+
monkeypatch.setattr("iii_observability.telemetry.init_otel", lambda **kwargs: None)
|
|
114
|
+
monkeypatch.setattr("iii_observability.telemetry.attach_event_loop", lambda loop: None)
|
|
115
115
|
monkeypatch.setattr(iii_module.III, "_register_worker_metadata", lambda self: None)
|
|
116
116
|
|
|
117
117
|
client = III("ws://fake")
|
|
@@ -153,8 +153,8 @@ def test_call_void_queued_while_disconnected_flushes_after_connect(
|
|
|
153
153
|
return ws
|
|
154
154
|
|
|
155
155
|
monkeypatch.setattr(iii_module.websockets, "connect", fake_connect)
|
|
156
|
-
monkeypatch.setattr("
|
|
157
|
-
monkeypatch.setattr("
|
|
156
|
+
monkeypatch.setattr("iii_observability.telemetry.init_otel", lambda **kwargs: None)
|
|
157
|
+
monkeypatch.setattr("iii_observability.telemetry.attach_event_loop", lambda loop: None)
|
|
158
158
|
monkeypatch.setattr(iii_module.III, "_register_worker_metadata", lambda self: None)
|
|
159
159
|
|
|
160
160
|
client = III("ws://fake")
|