iii-sdk 0.11.4.dev3__tar.gz → 0.11.4.dev4__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.4.dev3 → iii_sdk-0.11.4.dev4}/PKG-INFO +1 -1
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/pyproject.toml +1 -1
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/src/iii/stream.py +50 -5
- iii_sdk-0.11.4.dev4/tests/test_stream_models.py +73 -0
- iii_sdk-0.11.4.dev3/tests/test_stream_models.py +0 -9
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/.gitignore +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/README.md +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/src/iii/__init__.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/src/iii/channels.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/src/iii/errors.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/src/iii/format_utils.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/src/iii/iii.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/src/iii/iii_constants.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/src/iii/iii_types.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/src/iii/logger.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/src/iii/otel_worker_gauges.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/src/iii/state.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/src/iii/telemetry.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/src/iii/telemetry_exporters.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/src/iii/telemetry_types.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/src/iii/triggers.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/src/iii/types.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/src/iii/utils.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/src/iii/worker_metrics.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/conftest.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_api_triggers.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_async_api.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_bridge.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_channel_close_delay.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_context_propagation.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_data_channels.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_errors.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_format_utils.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_healthcheck.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_hold_process.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_http_external_functions_integration.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_iii_registration_dedup.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_init_api.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_invocation_exception.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_logger_function_ids.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_logger_otel.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_middleware.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_pubsub.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_queue_integration.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_rbac_workers.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_register_function_args.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_state.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_streams.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_streams_runtime_annotations.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_sync_api.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_telemetry.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_telemetry_exporters.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_telemetry_types.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_trace_helpers.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_trigger_metadata.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_utils.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_worker_metadata.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/tests/test_worker_metrics.py +0 -0
- {iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/uv.lock +0 -0
|
@@ -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):
|
|
@@ -151,13 +171,38 @@ class UpdateRemove(BaseModel):
|
|
|
151
171
|
|
|
152
172
|
|
|
153
173
|
class UpdateMerge(BaseModel):
|
|
154
|
-
"""Shallow
|
|
155
|
-
|
|
156
|
-
|
|
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.
|
|
157
199
|
"""
|
|
158
200
|
|
|
159
201
|
type: str = "merge"
|
|
160
|
-
|
|
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
|
|
161
206
|
value: Any
|
|
162
207
|
|
|
163
208
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Unit tests for stream model serialization."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from iii.stream import StreamUpdateResult, UpdateAppend, UpdateMerge, UpdateOpError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_update_append_model_serializes() -> None:
|
|
9
|
+
op = UpdateAppend(path="chunks", value={"text": "hello"})
|
|
10
|
+
|
|
11
|
+
assert op.model_dump() == {"type": "append", "path": "chunks", "value": {"text": "hello"}}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_update_merge_with_string_path_round_trips() -> None:
|
|
15
|
+
op = UpdateMerge(path="session-abc", value={"author": "alice"})
|
|
16
|
+
dumped = op.model_dump()
|
|
17
|
+
assert dumped == {
|
|
18
|
+
"type": "merge",
|
|
19
|
+
"path": "session-abc",
|
|
20
|
+
"value": {"author": "alice"},
|
|
21
|
+
}
|
|
22
|
+
# JSON round-trip preserves the string form.
|
|
23
|
+
parsed = UpdateMerge.model_validate(json.loads(json.dumps(dumped)))
|
|
24
|
+
assert parsed.path == "session-abc"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_update_merge_with_array_path_round_trips() -> None:
|
|
28
|
+
op = UpdateMerge(path=["sessions", "abc"], value={"ts": "chunk"})
|
|
29
|
+
dumped = op.model_dump()
|
|
30
|
+
assert dumped == {
|
|
31
|
+
"type": "merge",
|
|
32
|
+
"path": ["sessions", "abc"],
|
|
33
|
+
"value": {"ts": "chunk"},
|
|
34
|
+
}
|
|
35
|
+
parsed = UpdateMerge.model_validate(json.loads(json.dumps(dumped)))
|
|
36
|
+
assert parsed.path == ["sessions", "abc"]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_update_merge_without_path_round_trips() -> None:
|
|
40
|
+
op = UpdateMerge(value={"x": 1})
|
|
41
|
+
dumped = op.model_dump()
|
|
42
|
+
assert dumped == {"type": "merge", "path": None, "value": {"x": 1}}
|
|
43
|
+
parsed = UpdateMerge.model_validate(json.loads(json.dumps(dumped)))
|
|
44
|
+
assert parsed.path is None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_update_op_error_round_trip() -> None:
|
|
48
|
+
err = UpdateOpError(
|
|
49
|
+
op_index=0,
|
|
50
|
+
code="merge.path.too_deep",
|
|
51
|
+
message="Path depth 33 exceeds maximum of 32",
|
|
52
|
+
doc_url="https://docs.iii.dev/workers/iii-state#merge-bounds",
|
|
53
|
+
)
|
|
54
|
+
dumped = err.model_dump()
|
|
55
|
+
assert dumped["code"] == "merge.path.too_deep"
|
|
56
|
+
assert dumped["op_index"] == 0
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_stream_update_result_with_errors_round_trips() -> None:
|
|
60
|
+
result = StreamUpdateResult[dict](
|
|
61
|
+
old_value=None,
|
|
62
|
+
new_value={"a": 1},
|
|
63
|
+
errors=[
|
|
64
|
+
UpdateOpError(
|
|
65
|
+
op_index=0,
|
|
66
|
+
code="merge.path.proto_polluted",
|
|
67
|
+
message='Path segment "__proto__" is a prototype-pollution sink',
|
|
68
|
+
)
|
|
69
|
+
],
|
|
70
|
+
)
|
|
71
|
+
dumped = result.model_dump()
|
|
72
|
+
assert len(dumped["errors"]) == 1
|
|
73
|
+
assert dumped["errors"][0]["code"] == "merge.path.proto_polluted"
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
"""Unit tests for stream model serialization."""
|
|
2
|
-
|
|
3
|
-
from iii.stream import UpdateAppend
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def test_update_append_model_serializes() -> None:
|
|
7
|
-
op = UpdateAppend(path="chunks", value={"text": "hello"})
|
|
8
|
-
|
|
9
|
-
assert op.model_dump() == {"type": "append", "path": "chunks", "value": {"text": "hello"}}
|
|
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
|
|
File without changes
|
{iii_sdk-0.11.4.dev3 → iii_sdk-0.11.4.dev4}/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
|