iii-sdk 0.14.0.dev1__tar.gz → 0.14.0.dev2__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.14.0.dev2}/PKG-INFO +1 -1
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/pyproject.toml +1 -1
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/src/iii/__init__.py +0 -8
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/src/iii/iii.py +17 -39
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/src/iii/iii_types.py +0 -139
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/src/iii/logger.py +5 -5
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/src/iii/telemetry.py +8 -8
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/src/iii/types.py +0 -3
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_bridge.py +4 -4
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_logger_otel.py +3 -3
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_rbac_workers.py +4 -5
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_sync_api.py +1 -23
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_telemetry.py +14 -14
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_trigger_metadata.py +2 -18
- iii_sdk-0.14.0.dev2/tests/test_trigger_registration_error.py +57 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/uv.lock +1078 -1078
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/.gitignore +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/README.md +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/src/iii/baggage_span_processor.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/src/iii/channels.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/src/iii/errors.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/src/iii/format_utils.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/src/iii/iii_constants.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/src/iii/otel_worker_gauges.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/src/iii/payload.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/src/iii/span_ops.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/src/iii/state.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/src/iii/stream.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/src/iii/telemetry_exporters.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/src/iii/telemetry_types.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/src/iii/triggers.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/src/iii/utils.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/src/iii/worker_metrics.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/conftest.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_api_triggers.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_async_api.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_baggage_span_processor.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_channel_close_delay.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_context_propagation.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_data_channels.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_errors.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_format_utils.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_healthcheck.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_hold_process.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_http_external_functions_integration.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_iii_registration_dedup.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_init_api.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_invocation_exception.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_logger_function_ids.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_middleware.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_payload.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_pubsub.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_queue_integration.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_register_function_args.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_span_ops.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_state.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_stream_models.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_streams.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_streams_runtime_annotations.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_telemetry_exporters.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_telemetry_types.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_trace_helpers.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_utils.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_worker_metadata.py +0 -0
- {iii_sdk-0.14.0.dev1 → iii_sdk-0.14.0.dev2}/tests/test_worker_metrics.py +0 -0
|
@@ -10,7 +10,6 @@ from .iii_types import (
|
|
|
10
10
|
AuthInput,
|
|
11
11
|
AuthResult,
|
|
12
12
|
EnqueueResult,
|
|
13
|
-
FunctionInfo,
|
|
14
13
|
HttpAuthConfig,
|
|
15
14
|
HttpInvocationConfig,
|
|
16
15
|
MessageType,
|
|
@@ -23,7 +22,6 @@ from .iii_types import (
|
|
|
23
22
|
OnTriggerTypeRegistrationResult,
|
|
24
23
|
RegisterFunctionFormat,
|
|
25
24
|
RegisterFunctionMessage,
|
|
26
|
-
RegisterServiceInput,
|
|
27
25
|
RegisterTriggerInput,
|
|
28
26
|
RegisterTriggerMessage,
|
|
29
27
|
RegisterTriggerTypeInput,
|
|
@@ -31,9 +29,7 @@ from .iii_types import (
|
|
|
31
29
|
StreamChannelRef,
|
|
32
30
|
TriggerActionEnqueue,
|
|
33
31
|
TriggerActionVoid,
|
|
34
|
-
TriggerInfo,
|
|
35
32
|
TriggerRequest,
|
|
36
|
-
TriggerTypeInfo,
|
|
37
33
|
)
|
|
38
34
|
from .logger import Logger
|
|
39
35
|
from .payload import (
|
|
@@ -110,13 +106,11 @@ __all__ = [
|
|
|
110
106
|
"OnTriggerTypeRegistrationResult",
|
|
111
107
|
# Message types
|
|
112
108
|
"EnqueueResult",
|
|
113
|
-
"FunctionInfo",
|
|
114
109
|
"HttpAuthConfig",
|
|
115
110
|
"HttpInvocationConfig",
|
|
116
111
|
"MessageType",
|
|
117
112
|
"RegisterFunctionFormat",
|
|
118
113
|
"RegisterFunctionMessage",
|
|
119
|
-
"RegisterServiceInput",
|
|
120
114
|
"RegisterTriggerInput",
|
|
121
115
|
"RegisterTriggerMessage",
|
|
122
116
|
"RegisterTriggerTypeInput",
|
|
@@ -124,9 +118,7 @@ __all__ = [
|
|
|
124
118
|
"StreamChannelRef",
|
|
125
119
|
"TriggerActionEnqueue",
|
|
126
120
|
"TriggerActionVoid",
|
|
127
|
-
"TriggerInfo",
|
|
128
121
|
"TriggerRequest",
|
|
129
|
-
"TriggerTypeInfo",
|
|
130
122
|
# Logger
|
|
131
123
|
"Logger",
|
|
132
124
|
# Triggers
|
|
@@ -34,8 +34,6 @@ from .iii_types import (
|
|
|
34
34
|
RegisterFunctionFormat,
|
|
35
35
|
RegisterFunctionInput,
|
|
36
36
|
RegisterFunctionMessage,
|
|
37
|
-
RegisterServiceInput,
|
|
38
|
-
RegisterServiceMessage,
|
|
39
37
|
RegisterTriggerInput,
|
|
40
38
|
RegisterTriggerMessage,
|
|
41
39
|
RegisterTriggerTypeInput,
|
|
@@ -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] = {}
|
|
@@ -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
|
|
@@ -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
|
|
|
@@ -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
|
|
@@ -16,11 +16,11 @@ _SEVERITY_MAP = {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
def
|
|
20
|
-
"""
|
|
21
|
-
from .telemetry import
|
|
19
|
+
def _is_initialized() -> bool:
|
|
20
|
+
"""Internal: True if OTel has been initialized. Imported lazily to avoid a circular import."""
|
|
21
|
+
from .telemetry import _is_initialized as _check
|
|
22
22
|
|
|
23
|
-
return
|
|
23
|
+
return _check()
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class Logger:
|
|
@@ -60,7 +60,7 @@ class Logger:
|
|
|
60
60
|
|
|
61
61
|
def _emit_otel(self, level: str, message: str, data: Any = None) -> bool:
|
|
62
62
|
"""Emit an OTel LogRecord. Returns True if emitted, False if OTel not active."""
|
|
63
|
-
if not
|
|
63
|
+
if not _is_initialized():
|
|
64
64
|
return False
|
|
65
65
|
try:
|
|
66
66
|
from opentelemetry import _logs, trace
|
|
@@ -247,7 +247,7 @@ def _enable_fetch_instrumentation() -> None:
|
|
|
247
247
|
original = _original_opener_open
|
|
248
248
|
|
|
249
249
|
def _patched_open(self: Any, fullurl: Any, data: Any = None, timeout: Any = socket.getdefaulttimeout()) -> Any:
|
|
250
|
-
tracer =
|
|
250
|
+
tracer = _get_tracer()
|
|
251
251
|
if tracer is None:
|
|
252
252
|
return original(self, fullurl, data, timeout)
|
|
253
253
|
|
|
@@ -391,13 +391,13 @@ def attach_event_loop(loop: asyncio.AbstractEventLoop) -> None:
|
|
|
391
391
|
_connection.start(loop)
|
|
392
392
|
|
|
393
393
|
|
|
394
|
-
def
|
|
395
|
-
"""
|
|
394
|
+
def _get_tracer() -> Any:
|
|
395
|
+
"""Internal: return the active tracer, or None if OTel has not been initialized."""
|
|
396
396
|
return _tracer
|
|
397
397
|
|
|
398
398
|
|
|
399
|
-
def
|
|
400
|
-
"""
|
|
399
|
+
def _get_meter() -> Any:
|
|
400
|
+
"""Internal: return the active meter, or None if OTel metrics have not been initialized."""
|
|
401
401
|
return _meter
|
|
402
402
|
|
|
403
403
|
|
|
@@ -421,8 +421,8 @@ def current_span_id() -> str | None:
|
|
|
421
421
|
return None
|
|
422
422
|
|
|
423
423
|
|
|
424
|
-
def
|
|
425
|
-
"""
|
|
424
|
+
def _is_initialized() -> bool:
|
|
425
|
+
"""Internal: return True if OTel has been successfully initialized."""
|
|
426
426
|
return _initialized
|
|
427
427
|
|
|
428
428
|
|
|
@@ -456,7 +456,7 @@ async def with_span(
|
|
|
456
456
|
Returns:
|
|
457
457
|
The value returned by *fn*.
|
|
458
458
|
"""
|
|
459
|
-
tracer =
|
|
459
|
+
tracer = _get_tracer()
|
|
460
460
|
if tracer is None:
|
|
461
461
|
|
|
462
462
|
class _NoopSpan:
|
|
@@ -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,
|
|
@@ -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
|
|
@@ -72,7 +72,7 @@ def test_logger_emits_warn_severity():
|
|
|
72
72
|
|
|
73
73
|
log_exporter = _setup_in_memory_log_provider()
|
|
74
74
|
|
|
75
|
-
with patch("iii.logger.
|
|
75
|
+
with patch("iii.logger._is_initialized", return_value=True):
|
|
76
76
|
logger = Logger()
|
|
77
77
|
logger.warn("watch out")
|
|
78
78
|
|
|
@@ -94,7 +94,7 @@ def test_logger_attaches_trace_context_from_active_span():
|
|
|
94
94
|
trace.set_tracer_provider(tracer_provider)
|
|
95
95
|
tracer = tracer_provider.get_tracer("test")
|
|
96
96
|
|
|
97
|
-
with patch("iii.logger.
|
|
97
|
+
with patch("iii.logger._is_initialized", return_value=True):
|
|
98
98
|
with tracer.start_as_current_span("test-span") as span:
|
|
99
99
|
logger = Logger(service_name="fn1")
|
|
100
100
|
logger.info("inside span")
|
|
@@ -114,7 +114,7 @@ def test_logger_no_trace_context_outside_span():
|
|
|
114
114
|
|
|
115
115
|
log_exporter = _setup_in_memory_log_provider()
|
|
116
116
|
|
|
117
|
-
with patch("iii.logger.
|
|
117
|
+
with patch("iii.logger._is_initialized", return_value=True):
|
|
118
118
|
logger = Logger(service_name="fn1")
|
|
119
119
|
logger.info("outside span")
|
|
120
120
|
|
|
@@ -8,7 +8,6 @@ import pytest
|
|
|
8
8
|
from iii import (
|
|
9
9
|
AuthInput,
|
|
10
10
|
AuthResult,
|
|
11
|
-
FunctionInfo,
|
|
12
11
|
IIIForbiddenError,
|
|
13
12
|
IIIInvocationError,
|
|
14
13
|
InitOptions,
|
|
@@ -291,8 +290,8 @@ class TestRbacWorkers:
|
|
|
291
290
|
result = iii_client.trigger(
|
|
292
291
|
{"function_id": "engine::functions::list", "payload": {}}
|
|
293
292
|
)
|
|
294
|
-
functions =
|
|
295
|
-
function_ids = [f
|
|
293
|
+
functions = result.get("functions", [])
|
|
294
|
+
function_ids = [f["function_id"] for f in functions]
|
|
296
295
|
|
|
297
296
|
assert "test::ew::valid-token-echo" in function_ids
|
|
298
297
|
assert "test::ew::public::echo" in function_ids
|
|
@@ -315,8 +314,8 @@ class TestRbacWorkers:
|
|
|
315
314
|
result = iii_client.trigger(
|
|
316
315
|
{"function_id": "engine::functions::list", "payload": {}}
|
|
317
316
|
)
|
|
318
|
-
functions =
|
|
319
|
-
function_ids = [f
|
|
317
|
+
functions = result.get("functions", [])
|
|
318
|
+
function_ids = [f["function_id"] for f in functions]
|
|
320
319
|
|
|
321
320
|
assert "test::ew::public::echo" in function_ids
|
|
322
321
|
assert "test::ew::meta-public" in function_ids
|
|
@@ -8,7 +8,7 @@ from typing import Any
|
|
|
8
8
|
import pytest
|
|
9
9
|
|
|
10
10
|
import iii.iii as iii_module
|
|
11
|
-
from iii import InitOptions,
|
|
11
|
+
from iii import InitOptions, RegisterTriggerTypeInput
|
|
12
12
|
from iii.iii import III
|
|
13
13
|
from iii.triggers import TriggerConfig, TriggerHandler
|
|
14
14
|
|
|
@@ -158,28 +158,6 @@ def test_register_function_accepts_async_handler(monkeypatch: pytest.MonkeyPatch
|
|
|
158
158
|
client.shutdown()
|
|
159
159
|
|
|
160
160
|
|
|
161
|
-
def test_register_service_accepts_input_object(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
162
|
-
"""register_service should store services by the provided service id."""
|
|
163
|
-
_patch_ws(monkeypatch)
|
|
164
|
-
client = III("ws://fake", InitOptions())
|
|
165
|
-
time.sleep(0.05)
|
|
166
|
-
|
|
167
|
-
client.register_service(
|
|
168
|
-
RegisterServiceInput(
|
|
169
|
-
id="svc.test",
|
|
170
|
-
name="Test Service",
|
|
171
|
-
description="service description",
|
|
172
|
-
parent_service_id="svc.parent",
|
|
173
|
-
)
|
|
174
|
-
)
|
|
175
|
-
|
|
176
|
-
assert "svc.test" in client._services
|
|
177
|
-
service = client._services["svc.test"]
|
|
178
|
-
assert service.name == "Test Service"
|
|
179
|
-
assert service.parent_service_id == "svc.parent"
|
|
180
|
-
|
|
181
|
-
client.shutdown()
|
|
182
|
-
|
|
183
161
|
|
|
184
162
|
def test_register_and_unregister_trigger_type_accept_input_object(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
185
163
|
"""Trigger type registration should accept input objects symmetrically."""
|
|
@@ -4,7 +4,7 @@ import urllib.request
|
|
|
4
4
|
|
|
5
5
|
import pytest
|
|
6
6
|
|
|
7
|
-
from iii.telemetry import
|
|
7
|
+
from iii.telemetry import _get_tracer, _is_initialized, init_otel, shutdown_otel, shutdown_otel_async
|
|
8
8
|
from iii.telemetry_types import OtelConfig
|
|
9
9
|
|
|
10
10
|
# URLLibInstrumentor patches OpenerDirector.open, not urlopen directly
|
|
@@ -35,20 +35,20 @@ def cleanup():
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
def test_not_initialized_by_default():
|
|
38
|
-
assert not
|
|
39
|
-
assert
|
|
38
|
+
assert not _is_initialized()
|
|
39
|
+
assert _get_tracer() is None
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
def test_init_disabled_when_enabled_is_false():
|
|
43
43
|
init_otel(OtelConfig(enabled=False))
|
|
44
|
-
assert not
|
|
45
|
-
assert
|
|
44
|
+
assert not _is_initialized()
|
|
45
|
+
assert _get_tracer() is None
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
def test_init_enabled():
|
|
49
49
|
init_otel(OtelConfig(enabled=True))
|
|
50
|
-
assert
|
|
51
|
-
assert
|
|
50
|
+
assert _is_initialized()
|
|
51
|
+
assert _get_tracer() is not None
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
def test_init_patches_urlopen_by_default():
|
|
@@ -71,15 +71,15 @@ def test_shutdown_restores_urlopen():
|
|
|
71
71
|
def test_shutdown_clears_state():
|
|
72
72
|
init_otel(OtelConfig(enabled=True))
|
|
73
73
|
shutdown_otel()
|
|
74
|
-
assert not
|
|
75
|
-
assert
|
|
74
|
+
assert not _is_initialized()
|
|
75
|
+
assert _get_tracer() is None
|
|
76
76
|
|
|
77
77
|
|
|
78
78
|
def test_init_is_idempotent():
|
|
79
79
|
init_otel(OtelConfig(enabled=True))
|
|
80
|
-
tracer1 =
|
|
80
|
+
tracer1 = _get_tracer()
|
|
81
81
|
init_otel(OtelConfig(enabled=True)) # second call must be no-op
|
|
82
|
-
assert
|
|
82
|
+
assert _get_tracer() is tracer1
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
def test_shutdown_without_init_is_safe():
|
|
@@ -87,13 +87,13 @@ def test_shutdown_without_init_is_safe():
|
|
|
87
87
|
|
|
88
88
|
|
|
89
89
|
def test_telemetry_apis_importable_from_submodules():
|
|
90
|
-
from iii.telemetry import
|
|
90
|
+
from iii.telemetry import _get_tracer, _is_initialized, init_otel, shutdown_otel
|
|
91
91
|
from iii.telemetry_types import OtelConfig
|
|
92
92
|
|
|
93
93
|
assert callable(init_otel)
|
|
94
94
|
assert callable(shutdown_otel)
|
|
95
|
-
assert callable(
|
|
96
|
-
assert callable(
|
|
95
|
+
assert callable(_get_tracer)
|
|
96
|
+
assert callable(_is_initialized)
|
|
97
97
|
assert OtelConfig is not None
|
|
98
98
|
|
|
99
99
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
"""Tests for metadata support in trigger registration types."""
|
|
2
|
-
from iii.iii_types import RegisterTriggerInput, RegisterTriggerMessage
|
|
1
|
+
"""Tests for metadata support in trigger registration protocol types."""
|
|
2
|
+
from iii.iii_types import RegisterTriggerInput, RegisterTriggerMessage
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
def test_register_trigger_input_accepts_metadata():
|
|
@@ -26,19 +26,3 @@ def test_register_trigger_message_includes_metadata():
|
|
|
26
26
|
metadata={"env": "prod"},
|
|
27
27
|
)
|
|
28
28
|
assert msg.metadata == {"env": "prod"}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def test_trigger_info_includes_metadata():
|
|
32
|
-
info = TriggerInfo(
|
|
33
|
-
id="t1",
|
|
34
|
-
trigger_type="http",
|
|
35
|
-
function_id="fn",
|
|
36
|
-
config={},
|
|
37
|
-
metadata={"team": "api"},
|
|
38
|
-
)
|
|
39
|
-
assert info.metadata == {"team": "api"}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def test_trigger_info_metadata_defaults_none():
|
|
43
|
-
info = TriggerInfo(id="t2", trigger_type="cron", function_id="fn")
|
|
44
|
-
assert info.metadata is None
|