iii-sdk 0.11.3.dev2__tar.gz → 0.11.4__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.11.3.dev2 → iii_sdk-0.11.4}/PKG-INFO +8 -8
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/README.md +5 -5
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/pyproject.toml +3 -3
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/src/iii/__init__.py +0 -2
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/src/iii/iii.py +48 -262
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/src/iii/stream.py +60 -4
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/src/iii/types.py +1 -17
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_api_triggers.py +46 -10
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_async_api.py +0 -18
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_bridge.py +24 -18
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_context_propagation.py +18 -14
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_data_channels.py +37 -29
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_healthcheck.py +1 -1
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_http_external_functions_integration.py +30 -16
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_iii_registration_dedup.py +2 -2
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_middleware.py +10 -10
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_pubsub.py +3 -3
- iii_sdk-0.11.4/tests/test_queue_integration.py +363 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_rbac_workers.py +30 -17
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_register_function_args.py +57 -71
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_state.py +126 -6
- iii_sdk-0.11.4/tests/test_stream_models.py +73 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_streams.py +175 -7
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_sync_api.py +13 -11
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/uv.lock +1 -1
- iii_sdk-0.11.3.dev2/tests/test_queue_integration.py +0 -136
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/.gitignore +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/src/iii/channels.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/src/iii/errors.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/src/iii/format_utils.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/src/iii/iii_constants.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/src/iii/iii_types.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/src/iii/logger.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/src/iii/otel_worker_gauges.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/src/iii/state.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/src/iii/telemetry.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/src/iii/telemetry_exporters.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/src/iii/telemetry_types.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/src/iii/triggers.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/src/iii/utils.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/src/iii/worker_metrics.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/conftest.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_channel_close_delay.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_errors.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_format_utils.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_hold_process.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_init_api.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_invocation_exception.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_logger_function_ids.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_logger_otel.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_streams_runtime_annotations.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_telemetry.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_telemetry_exporters.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_telemetry_types.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_trace_helpers.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_trigger_metadata.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_utils.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_worker_metadata.py +0 -0
- {iii_sdk-0.11.3.dev2 → iii_sdk-0.11.4}/tests/test_worker_metrics.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iii-sdk
|
|
3
|
-
Version: 0.11.
|
|
3
|
+
Version: 0.11.4
|
|
4
4
|
Summary: III SDK for Python
|
|
5
|
-
Project-URL: Homepage, https://github.com/iii-hq/
|
|
6
|
-
Project-URL: Repository, https://github.com/iii-hq/
|
|
5
|
+
Project-URL: Homepage, https://github.com/iii-hq/iii
|
|
6
|
+
Project-URL: Repository, https://github.com/iii-hq/iii
|
|
7
7
|
Author: III
|
|
8
8
|
License: Apache-2.0
|
|
9
9
|
Keywords: iii,sdk
|
|
@@ -53,7 +53,7 @@ iii = register_worker("ws://localhost:49134")
|
|
|
53
53
|
def greet(data):
|
|
54
54
|
return {"message": f"Hello, {data['name']}!"}
|
|
55
55
|
|
|
56
|
-
iii.register_function(
|
|
56
|
+
iii.register_function("greet", greet)
|
|
57
57
|
|
|
58
58
|
iii.register_trigger({
|
|
59
59
|
"type": "http",
|
|
@@ -72,7 +72,7 @@ print(result) # {"message": "Hello, world!"}
|
|
|
72
72
|
| Operation | Signature | Description |
|
|
73
73
|
| ------------------------ | ------------------------------------------------- | ------------------------------------------------------ |
|
|
74
74
|
| Initialize | `register_worker(url, options?)` | Create an SDK instance and auto-connect |
|
|
75
|
-
| Register function | `iii.register_function(
|
|
75
|
+
| Register function | `iii.register_function(id, handler)` | Register a function that can be invoked by name |
|
|
76
76
|
| Register trigger | `iii.register_trigger({"type": ..., "function_id": ..., "config": ...})` | Bind a trigger (HTTP, cron, queue, etc.) to a function |
|
|
77
77
|
| Invoke (await result) | `iii.trigger({"function_id": id, "payload": data})` | Invoke a function and wait for the result |
|
|
78
78
|
| Invoke (fire-and-forget) | `iii.trigger({"function_id": id, ..., "action": TriggerAction.Void()})` | Fire-and-forget |
|
|
@@ -86,7 +86,7 @@ print(result) # {"message": "Hello, world!"}
|
|
|
86
86
|
def create_order(data):
|
|
87
87
|
return {"status_code": 201, "body": {"id": "123", "item": data["body"]["item"]}}
|
|
88
88
|
|
|
89
|
-
iii.register_function(
|
|
89
|
+
iii.register_function("orders::create", create_order)
|
|
90
90
|
```
|
|
91
91
|
|
|
92
92
|
### Registering Triggers
|
|
@@ -94,7 +94,7 @@ iii.register_function({"id": "orders.create"}, create_order)
|
|
|
94
94
|
```python
|
|
95
95
|
iii.register_trigger({
|
|
96
96
|
"type": "http",
|
|
97
|
-
"function_id": "orders
|
|
97
|
+
"function_id": "orders::create",
|
|
98
98
|
"config": {"api_path": "/orders", "http_method": "POST"},
|
|
99
99
|
})
|
|
100
100
|
```
|
|
@@ -102,7 +102,7 @@ iii.register_trigger({
|
|
|
102
102
|
### Invoking Functions
|
|
103
103
|
|
|
104
104
|
```python
|
|
105
|
-
result = iii.trigger({"function_id": "orders
|
|
105
|
+
result = iii.trigger({"function_id": "orders::create", "payload": {"body": {"item": "widget"}}})
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
## Modules
|
|
@@ -22,7 +22,7 @@ iii = register_worker("ws://localhost:49134")
|
|
|
22
22
|
def greet(data):
|
|
23
23
|
return {"message": f"Hello, {data['name']}!"}
|
|
24
24
|
|
|
25
|
-
iii.register_function(
|
|
25
|
+
iii.register_function("greet", greet)
|
|
26
26
|
|
|
27
27
|
iii.register_trigger({
|
|
28
28
|
"type": "http",
|
|
@@ -41,7 +41,7 @@ print(result) # {"message": "Hello, world!"}
|
|
|
41
41
|
| Operation | Signature | Description |
|
|
42
42
|
| ------------------------ | ------------------------------------------------- | ------------------------------------------------------ |
|
|
43
43
|
| Initialize | `register_worker(url, options?)` | Create an SDK instance and auto-connect |
|
|
44
|
-
| Register function | `iii.register_function(
|
|
44
|
+
| Register function | `iii.register_function(id, handler)` | Register a function that can be invoked by name |
|
|
45
45
|
| Register trigger | `iii.register_trigger({"type": ..., "function_id": ..., "config": ...})` | Bind a trigger (HTTP, cron, queue, etc.) to a function |
|
|
46
46
|
| Invoke (await result) | `iii.trigger({"function_id": id, "payload": data})` | Invoke a function and wait for the result |
|
|
47
47
|
| Invoke (fire-and-forget) | `iii.trigger({"function_id": id, ..., "action": TriggerAction.Void()})` | Fire-and-forget |
|
|
@@ -55,7 +55,7 @@ print(result) # {"message": "Hello, world!"}
|
|
|
55
55
|
def create_order(data):
|
|
56
56
|
return {"status_code": 201, "body": {"id": "123", "item": data["body"]["item"]}}
|
|
57
57
|
|
|
58
|
-
iii.register_function(
|
|
58
|
+
iii.register_function("orders::create", create_order)
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
### Registering Triggers
|
|
@@ -63,7 +63,7 @@ iii.register_function({"id": "orders.create"}, create_order)
|
|
|
63
63
|
```python
|
|
64
64
|
iii.register_trigger({
|
|
65
65
|
"type": "http",
|
|
66
|
-
"function_id": "orders
|
|
66
|
+
"function_id": "orders::create",
|
|
67
67
|
"config": {"api_path": "/orders", "http_method": "POST"},
|
|
68
68
|
})
|
|
69
69
|
```
|
|
@@ -71,7 +71,7 @@ iii.register_trigger({
|
|
|
71
71
|
### Invoking Functions
|
|
72
72
|
|
|
73
73
|
```python
|
|
74
|
-
result = iii.trigger({"function_id": "orders
|
|
74
|
+
result = iii.trigger({"function_id": "orders::create", "payload": {"body": {"item": "widget"}}})
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
## Modules
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "iii-sdk"
|
|
7
|
-
version = "0.11.
|
|
7
|
+
version = "0.11.4"
|
|
8
8
|
description = "III SDK for Python"
|
|
9
9
|
authors = [{ name = "III" }]
|
|
10
10
|
license = { text = "Apache-2.0" }
|
|
@@ -27,8 +27,8 @@ dependencies = [
|
|
|
27
27
|
]
|
|
28
28
|
|
|
29
29
|
[project.urls]
|
|
30
|
-
Homepage = "https://github.com/iii-hq/
|
|
31
|
-
Repository = "https://github.com/iii-hq/
|
|
30
|
+
Homepage = "https://github.com/iii-hq/iii"
|
|
31
|
+
Repository = "https://github.com/iii-hq/iii"
|
|
32
32
|
|
|
33
33
|
[project.optional-dependencies]
|
|
34
34
|
dev = [
|
|
@@ -21,7 +21,6 @@ from .iii_types import (
|
|
|
21
21
|
OnTriggerTypeRegistrationInput,
|
|
22
22
|
OnTriggerTypeRegistrationResult,
|
|
23
23
|
RegisterFunctionFormat,
|
|
24
|
-
RegisterFunctionInput,
|
|
25
24
|
RegisterFunctionMessage,
|
|
26
25
|
RegisterServiceInput,
|
|
27
26
|
RegisterTriggerInput,
|
|
@@ -92,7 +91,6 @@ __all__ = [
|
|
|
92
91
|
"HttpInvocationConfig",
|
|
93
92
|
"MessageType",
|
|
94
93
|
"RegisterFunctionFormat",
|
|
95
|
-
"RegisterFunctionInput",
|
|
96
94
|
"RegisterFunctionMessage",
|
|
97
95
|
"RegisterServiceInput",
|
|
98
96
|
"RegisterTriggerInput",
|
|
@@ -27,7 +27,6 @@ from .iii_constants import (
|
|
|
27
27
|
InitOptions,
|
|
28
28
|
)
|
|
29
29
|
from .iii_types import (
|
|
30
|
-
FunctionInfo,
|
|
31
30
|
HttpInvocationConfig,
|
|
32
31
|
InvocationResultMessage,
|
|
33
32
|
InvokeFunctionMessage,
|
|
@@ -44,13 +43,10 @@ from .iii_types import (
|
|
|
44
43
|
StreamChannelRef,
|
|
45
44
|
TriggerActionEnqueue,
|
|
46
45
|
TriggerActionVoid,
|
|
47
|
-
TriggerInfo,
|
|
48
46
|
TriggerRequest,
|
|
49
|
-
TriggerTypeInfo,
|
|
50
47
|
UnregisterFunctionMessage,
|
|
51
48
|
UnregisterTriggerMessage,
|
|
52
49
|
UnregisterTriggerTypeMessage,
|
|
53
|
-
WorkerInfo,
|
|
54
50
|
)
|
|
55
51
|
from .stream import (
|
|
56
52
|
IStream,
|
|
@@ -128,11 +124,6 @@ class III:
|
|
|
128
124
|
self._reconnect_task: asyncio.Task[None] | None = None
|
|
129
125
|
self._running = False
|
|
130
126
|
self._receiver_task: asyncio.Task[None] | None = None
|
|
131
|
-
self._functions_available_callbacks: set[
|
|
132
|
-
Callable[[list[FunctionInfo]], None]
|
|
133
|
-
] = set()
|
|
134
|
-
self._functions_available_trigger: Trigger | None = None
|
|
135
|
-
self._functions_available_function_id: str | None = None
|
|
136
127
|
self._reconnection_config = (
|
|
137
128
|
self._options.reconnection_config or DEFAULT_RECONNECTION_CONFIG
|
|
138
129
|
)
|
|
@@ -796,7 +787,7 @@ class III:
|
|
|
796
787
|
|
|
797
788
|
def register_function(
|
|
798
789
|
self,
|
|
799
|
-
|
|
790
|
+
function_id: str,
|
|
800
791
|
handler_or_invocation: RemoteFunctionHandler | HttpInvocationConfig,
|
|
801
792
|
*,
|
|
802
793
|
description: str | None = None,
|
|
@@ -814,27 +805,28 @@ class III:
|
|
|
814
805
|
block the event loop. Each handler receives a single ``data``
|
|
815
806
|
argument containing the trigger payload.
|
|
816
807
|
|
|
817
|
-
When ``func_or_id`` is a ``str``, the simplified API is used:
|
|
818
808
|
``request_format`` and ``response_format`` are auto-extracted
|
|
819
|
-
from the handler's type hints when
|
|
809
|
+
from the handler's type hints when omitted or passed as ``None``
|
|
810
|
+
(the default). To opt out of auto-extraction, pass an explicit
|
|
811
|
+
schema (``RegisterFunctionFormat`` or ``dict``). This behavior
|
|
812
|
+
is Python-specific -- the Node SDK does not auto-extract from TS
|
|
813
|
+
types, because TypeScript types are erased at runtime.
|
|
820
814
|
|
|
821
815
|
Args:
|
|
822
|
-
|
|
823
|
-
a plain string function ID. When a string is passed, use
|
|
824
|
-
keyword arguments for ``description``, ``metadata``,
|
|
825
|
-
``request_format``, and ``response_format``.
|
|
816
|
+
function_id: Unique string identifier for the function.
|
|
826
817
|
handler_or_invocation: A callable handler or
|
|
827
818
|
``HttpInvocationConfig``. Callable handlers receive one
|
|
828
819
|
positional argument (``data`` -- the trigger payload) and
|
|
829
820
|
may return a value.
|
|
830
|
-
description: Human-readable description
|
|
831
|
-
metadata: Arbitrary metadata
|
|
832
|
-
request_format: Schema describing expected input
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
821
|
+
description: Human-readable description.
|
|
822
|
+
metadata: Arbitrary metadata.
|
|
823
|
+
request_format: Schema describing expected input. When
|
|
824
|
+
``None`` (default), auto-extracted from the handler's
|
|
825
|
+
first-parameter type hint. Pass an explicit schema to
|
|
826
|
+
override; there is no way to register with no schema
|
|
827
|
+
when the handler is typed.
|
|
828
|
+
response_format: Schema describing expected output. Same
|
|
829
|
+
auto-extraction semantics as ``request_format``.
|
|
838
830
|
|
|
839
831
|
Returns:
|
|
840
832
|
A ``FunctionRef`` with an ``id`` attribute and an
|
|
@@ -842,14 +834,15 @@ class III:
|
|
|
842
834
|
the function from the engine.
|
|
843
835
|
|
|
844
836
|
Raises:
|
|
845
|
-
|
|
846
|
-
|
|
837
|
+
TypeError: If ``function_id`` is not a string, or if
|
|
838
|
+
``handler_or_invocation`` is not callable or
|
|
847
839
|
``HttpInvocationConfig``.
|
|
840
|
+
ValueError: If ``function_id`` is empty or already registered.
|
|
848
841
|
|
|
849
842
|
Examples:
|
|
850
843
|
>>> def greet(data):
|
|
851
844
|
... return {'message': f"Hello, {data['name']}!"}
|
|
852
|
-
>>> fn = iii.register_function(
|
|
845
|
+
>>> fn = iii.register_function("greet", greet, description="Greets a user")
|
|
853
846
|
>>> fn.unregister()
|
|
854
847
|
|
|
855
848
|
>>> from pydantic import BaseModel
|
|
@@ -861,31 +854,29 @@ class III:
|
|
|
861
854
|
... return GreetOutput(message=f"Hello, {data.name}!")
|
|
862
855
|
>>> fn = iii.register_function("greet", greet, description="Greets a user")
|
|
863
856
|
"""
|
|
864
|
-
if isinstance(
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
handler_or_invocation if callable(handler_or_invocation) else None
|
|
868
|
-
)
|
|
869
|
-
if request_format is None and handler_for_extraction is not None:
|
|
870
|
-
request_format = extract_request_format(handler_for_extraction)
|
|
871
|
-
if response_format is None and handler_for_extraction is not None:
|
|
872
|
-
response_format = extract_response_format(handler_for_extraction)
|
|
873
|
-
func = RegisterFunctionInput(
|
|
874
|
-
id=func_or_id,
|
|
875
|
-
description=description,
|
|
876
|
-
metadata=metadata,
|
|
877
|
-
request_format=request_format,
|
|
878
|
-
response_format=response_format,
|
|
857
|
+
if not isinstance(function_id, str):
|
|
858
|
+
raise TypeError(
|
|
859
|
+
f"function_id must be str, got {type(function_id).__name__}"
|
|
879
860
|
)
|
|
880
|
-
|
|
881
|
-
func = RegisterFunctionInput(**func_or_id)
|
|
882
|
-
else:
|
|
883
|
-
func = func_or_id
|
|
884
|
-
|
|
885
|
-
if not func.id or not func.id.strip():
|
|
861
|
+
if not function_id or not function_id.strip():
|
|
886
862
|
raise ValueError("id is required")
|
|
887
|
-
if
|
|
888
|
-
raise ValueError(f"function id '{
|
|
863
|
+
if function_id in self._functions:
|
|
864
|
+
raise ValueError(f"function id '{function_id}' already registered")
|
|
865
|
+
|
|
866
|
+
handler_for_extraction = (
|
|
867
|
+
handler_or_invocation if callable(handler_or_invocation) else None
|
|
868
|
+
)
|
|
869
|
+
if request_format is None and handler_for_extraction is not None:
|
|
870
|
+
request_format = extract_request_format(handler_for_extraction)
|
|
871
|
+
if response_format is None and handler_for_extraction is not None:
|
|
872
|
+
response_format = extract_response_format(handler_for_extraction)
|
|
873
|
+
func = RegisterFunctionInput(
|
|
874
|
+
id=function_id,
|
|
875
|
+
description=description,
|
|
876
|
+
metadata=metadata,
|
|
877
|
+
request_format=request_format,
|
|
878
|
+
response_format=response_format,
|
|
879
|
+
)
|
|
889
880
|
|
|
890
881
|
if isinstance(handler_or_invocation, HttpInvocationConfig):
|
|
891
882
|
msg = RegisterFunctionMessage(
|
|
@@ -1096,148 +1087,6 @@ class III:
|
|
|
1096
1087
|
invocation_id=invocation_id,
|
|
1097
1088
|
)
|
|
1098
1089
|
|
|
1099
|
-
def list_functions(self) -> list[FunctionInfo]:
|
|
1100
|
-
"""List all functions registered with the engine across all workers.
|
|
1101
|
-
|
|
1102
|
-
Returns:
|
|
1103
|
-
A list of ``FunctionInfo`` objects describing each function.
|
|
1104
|
-
|
|
1105
|
-
Examples:
|
|
1106
|
-
>>> for fn in iii.list_functions():
|
|
1107
|
-
... print(fn.function_id, fn.description)
|
|
1108
|
-
"""
|
|
1109
|
-
return self._run_on_loop(self.list_functions_async())
|
|
1110
|
-
|
|
1111
|
-
async def list_functions_async(self) -> list[FunctionInfo]:
|
|
1112
|
-
"""List all functions registered with the engine across all workers.
|
|
1113
|
-
|
|
1114
|
-
Returns:
|
|
1115
|
-
A list of ``FunctionInfo`` objects describing each function.
|
|
1116
|
-
|
|
1117
|
-
Examples:
|
|
1118
|
-
>>> for fn in await iii.list_functions_async():
|
|
1119
|
-
... print(fn.function_id, fn.description)
|
|
1120
|
-
"""
|
|
1121
|
-
result = await self.trigger_async(
|
|
1122
|
-
{"function_id": "engine::functions::list", "payload": {}}
|
|
1123
|
-
)
|
|
1124
|
-
functions_data = result.get("functions", [])
|
|
1125
|
-
return [FunctionInfo(**f) for f in functions_data]
|
|
1126
|
-
|
|
1127
|
-
def list_workers(self) -> list[WorkerInfo]:
|
|
1128
|
-
"""List all workers currently connected to the engine.
|
|
1129
|
-
|
|
1130
|
-
Returns:
|
|
1131
|
-
A list of ``WorkerInfo`` objects with worker metadata.
|
|
1132
|
-
|
|
1133
|
-
Examples:
|
|
1134
|
-
>>> for w in iii.list_workers():
|
|
1135
|
-
... print(w.name, w.worker_id)
|
|
1136
|
-
"""
|
|
1137
|
-
return self._run_on_loop(self.list_workers_async())
|
|
1138
|
-
|
|
1139
|
-
async def list_workers_async(self) -> list[WorkerInfo]:
|
|
1140
|
-
"""List all workers currently connected to the engine.
|
|
1141
|
-
|
|
1142
|
-
Returns:
|
|
1143
|
-
A list of ``WorkerInfo`` objects with worker metadata.
|
|
1144
|
-
|
|
1145
|
-
Examples:
|
|
1146
|
-
>>> for w in await iii.list_workers_async():
|
|
1147
|
-
... print(w.name, w.worker_id)
|
|
1148
|
-
"""
|
|
1149
|
-
result = await self.trigger_async(
|
|
1150
|
-
{"function_id": "engine::workers::list", "payload": {}}
|
|
1151
|
-
)
|
|
1152
|
-
workers_data = result.get("workers", [])
|
|
1153
|
-
return [WorkerInfo(**w) for w in workers_data]
|
|
1154
|
-
|
|
1155
|
-
def list_triggers(self, include_internal: bool = False) -> list[TriggerInfo]:
|
|
1156
|
-
"""List all triggers registered with the engine.
|
|
1157
|
-
|
|
1158
|
-
Args:
|
|
1159
|
-
include_internal: If ``True``, include engine-internal triggers
|
|
1160
|
-
(e.g. ``functions-available``). Defaults to ``False``.
|
|
1161
|
-
|
|
1162
|
-
Returns:
|
|
1163
|
-
A list of ``TriggerInfo`` objects.
|
|
1164
|
-
|
|
1165
|
-
Examples:
|
|
1166
|
-
>>> triggers = iii.list_triggers()
|
|
1167
|
-
>>> internal = iii.list_triggers(include_internal=True)
|
|
1168
|
-
"""
|
|
1169
|
-
return self._run_on_loop(self.list_triggers_async(include_internal))
|
|
1170
|
-
|
|
1171
|
-
async def list_triggers_async(
|
|
1172
|
-
self, include_internal: bool = False
|
|
1173
|
-
) -> list[TriggerInfo]:
|
|
1174
|
-
"""List all triggers registered with the engine.
|
|
1175
|
-
|
|
1176
|
-
Args:
|
|
1177
|
-
include_internal: If ``True``, include engine-internal triggers
|
|
1178
|
-
(e.g. ``functions-available``). Defaults to ``False``.
|
|
1179
|
-
|
|
1180
|
-
Returns:
|
|
1181
|
-
A list of ``TriggerInfo`` objects.
|
|
1182
|
-
|
|
1183
|
-
Examples:
|
|
1184
|
-
>>> triggers = await iii.list_triggers_async()
|
|
1185
|
-
>>> internal = await iii.list_triggers_async(include_internal=True)
|
|
1186
|
-
"""
|
|
1187
|
-
result = await self.trigger_async(
|
|
1188
|
-
{
|
|
1189
|
-
"function_id": "engine::triggers::list",
|
|
1190
|
-
"payload": {"include_internal": include_internal},
|
|
1191
|
-
}
|
|
1192
|
-
)
|
|
1193
|
-
triggers_data = result.get("triggers", [])
|
|
1194
|
-
return [TriggerInfo(**t) for t in triggers_data]
|
|
1195
|
-
|
|
1196
|
-
def list_trigger_types(
|
|
1197
|
-
self, include_internal: bool = False
|
|
1198
|
-
) -> list[TriggerTypeInfo]:
|
|
1199
|
-
"""List all trigger types registered with the engine.
|
|
1200
|
-
|
|
1201
|
-
Args:
|
|
1202
|
-
include_internal: If ``True``, include engine-internal trigger
|
|
1203
|
-
types (e.g. ``engine::functions-available``). Defaults to ``False``.
|
|
1204
|
-
|
|
1205
|
-
Returns:
|
|
1206
|
-
A list of ``TriggerTypeInfo`` objects with ``trigger_request_format``
|
|
1207
|
-
and ``call_request_format`` schemas.
|
|
1208
|
-
|
|
1209
|
-
Examples:
|
|
1210
|
-
>>> trigger_types = iii.list_trigger_types()
|
|
1211
|
-
>>> for tt in trigger_types:
|
|
1212
|
-
... print(tt.id, tt.trigger_request_format)
|
|
1213
|
-
"""
|
|
1214
|
-
return self._run_on_loop(self.list_trigger_types_async(include_internal))
|
|
1215
|
-
|
|
1216
|
-
async def list_trigger_types_async(
|
|
1217
|
-
self, include_internal: bool = False
|
|
1218
|
-
) -> list[TriggerTypeInfo]:
|
|
1219
|
-
"""List all trigger types registered with the engine.
|
|
1220
|
-
|
|
1221
|
-
Args:
|
|
1222
|
-
include_internal: If ``True``, include engine-internal trigger
|
|
1223
|
-
types (e.g. ``engine::functions-available``). Defaults to ``False``.
|
|
1224
|
-
|
|
1225
|
-
Returns:
|
|
1226
|
-
A list of ``TriggerTypeInfo`` objects with ``trigger_request_format``
|
|
1227
|
-
and ``call_request_format`` schemas.
|
|
1228
|
-
|
|
1229
|
-
Examples:
|
|
1230
|
-
>>> trigger_types = await iii.list_trigger_types_async()
|
|
1231
|
-
"""
|
|
1232
|
-
result = await self.trigger_async(
|
|
1233
|
-
{
|
|
1234
|
-
"function_id": "engine::trigger-types::list",
|
|
1235
|
-
"payload": {"include_internal": include_internal},
|
|
1236
|
-
}
|
|
1237
|
-
)
|
|
1238
|
-
types_data = result.get("trigger_types", [])
|
|
1239
|
-
return [TriggerTypeInfo(**t) for t in types_data]
|
|
1240
|
-
|
|
1241
1090
|
def create_channel(self, buffer_size: int | None = None) -> Channel:
|
|
1242
1091
|
"""Create a streaming channel pair for worker-to-worker data transfer.
|
|
1243
1092
|
|
|
@@ -1258,7 +1107,7 @@ class III:
|
|
|
1258
1107
|
|
|
1259
1108
|
Examples:
|
|
1260
1109
|
>>> ch = iii.create_channel()
|
|
1261
|
-
>>> fn = iii.register_function(
|
|
1110
|
+
>>> fn = iii.register_function("producer", producer_handler)
|
|
1262
1111
|
>>> iii.trigger({"function_id": "producer", "payload": {"output": ch.writer_ref}})
|
|
1263
1112
|
"""
|
|
1264
1113
|
return self._run_on_loop(self.create_channel_async(buffer_size))
|
|
@@ -1279,7 +1128,7 @@ class III:
|
|
|
1279
1128
|
|
|
1280
1129
|
Examples:
|
|
1281
1130
|
>>> ch = await iii.create_channel_async()
|
|
1282
|
-
>>> fn = iii.register_function(
|
|
1131
|
+
>>> fn = iii.register_function("producer", producer_handler)
|
|
1283
1132
|
>>> await iii.trigger_async({"function_id": "producer", "payload": {"output": ch.writer_ref}})
|
|
1284
1133
|
"""
|
|
1285
1134
|
result = await self.trigger_async(
|
|
@@ -1341,69 +1190,6 @@ class III:
|
|
|
1341
1190
|
)
|
|
1342
1191
|
asyncio.run_coroutine_threadsafe(self._send(msg), self._loop)
|
|
1343
1192
|
|
|
1344
|
-
def on_functions_available(
|
|
1345
|
-
self, callback: Callable[[list[FunctionInfo]], None]
|
|
1346
|
-
) -> Callable[[], None]:
|
|
1347
|
-
"""Subscribe to function-availability events from the engine.
|
|
1348
|
-
|
|
1349
|
-
The callback fires whenever the set of available functions changes
|
|
1350
|
-
(e.g. a new worker connects or a function is unregistered).
|
|
1351
|
-
|
|
1352
|
-
Args:
|
|
1353
|
-
callback (Callable[[list[FunctionInfo]], None]): Receives the
|
|
1354
|
-
current list of ``FunctionInfo`` objects each time
|
|
1355
|
-
availability changes.
|
|
1356
|
-
|
|
1357
|
-
Returns:
|
|
1358
|
-
A callable that unsubscribes when called. Calling the
|
|
1359
|
-
returned function removes the callback and, if no callbacks
|
|
1360
|
-
remain, tears down the internal trigger.
|
|
1361
|
-
|
|
1362
|
-
Examples:
|
|
1363
|
-
>>> def on_change(functions):
|
|
1364
|
-
... print("Available:", [f.function_id for f in functions])
|
|
1365
|
-
>>> unsub = iii.on_functions_available(on_change)
|
|
1366
|
-
>>> # later ...
|
|
1367
|
-
>>> unsub()
|
|
1368
|
-
"""
|
|
1369
|
-
self._functions_available_callbacks.add(callback)
|
|
1370
|
-
|
|
1371
|
-
if not self._functions_available_trigger:
|
|
1372
|
-
if not self._functions_available_function_id:
|
|
1373
|
-
self._functions_available_function_id = (
|
|
1374
|
-
f"iii.on_functions_available.{uuid.uuid4()}"
|
|
1375
|
-
)
|
|
1376
|
-
|
|
1377
|
-
function_id = self._functions_available_function_id
|
|
1378
|
-
if function_id not in self._functions:
|
|
1379
|
-
|
|
1380
|
-
async def handler(data: dict[str, Any]) -> None:
|
|
1381
|
-
functions_data = data.get("functions", [])
|
|
1382
|
-
functions = [FunctionInfo(**f) for f in functions_data]
|
|
1383
|
-
for cb in list(self._functions_available_callbacks):
|
|
1384
|
-
cb(functions)
|
|
1385
|
-
|
|
1386
|
-
self.register_function({"id": function_id}, handler)
|
|
1387
|
-
|
|
1388
|
-
self._functions_available_trigger = self.register_trigger(
|
|
1389
|
-
{
|
|
1390
|
-
"type": "engine::functions-available",
|
|
1391
|
-
"function_id": function_id,
|
|
1392
|
-
"config": {},
|
|
1393
|
-
}
|
|
1394
|
-
)
|
|
1395
|
-
|
|
1396
|
-
def unsubscribe() -> None:
|
|
1397
|
-
self._functions_available_callbacks.discard(callback)
|
|
1398
|
-
if (
|
|
1399
|
-
len(self._functions_available_callbacks) == 0
|
|
1400
|
-
and self._functions_available_trigger
|
|
1401
|
-
):
|
|
1402
|
-
self._functions_available_trigger.unregister()
|
|
1403
|
-
self._functions_available_trigger = None
|
|
1404
|
-
|
|
1405
|
-
return unsubscribe
|
|
1406
|
-
|
|
1407
1193
|
def create_stream(self, stream_name: str, stream: IStream[Any]) -> None:
|
|
1408
1194
|
"""Register a custom stream implementation, overriding the engine default.
|
|
1409
1195
|
|
|
@@ -1451,12 +1237,12 @@ class III:
|
|
|
1451
1237
|
)
|
|
1452
1238
|
return await stream.list_groups(input_data)
|
|
1453
1239
|
|
|
1454
|
-
self.register_function(
|
|
1455
|
-
self.register_function(
|
|
1456
|
-
self.register_function(
|
|
1457
|
-
self.register_function(
|
|
1240
|
+
self.register_function(f"stream::get({stream_name})", get_handler)
|
|
1241
|
+
self.register_function(f"stream::set({stream_name})", set_handler)
|
|
1242
|
+
self.register_function(f"stream::delete({stream_name})", delete_handler)
|
|
1243
|
+
self.register_function(f"stream::list({stream_name})", list_handler)
|
|
1458
1244
|
self.register_function(
|
|
1459
|
-
|
|
1245
|
+
f"stream::list_groups({stream_name})", list_groups_handler
|
|
1460
1246
|
)
|
|
1461
1247
|
|
|
1462
1248
|
|
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
from abc import ABC, abstractmethod
|
|
6
6
|
from typing import Any, Generic, List, Literal, TypeVar
|
|
7
7
|
|
|
8
|
-
from pydantic import BaseModel
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
9
|
|
|
10
10
|
TData = TypeVar("TData")
|
|
11
11
|
|
|
@@ -91,6 +91,20 @@ class StreamUpdateInput(BaseModel):
|
|
|
91
91
|
ops: list["UpdateOp"]
|
|
92
92
|
|
|
93
93
|
|
|
94
|
+
class UpdateOpError(BaseModel):
|
|
95
|
+
"""Per-op error returned by ``state::update`` / ``stream::update``.
|
|
96
|
+
|
|
97
|
+
Currently emitted only by the ``merge`` op when input violates the
|
|
98
|
+
new validation bounds. Successfully applied ops are still
|
|
99
|
+
reflected in the response's ``new_value``.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
op_index: int
|
|
103
|
+
code: str
|
|
104
|
+
message: str
|
|
105
|
+
doc_url: str | None = None
|
|
106
|
+
|
|
107
|
+
|
|
94
108
|
class StreamSetResult(BaseModel, Generic[TData]):
|
|
95
109
|
"""Result of stream set operation."""
|
|
96
110
|
|
|
@@ -103,6 +117,12 @@ class StreamUpdateResult(BaseModel, Generic[TData]):
|
|
|
103
117
|
|
|
104
118
|
old_value: TData | None = None
|
|
105
119
|
new_value: TData
|
|
120
|
+
# Per-op errors. Currently emitted only by the ``merge`` op for
|
|
121
|
+
# validation rejections. Field is omitted from the JSON wire when
|
|
122
|
+
# empty. ``default_factory`` is used (not ``= []``) to keep
|
|
123
|
+
# Pydantic's parameterized-Generic + default handling well-behaved
|
|
124
|
+
# across Python versions.
|
|
125
|
+
errors: list[UpdateOpError] = Field(default_factory=list)
|
|
106
126
|
|
|
107
127
|
|
|
108
128
|
class StreamDeleteResult(BaseModel):
|
|
@@ -135,6 +155,14 @@ class UpdateDecrement(BaseModel):
|
|
|
135
155
|
by: int | float
|
|
136
156
|
|
|
137
157
|
|
|
158
|
+
class UpdateAppend(BaseModel):
|
|
159
|
+
"""Append operation for stream update."""
|
|
160
|
+
|
|
161
|
+
type: str = "append"
|
|
162
|
+
path: str
|
|
163
|
+
value: Any
|
|
164
|
+
|
|
165
|
+
|
|
138
166
|
class UpdateRemove(BaseModel):
|
|
139
167
|
"""Remove operation for stream update."""
|
|
140
168
|
|
|
@@ -143,14 +171,42 @@ class UpdateRemove(BaseModel):
|
|
|
143
171
|
|
|
144
172
|
|
|
145
173
|
class UpdateMerge(BaseModel):
|
|
146
|
-
"""
|
|
174
|
+
"""Shallow merge an object into the target.
|
|
175
|
+
|
|
176
|
+
The target is the root (when ``path`` is omitted, an empty string,
|
|
177
|
+
or an empty list) or an arbitrary nested location specified by an
|
|
178
|
+
array of literal segments.
|
|
179
|
+
|
|
180
|
+
Path forms accepted:
|
|
181
|
+
- ``None`` / ``""`` / ``[]``: merge at the root.
|
|
182
|
+
- ``"foo"``: equivalent to ``["foo"]`` -- single first-level key.
|
|
183
|
+
- ``["a", "b", "c"]``: nested path. Each element is a *literal*
|
|
184
|
+
key. ``["a.b"]`` writes a single key named ``"a.b"``, not
|
|
185
|
+
``a -> b``.
|
|
186
|
+
|
|
187
|
+
Engine semantics:
|
|
188
|
+
- Missing or non-object intermediates along the path are
|
|
189
|
+
auto-replaced with ``{}``.
|
|
190
|
+
- The merge is shallow at the target node (top-level keys of
|
|
191
|
+
``value`` overwrite same-named keys; siblings preserved).
|
|
192
|
+
|
|
193
|
+
Validation: invalid paths/values (depth > 32 segments, segment >
|
|
194
|
+
256 bytes, value depth > 16, > 1024 top-level keys, or any
|
|
195
|
+
``__proto__`` / ``constructor`` / ``prototype`` segment or
|
|
196
|
+
top-level key) are rejected with a structured error in the
|
|
197
|
+
``errors`` array of the ``state::update`` / ``stream::update``
|
|
198
|
+
response. The merge does not apply when an error is returned.
|
|
199
|
+
"""
|
|
147
200
|
|
|
148
201
|
type: str = "merge"
|
|
149
|
-
|
|
202
|
+
# Optional. Accepts a single string or a list of literal segments.
|
|
203
|
+
# Pydantic resolves ``str | list[str]`` via smart-union: string
|
|
204
|
+
# input -> str, array input -> list[str].
|
|
205
|
+
path: str | list[str] | None = None
|
|
150
206
|
value: Any
|
|
151
207
|
|
|
152
208
|
|
|
153
|
-
UpdateOp = UpdateSet | UpdateIncrement | UpdateDecrement | UpdateRemove | UpdateMerge
|
|
209
|
+
UpdateOp = UpdateSet | UpdateIncrement | UpdateDecrement | UpdateAppend | UpdateRemove | UpdateMerge
|
|
154
210
|
|
|
155
211
|
|
|
156
212
|
class StreamTriggerConfig(BaseModel):
|