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.
Files changed (61) hide show
  1. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/PKG-INFO +2 -2
  2. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/pyproject.toml +2 -2
  3. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/iii.py +28 -0
  4. iii_sdk-0.19.0.dev1/tests/test_trigger_type_lifecycle.py +157 -0
  5. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/.gitignore +0 -0
  6. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/README.md +0 -0
  7. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/__init__.py +0 -0
  8. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/channels.py +0 -0
  9. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/errors.py +0 -0
  10. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/format_utils.py +0 -0
  11. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/helpers.py +0 -0
  12. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/iii_constants.py +0 -0
  13. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/iii_types.py +0 -0
  14. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/otel_worker_gauges.py +0 -0
  15. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/state.py +0 -0
  16. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/stream.py +0 -0
  17. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/triggers.py +0 -0
  18. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/types.py +0 -0
  19. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/utils.py +0 -0
  20. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/src/iii/worker_metrics.py +0 -0
  21. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/conftest.py +0 -0
  22. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_api_triggers.py +0 -0
  23. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_async_api.py +0 -0
  24. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_baggage_span_processor.py +0 -0
  25. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_bridge.py +0 -0
  26. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_channel_close_delay.py +0 -0
  27. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_context_propagation.py +0 -0
  28. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_data_channels.py +0 -0
  29. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_errors.py +0 -0
  30. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_format_utils.py +0 -0
  31. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_healthcheck.py +0 -0
  32. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_helpers.py +0 -0
  33. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_hold_process.py +0 -0
  34. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_http_external_functions_integration.py +0 -0
  35. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_iii_registration_dedup.py +0 -0
  36. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_init_api.py +0 -0
  37. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_invocation_exception.py +0 -0
  38. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_logger_function_ids.py +0 -0
  39. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_logger_otel.py +0 -0
  40. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_middleware.py +0 -0
  41. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_payload.py +0 -0
  42. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_pubsub.py +0 -0
  43. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_queue_integration.py +0 -0
  44. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_rbac_workers.py +0 -0
  45. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_register_function_args.py +0 -0
  46. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_span_ops.py +0 -0
  47. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_state.py +0 -0
  48. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_stream_models.py +0 -0
  49. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_streams.py +0 -0
  50. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_streams_runtime_annotations.py +0 -0
  51. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_sync_api.py +0 -0
  52. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_telemetry.py +0 -0
  53. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_telemetry_exporters.py +0 -0
  54. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_telemetry_types.py +0 -0
  55. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_trace_helpers.py +0 -0
  56. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_trigger_metadata.py +0 -0
  57. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_trigger_registration_error.py +0 -0
  58. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_utils.py +0 -0
  59. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_worker_metadata.py +0 -0
  60. {iii_sdk-0.18.0.dev1 → iii_sdk-0.19.0.dev1}/tests/test_worker_metrics.py +0 -0
  61. {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.18.0.dev1
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.18.0.dev1
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.18.0.dev1"
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.18.0.dev1",
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