iii-sdk 0.18.0.dev1__tar.gz → 0.19.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.18.0.dev1 → iii_sdk-0.19.0.dev1}/PKG-INFO +2 -2
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/pyproject.toml +2 -2
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/iii.py +28 -0
- iii_sdk-0.19.0.dev1/tests/test_trigger_type_lifecycle.py +157 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/.gitignore +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/README.md +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/__init__.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/channels.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/errors.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/format_utils.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/helpers.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/iii_constants.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/iii_types.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/otel_worker_gauges.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/state.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/stream.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/triggers.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/types.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/utils.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/worker_metrics.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/conftest.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_api_triggers.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_async_api.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_baggage_span_processor.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_bridge.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_channel_close_delay.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_context_propagation.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_data_channels.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_errors.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_format_utils.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_healthcheck.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_helpers.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_hold_process.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_http_external_functions_integration.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_iii_registration_dedup.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_init_api.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_invocation_exception.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_logger_function_ids.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_logger_otel.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_middleware.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_payload.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_pubsub.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_queue_integration.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_rbac_workers.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_register_function_args.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_span_ops.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_state.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_stream_models.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_streams.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_streams_runtime_annotations.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_sync_api.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_telemetry.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_telemetry_exporters.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_telemetry_types.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_trace_helpers.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_trigger_metadata.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_trigger_registration_error.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_utils.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_worker_metadata.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_worker_metrics.py +0 -0
- {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iii-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.19.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,7 +14,7 @@ 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.
|
|
17
|
+
Requires-Dist: iii-observability==0.19.0.dev1
|
|
18
18
|
Requires-Dist: opentelemetry-api>=1.25
|
|
19
19
|
Requires-Dist: pydantic>=2.0
|
|
20
20
|
Requires-Dist: websockets>=12.0
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "iii-sdk"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.19.0.dev1"
|
|
8
8
|
description = "III SDK for Python"
|
|
9
9
|
authors = [{ name = "III" }]
|
|
10
10
|
license = { text = "Apache-2.0" }
|
|
@@ -23,7 +23,7 @@ dependencies = [
|
|
|
23
23
|
"websockets>=12.0",
|
|
24
24
|
"pydantic>=2.0",
|
|
25
25
|
"opentelemetry-api>=1.25",
|
|
26
|
-
"iii-observability==0.
|
|
26
|
+
"iii-observability==0.19.0.dev1",
|
|
27
27
|
]
|
|
28
28
|
|
|
29
29
|
[project.urls]
|
|
@@ -438,6 +438,8 @@ class III:
|
|
|
438
438
|
)
|
|
439
439
|
elif msg_type == MessageType.REGISTER_TRIGGER.value:
|
|
440
440
|
asyncio.create_task(self._handle_trigger_registration(data))
|
|
441
|
+
elif msg_type == MessageType.UNREGISTER_TRIGGER.value:
|
|
442
|
+
asyncio.create_task(self._handle_trigger_unregistration(data))
|
|
441
443
|
elif msg_type == MessageType.TRIGGER_REGISTRATION_RESULT.value:
|
|
442
444
|
self._handle_trigger_registration_result(data)
|
|
443
445
|
elif msg_type == MessageType.WORKER_REGISTERED.value:
|
|
@@ -718,6 +720,32 @@ class III:
|
|
|
718
720
|
message,
|
|
719
721
|
)
|
|
720
722
|
|
|
723
|
+
async def _handle_trigger_unregistration(self, data: dict[str, Any]) -> None:
|
|
724
|
+
trigger_type_id = data.get("trigger_type")
|
|
725
|
+
if not trigger_type_id:
|
|
726
|
+
return
|
|
727
|
+
|
|
728
|
+
handler_data = self._trigger_types.get(trigger_type_id)
|
|
729
|
+
if not handler_data:
|
|
730
|
+
return
|
|
731
|
+
|
|
732
|
+
trigger_id = data.get("id", "")
|
|
733
|
+
function_id = data.get("function_id", "")
|
|
734
|
+
config = data.get("config")
|
|
735
|
+
metadata = data.get("metadata")
|
|
736
|
+
|
|
737
|
+
try:
|
|
738
|
+
await handler_data.handler.unregister_trigger(
|
|
739
|
+
TriggerConfig(
|
|
740
|
+
id=trigger_id,
|
|
741
|
+
function_id=function_id,
|
|
742
|
+
config=config,
|
|
743
|
+
metadata=metadata,
|
|
744
|
+
)
|
|
745
|
+
)
|
|
746
|
+
except Exception:
|
|
747
|
+
log.exception(f"Error unregistering trigger {trigger_id}")
|
|
748
|
+
|
|
721
749
|
# Connection state management
|
|
722
750
|
|
|
723
751
|
def _set_connection_state(self, state: IIIConnectionState) -> None:
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""Integration tests for custom trigger type lifecycle across two workers."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import time
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from iii import TriggerAction, TriggerConfig, TriggerHandler, register_worker
|
|
10
|
+
from iii.iii import III
|
|
11
|
+
|
|
12
|
+
ENGINE_WS_URL = os.environ.get("III_URL", "ws://localhost:49199")
|
|
13
|
+
|
|
14
|
+
TRIGGER_TYPE_ID = "test.tt-lifecycle.python"
|
|
15
|
+
CONSUMER_FN = "test.tt-lifecycle.python.consumer"
|
|
16
|
+
FIRE_FN = "test.tt-lifecycle.python.fire"
|
|
17
|
+
TRIGGER_CONFIG = {"tag": "test"}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class LifecycleTriggerHandler(TriggerHandler[Any]):
|
|
21
|
+
def __init__(self) -> None:
|
|
22
|
+
self.bindings: dict[str, TriggerConfig[Any]] = {}
|
|
23
|
+
self.register_calls: list[TriggerConfig[Any]] = []
|
|
24
|
+
self.unregister_calls: list[TriggerConfig[Any]] = []
|
|
25
|
+
|
|
26
|
+
async def register_trigger(self, config: TriggerConfig[Any]) -> None:
|
|
27
|
+
self.bindings[config.id] = config
|
|
28
|
+
self.register_calls.append(config)
|
|
29
|
+
|
|
30
|
+
async def unregister_trigger(self, config: TriggerConfig[Any]) -> None:
|
|
31
|
+
stored = self.bindings.pop(config.id, None)
|
|
32
|
+
self.unregister_calls.append(stored if stored is not None else config)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _wait() -> None:
|
|
36
|
+
time.sleep(0.4)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _create_provider(handler: LifecycleTriggerHandler) -> III:
|
|
40
|
+
client = register_worker(ENGINE_WS_URL)
|
|
41
|
+
client._wait_until_connected()
|
|
42
|
+
time.sleep(0.3)
|
|
43
|
+
|
|
44
|
+
client.register_trigger_type(
|
|
45
|
+
{"id": TRIGGER_TYPE_ID, "description": "Python SDK lifecycle test trigger type"},
|
|
46
|
+
handler,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def fire_handler(payload: dict[str, Any]) -> dict[str, int]:
|
|
50
|
+
for binding in list(handler.bindings.values()):
|
|
51
|
+
client.trigger(
|
|
52
|
+
{
|
|
53
|
+
"function_id": binding.function_id,
|
|
54
|
+
"payload": payload,
|
|
55
|
+
"action": TriggerAction.Void(),
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
return {"fired": len(handler.bindings)}
|
|
59
|
+
|
|
60
|
+
client.register_function(FIRE_FN, fire_handler)
|
|
61
|
+
return client
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _create_consumer(handler_calls: list[Any]) -> III:
|
|
65
|
+
client = register_worker(ENGINE_WS_URL)
|
|
66
|
+
client._wait_until_connected()
|
|
67
|
+
time.sleep(0.3)
|
|
68
|
+
|
|
69
|
+
def consumer_handler(payload: dict[str, Any]) -> dict[str, Any]:
|
|
70
|
+
handler_calls.append(payload)
|
|
71
|
+
return {"ok": True, "payload": payload}
|
|
72
|
+
|
|
73
|
+
client.register_function(CONSUMER_FN, consumer_handler)
|
|
74
|
+
client.register_trigger(
|
|
75
|
+
{
|
|
76
|
+
"type": TRIGGER_TYPE_ID,
|
|
77
|
+
"function_id": CONSUMER_FN,
|
|
78
|
+
"config": TRIGGER_CONFIG,
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
_wait()
|
|
82
|
+
return client
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@pytest.fixture
|
|
86
|
+
def trigger_handler() -> LifecycleTriggerHandler:
|
|
87
|
+
return LifecycleTriggerHandler()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_fire_invokes_bound_function(trigger_handler: LifecycleTriggerHandler) -> None:
|
|
91
|
+
provider = _create_provider(trigger_handler)
|
|
92
|
+
handler_calls: list[Any] = []
|
|
93
|
+
consumer = _create_consumer(handler_calls)
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
assert len(trigger_handler.register_calls) == 1
|
|
97
|
+
assert trigger_handler.register_calls[0].function_id == CONSUMER_FN
|
|
98
|
+
|
|
99
|
+
provider.trigger({"function_id": FIRE_FN, "payload": {"n": 1}})
|
|
100
|
+
_wait()
|
|
101
|
+
|
|
102
|
+
assert len(handler_calls) == 1
|
|
103
|
+
assert handler_calls[0]["n"] == 1
|
|
104
|
+
finally:
|
|
105
|
+
consumer.shutdown()
|
|
106
|
+
provider.shutdown()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_provider_reconnect_rebinds_trigger(trigger_handler: LifecycleTriggerHandler) -> None:
|
|
110
|
+
provider = _create_provider(trigger_handler)
|
|
111
|
+
handler_calls: list[Any] = []
|
|
112
|
+
consumer = _create_consumer(handler_calls)
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
bound_trigger_id = trigger_handler.register_calls[0].id
|
|
116
|
+
trigger_handler.register_calls.clear()
|
|
117
|
+
|
|
118
|
+
provider.shutdown()
|
|
119
|
+
_wait()
|
|
120
|
+
|
|
121
|
+
provider = _create_provider(trigger_handler)
|
|
122
|
+
_wait()
|
|
123
|
+
|
|
124
|
+
assert len(trigger_handler.register_calls) == 1
|
|
125
|
+
assert trigger_handler.register_calls[0].id == bound_trigger_id
|
|
126
|
+
assert trigger_handler.register_calls[0].function_id == CONSUMER_FN
|
|
127
|
+
|
|
128
|
+
handler_calls.clear()
|
|
129
|
+
provider.trigger({"function_id": FIRE_FN, "payload": {"n": 2}})
|
|
130
|
+
_wait()
|
|
131
|
+
|
|
132
|
+
assert len(handler_calls) == 1
|
|
133
|
+
assert handler_calls[0]["n"] == 2
|
|
134
|
+
finally:
|
|
135
|
+
consumer.shutdown()
|
|
136
|
+
provider.shutdown()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_consumer_disconnect_invokes_unregister_trigger(
|
|
140
|
+
trigger_handler: LifecycleTriggerHandler,
|
|
141
|
+
) -> None:
|
|
142
|
+
provider = _create_provider(trigger_handler)
|
|
143
|
+
handler_calls: list[Any] = []
|
|
144
|
+
consumer = _create_consumer(handler_calls)
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
trigger_handler.unregister_calls.clear()
|
|
148
|
+
|
|
149
|
+
consumer.shutdown()
|
|
150
|
+
_wait()
|
|
151
|
+
|
|
152
|
+
assert len(trigger_handler.unregister_calls) == 1
|
|
153
|
+
unreg = trigger_handler.unregister_calls[0]
|
|
154
|
+
assert unreg.function_id == CONSUMER_FN
|
|
155
|
+
assert unreg.config == TRIGGER_CONFIG
|
|
156
|
+
finally:
|
|
157
|
+
provider.shutdown()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_http_external_functions_integration.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|