iii-sdk 0.12.0.dev1__tar.gz → 0.14.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.12.0.dev1 → iii_sdk-0.14.0.dev1}/PKG-INFO +20 -30
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/README.md +15 -22
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/pyproject.toml +5 -9
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/src/iii/__init__.py +29 -2
- iii_sdk-0.14.0.dev1/src/iii/baggage_span_processor.py +42 -0
- iii_sdk-0.14.0.dev1/src/iii/errors.py +88 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/src/iii/iii.py +217 -322
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/src/iii/iii_types.py +4 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/src/iii/logger.py +2 -5
- iii_sdk-0.14.0.dev1/src/iii/payload.py +92 -0
- iii_sdk-0.14.0.dev1/src/iii/span_ops.py +38 -0
- iii_sdk-0.14.0.dev1/src/iii/stream.py +333 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/src/iii/telemetry.py +89 -123
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/src/iii/telemetry_exporters.py +2 -2
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/src/iii/types.py +1 -17
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_api_triggers.py +95 -10
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_async_api.py +0 -18
- iii_sdk-0.14.0.dev1/tests/test_baggage_span_processor.py +162 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_bridge.py +24 -18
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_context_propagation.py +18 -14
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_data_channels.py +37 -29
- iii_sdk-0.14.0.dev1/tests/test_errors.py +168 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_healthcheck.py +1 -1
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_http_external_functions_integration.py +30 -16
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_iii_registration_dedup.py +2 -2
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_middleware.py +10 -10
- iii_sdk-0.14.0.dev1/tests/test_payload.py +173 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_pubsub.py +3 -3
- iii_sdk-0.14.0.dev1/tests/test_queue_integration.py +363 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_rbac_workers.py +157 -15
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_register_function_args.py +57 -71
- iii_sdk-0.14.0.dev1/tests/test_span_ops.py +93 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_state.py +126 -6
- iii_sdk-0.14.0.dev1/tests/test_stream_models.py +134 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_streams.py +175 -7
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_sync_api.py +13 -11
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_telemetry_exporters.py +26 -0
- iii_sdk-0.14.0.dev1/tests/test_worker_metadata.py +113 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/uv.lock +1083 -1089
- iii_sdk-0.12.0.dev1/src/iii/stream.py +0 -220
- iii_sdk-0.12.0.dev1/tests/test_queue_integration.py +0 -136
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/.gitignore +0 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/src/iii/channels.py +0 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/src/iii/format_utils.py +0 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/src/iii/iii_constants.py +0 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/src/iii/otel_worker_gauges.py +0 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/src/iii/state.py +0 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/src/iii/telemetry_types.py +0 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/src/iii/triggers.py +0 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/src/iii/utils.py +0 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/src/iii/worker_metrics.py +0 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/conftest.py +0 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_channel_close_delay.py +0 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_format_utils.py +0 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_hold_process.py +0 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_init_api.py +0 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_invocation_exception.py +0 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_logger_function_ids.py +0 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_logger_otel.py +0 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_streams_runtime_annotations.py +0 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_telemetry.py +0 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_telemetry_types.py +0 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_trace_helpers.py +0 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_trigger_metadata.py +0 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/tests/test_utils.py +0 -0
- {iii_sdk-0.12.0.dev1 → iii_sdk-0.14.0.dev1}/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.
|
|
3
|
+
Version: 0.14.0.dev1
|
|
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
|
|
@@ -14,6 +14,8 @@ 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: opentelemetry-api>=1.25
|
|
18
|
+
Requires-Dist: opentelemetry-sdk>=1.25
|
|
17
19
|
Requires-Dist: pydantic>=2.0
|
|
18
20
|
Requires-Dist: websockets>=12.0
|
|
19
21
|
Provides-Extra: dev
|
|
@@ -21,15 +23,10 @@ Requires-Dist: aiohttp>=3.9; extra == 'dev'
|
|
|
21
23
|
Requires-Dist: anyio>=4.0; extra == 'dev'
|
|
22
24
|
Requires-Dist: griffe>=1.0; extra == 'dev'
|
|
23
25
|
Requires-Dist: mypy>=1.8; extra == 'dev'
|
|
24
|
-
Requires-Dist: opentelemetry-api>=1.25; extra == 'dev'
|
|
25
|
-
Requires-Dist: opentelemetry-sdk>=1.25; extra == 'dev'
|
|
26
26
|
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
27
27
|
Requires-Dist: pytest-cov>=6.0; extra == 'dev'
|
|
28
28
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
29
29
|
Requires-Dist: ruff>=0.2; extra == 'dev'
|
|
30
|
-
Provides-Extra: otel
|
|
31
|
-
Requires-Dist: opentelemetry-api>=1.25; extra == 'otel'
|
|
32
|
-
Requires-Dist: opentelemetry-sdk>=1.25; extra == 'otel'
|
|
33
30
|
Description-Content-Type: text/markdown
|
|
34
31
|
|
|
35
32
|
# iii-sdk
|
|
@@ -56,30 +53,31 @@ iii = register_worker("ws://localhost:49134")
|
|
|
56
53
|
def greet(data):
|
|
57
54
|
return {"message": f"Hello, {data['name']}!"}
|
|
58
55
|
|
|
59
|
-
iii.register_function(
|
|
56
|
+
iii.register_function("hello::greet", greet)
|
|
60
57
|
|
|
61
58
|
iii.register_trigger({
|
|
62
59
|
"type": "http",
|
|
63
|
-
"function_id": "greet",
|
|
60
|
+
"function_id": "hello::greet",
|
|
64
61
|
"config": {"api_path": "/greet", "http_method": "POST"},
|
|
65
62
|
})
|
|
66
63
|
|
|
67
64
|
iii.connect()
|
|
68
65
|
|
|
69
|
-
result = iii.trigger({"function_id": "greet", "payload": {"name": "world"}})
|
|
66
|
+
result = iii.trigger({"function_id": "hello::greet", "payload": {"name": "world"}})
|
|
70
67
|
print(result) # {"message": "Hello, world!"}
|
|
71
68
|
```
|
|
72
69
|
|
|
73
70
|
## API
|
|
74
71
|
|
|
75
|
-
| Operation | Signature
|
|
76
|
-
| ------------------------ |
|
|
77
|
-
| Initialize | `register_worker(url, options?)`
|
|
78
|
-
| Register function | `iii.register_function(
|
|
79
|
-
| Register trigger | `iii.register_trigger({"type": ..., "function_id": ..., "config": ...})`
|
|
80
|
-
| Invoke (await result) | `iii.trigger({"function_id": id, "payload": data})`
|
|
81
|
-
| Invoke (fire-and-forget) | `iii.trigger({"function_id": id, ..., "action": TriggerAction.Void()})`
|
|
82
|
-
|
|
|
72
|
+
| Operation | Signature | Description |
|
|
73
|
+
| ------------------------ | -------------------------------------------------------------------------------------- | ------------------------------------------------------ |
|
|
74
|
+
| Initialize | `register_worker(url, options?)` | Create an SDK instance and auto-connect |
|
|
75
|
+
| Register function | `iii.register_function(id, handler)` | Register a function that can be invoked by name |
|
|
76
|
+
| Register trigger | `iii.register_trigger({"type": ..., "function_id": ..., "config": ...})` | Bind a trigger (HTTP, cron, queue, etc.) to a function |
|
|
77
|
+
| Invoke (await result) | `iii.trigger({"function_id": id, "payload": data})` | Invoke a function and wait for the result |
|
|
78
|
+
| Invoke (fire-and-forget) | `iii.trigger({"function_id": id, ..., "action": TriggerAction.Void()})` | Fire-and-forget |
|
|
79
|
+
| Invoke (enqueue) | `iii.trigger({"function_id": id, ..., "action": TriggerAction.Enqueue(queue="name")})` | Route invocation through a named queue |
|
|
80
|
+
| Shutdown | `iii.shutdown()` | Disconnect and stop background thread |
|
|
83
81
|
|
|
84
82
|
`register_worker()` creates the SDK instance and auto-connects to the engine.
|
|
85
83
|
|
|
@@ -89,7 +87,7 @@ print(result) # {"message": "Hello, world!"}
|
|
|
89
87
|
def create_order(data):
|
|
90
88
|
return {"status_code": 201, "body": {"id": "123", "item": data["body"]["item"]}}
|
|
91
89
|
|
|
92
|
-
iii.register_function(
|
|
90
|
+
iii.register_function("orders::create", create_order)
|
|
93
91
|
```
|
|
94
92
|
|
|
95
93
|
### Registering Triggers
|
|
@@ -97,7 +95,7 @@ iii.register_function({"id": "orders.create"}, create_order)
|
|
|
97
95
|
```python
|
|
98
96
|
iii.register_trigger({
|
|
99
97
|
"type": "http",
|
|
100
|
-
"function_id": "orders
|
|
98
|
+
"function_id": "orders::create",
|
|
101
99
|
"config": {"api_path": "/orders", "http_method": "POST"},
|
|
102
100
|
})
|
|
103
101
|
```
|
|
@@ -105,17 +103,9 @@ iii.register_trigger({
|
|
|
105
103
|
### Invoking Functions
|
|
106
104
|
|
|
107
105
|
```python
|
|
108
|
-
result = iii.trigger({"function_id": "orders
|
|
106
|
+
result = iii.trigger({"function_id": "orders::create", "payload": {"body": {"item": "widget"}}})
|
|
109
107
|
```
|
|
110
108
|
|
|
111
|
-
## Modules
|
|
112
|
-
|
|
113
|
-
| Import | What it provides |
|
|
114
|
-
| --------------- | --------------------------------- |
|
|
115
|
-
| `iii` | Core SDK (`III`, types) |
|
|
116
|
-
| `iii.stream` | Stream client for real-time state |
|
|
117
|
-
| `iii.telemetry` | OpenTelemetry integration |
|
|
118
|
-
|
|
119
109
|
## Development
|
|
120
110
|
|
|
121
111
|
### Install in development mode
|
|
@@ -22,30 +22,31 @@ 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("hello::greet", greet)
|
|
26
26
|
|
|
27
27
|
iii.register_trigger({
|
|
28
28
|
"type": "http",
|
|
29
|
-
"function_id": "greet",
|
|
29
|
+
"function_id": "hello::greet",
|
|
30
30
|
"config": {"api_path": "/greet", "http_method": "POST"},
|
|
31
31
|
})
|
|
32
32
|
|
|
33
33
|
iii.connect()
|
|
34
34
|
|
|
35
|
-
result = iii.trigger({"function_id": "greet", "payload": {"name": "world"}})
|
|
35
|
+
result = iii.trigger({"function_id": "hello::greet", "payload": {"name": "world"}})
|
|
36
36
|
print(result) # {"message": "Hello, world!"}
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
## API
|
|
40
40
|
|
|
41
|
-
| Operation | Signature
|
|
42
|
-
| ------------------------ |
|
|
43
|
-
| Initialize | `register_worker(url, options?)`
|
|
44
|
-
| Register function | `iii.register_function(
|
|
45
|
-
| Register trigger | `iii.register_trigger({"type": ..., "function_id": ..., "config": ...})`
|
|
46
|
-
| Invoke (await result) | `iii.trigger({"function_id": id, "payload": data})`
|
|
47
|
-
| Invoke (fire-and-forget) | `iii.trigger({"function_id": id, ..., "action": TriggerAction.Void()})`
|
|
48
|
-
|
|
|
41
|
+
| Operation | Signature | Description |
|
|
42
|
+
| ------------------------ | -------------------------------------------------------------------------------------- | ------------------------------------------------------ |
|
|
43
|
+
| Initialize | `register_worker(url, options?)` | Create an SDK instance and auto-connect |
|
|
44
|
+
| Register function | `iii.register_function(id, handler)` | Register a function that can be invoked by name |
|
|
45
|
+
| Register trigger | `iii.register_trigger({"type": ..., "function_id": ..., "config": ...})` | Bind a trigger (HTTP, cron, queue, etc.) to a function |
|
|
46
|
+
| Invoke (await result) | `iii.trigger({"function_id": id, "payload": data})` | Invoke a function and wait for the result |
|
|
47
|
+
| Invoke (fire-and-forget) | `iii.trigger({"function_id": id, ..., "action": TriggerAction.Void()})` | Fire-and-forget |
|
|
48
|
+
| Invoke (enqueue) | `iii.trigger({"function_id": id, ..., "action": TriggerAction.Enqueue(queue="name")})` | Route invocation through a named queue |
|
|
49
|
+
| Shutdown | `iii.shutdown()` | Disconnect and stop background thread |
|
|
49
50
|
|
|
50
51
|
`register_worker()` creates the SDK instance and auto-connects to the engine.
|
|
51
52
|
|
|
@@ -55,7 +56,7 @@ print(result) # {"message": "Hello, world!"}
|
|
|
55
56
|
def create_order(data):
|
|
56
57
|
return {"status_code": 201, "body": {"id": "123", "item": data["body"]["item"]}}
|
|
57
58
|
|
|
58
|
-
iii.register_function(
|
|
59
|
+
iii.register_function("orders::create", create_order)
|
|
59
60
|
```
|
|
60
61
|
|
|
61
62
|
### Registering Triggers
|
|
@@ -63,7 +64,7 @@ iii.register_function({"id": "orders.create"}, create_order)
|
|
|
63
64
|
```python
|
|
64
65
|
iii.register_trigger({
|
|
65
66
|
"type": "http",
|
|
66
|
-
"function_id": "orders
|
|
67
|
+
"function_id": "orders::create",
|
|
67
68
|
"config": {"api_path": "/orders", "http_method": "POST"},
|
|
68
69
|
})
|
|
69
70
|
```
|
|
@@ -71,17 +72,9 @@ iii.register_trigger({
|
|
|
71
72
|
### Invoking Functions
|
|
72
73
|
|
|
73
74
|
```python
|
|
74
|
-
result = iii.trigger({"function_id": "orders
|
|
75
|
+
result = iii.trigger({"function_id": "orders::create", "payload": {"body": {"item": "widget"}}})
|
|
75
76
|
```
|
|
76
77
|
|
|
77
|
-
## Modules
|
|
78
|
-
|
|
79
|
-
| Import | What it provides |
|
|
80
|
-
| --------------- | --------------------------------- |
|
|
81
|
-
| `iii` | Core SDK (`III`, types) |
|
|
82
|
-
| `iii.stream` | Stream client for real-time state |
|
|
83
|
-
| `iii.telemetry` | OpenTelemetry integration |
|
|
84
|
-
|
|
85
78
|
## Development
|
|
86
79
|
|
|
87
80
|
### Install in development mode
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "iii-sdk"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.14.0.dev1"
|
|
8
8
|
description = "III SDK for Python"
|
|
9
9
|
authors = [{ name = "III" }]
|
|
10
10
|
license = { text = "Apache-2.0" }
|
|
@@ -22,17 +22,15 @@ classifiers = [
|
|
|
22
22
|
dependencies = [
|
|
23
23
|
"websockets>=12.0",
|
|
24
24
|
"pydantic>=2.0",
|
|
25
|
+
"opentelemetry-api>=1.25",
|
|
26
|
+
"opentelemetry-sdk>=1.25",
|
|
25
27
|
]
|
|
26
28
|
|
|
27
29
|
[project.urls]
|
|
28
|
-
Homepage = "https://github.com/iii-hq/
|
|
29
|
-
Repository = "https://github.com/iii-hq/
|
|
30
|
+
Homepage = "https://github.com/iii-hq/iii"
|
|
31
|
+
Repository = "https://github.com/iii-hq/iii"
|
|
30
32
|
|
|
31
33
|
[project.optional-dependencies]
|
|
32
|
-
otel = [
|
|
33
|
-
"opentelemetry-api>=1.25",
|
|
34
|
-
"opentelemetry-sdk>=1.25",
|
|
35
|
-
]
|
|
36
34
|
dev = [
|
|
37
35
|
"pytest>=8.0",
|
|
38
36
|
"pytest-asyncio>=0.23",
|
|
@@ -41,8 +39,6 @@ dev = [
|
|
|
41
39
|
"aiohttp>=3.9",
|
|
42
40
|
"mypy>=1.8",
|
|
43
41
|
"ruff>=0.2",
|
|
44
|
-
"opentelemetry-api>=1.25",
|
|
45
|
-
"opentelemetry-sdk>=1.25",
|
|
46
42
|
"griffe>=1.0",
|
|
47
43
|
]
|
|
48
44
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""III SDK for Python."""
|
|
2
2
|
|
|
3
|
+
from .baggage_span_processor import DEFAULT_ALLOWLIST, BaggageSpanProcessor
|
|
3
4
|
from .channels import ChannelReader, ChannelWriter
|
|
5
|
+
from .errors import IIIForbiddenError, IIIInvocationError, IIITimeoutError
|
|
4
6
|
from .format_utils import extract_request_format, extract_response_format, python_type_to_format
|
|
5
7
|
from .iii import TriggerAction, register_worker
|
|
6
8
|
from .iii_constants import FunctionRef, InitOptions, ReconnectionConfig, TelemetryOptions
|
|
@@ -20,7 +22,6 @@ from .iii_types import (
|
|
|
20
22
|
OnTriggerTypeRegistrationInput,
|
|
21
23
|
OnTriggerTypeRegistrationResult,
|
|
22
24
|
RegisterFunctionFormat,
|
|
23
|
-
RegisterFunctionInput,
|
|
24
25
|
RegisterFunctionMessage,
|
|
25
26
|
RegisterServiceInput,
|
|
26
27
|
RegisterTriggerInput,
|
|
@@ -35,6 +36,18 @@ from .iii_types import (
|
|
|
35
36
|
TriggerTypeInfo,
|
|
36
37
|
)
|
|
37
38
|
from .logger import Logger
|
|
39
|
+
from .payload import (
|
|
40
|
+
REDACTED_PLACEHOLDER,
|
|
41
|
+
redact,
|
|
42
|
+
redact_and_truncate,
|
|
43
|
+
resolve_max_bytes_from_env,
|
|
44
|
+
)
|
|
45
|
+
from .span_ops import (
|
|
46
|
+
current_span_is_recording,
|
|
47
|
+
record_span_event,
|
|
48
|
+
set_current_span_attribute,
|
|
49
|
+
set_current_span_error,
|
|
50
|
+
)
|
|
38
51
|
from .stream import (
|
|
39
52
|
IStream,
|
|
40
53
|
StreamChangeEvent,
|
|
@@ -59,9 +72,24 @@ from .types import (
|
|
|
59
72
|
from .utils import http
|
|
60
73
|
|
|
61
74
|
__all__ = [
|
|
75
|
+
# Telemetry helpers
|
|
76
|
+
"BaggageSpanProcessor",
|
|
77
|
+
"DEFAULT_ALLOWLIST",
|
|
78
|
+
"REDACTED_PLACEHOLDER",
|
|
79
|
+
"current_span_is_recording",
|
|
80
|
+
"record_span_event",
|
|
81
|
+
"set_current_span_attribute",
|
|
82
|
+
"set_current_span_error",
|
|
83
|
+
"redact",
|
|
84
|
+
"redact_and_truncate",
|
|
85
|
+
"resolve_max_bytes_from_env",
|
|
62
86
|
# Channels
|
|
63
87
|
"ChannelReader",
|
|
64
88
|
"ChannelWriter",
|
|
89
|
+
# Errors
|
|
90
|
+
"IIIForbiddenError",
|
|
91
|
+
"IIIInvocationError",
|
|
92
|
+
"IIITimeoutError",
|
|
65
93
|
# Core
|
|
66
94
|
"FunctionRef",
|
|
67
95
|
"InitOptions",
|
|
@@ -87,7 +115,6 @@ __all__ = [
|
|
|
87
115
|
"HttpInvocationConfig",
|
|
88
116
|
"MessageType",
|
|
89
117
|
"RegisterFunctionFormat",
|
|
90
|
-
"RegisterFunctionInput",
|
|
91
118
|
"RegisterFunctionMessage",
|
|
92
119
|
"RegisterServiceInput",
|
|
93
120
|
"RegisterTriggerInput",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Baggage -> span attribute processor."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Sequence
|
|
6
|
+
|
|
7
|
+
from opentelemetry import baggage
|
|
8
|
+
from opentelemetry.context import Context
|
|
9
|
+
from opentelemetry.sdk.trace import ReadableSpan, Span, SpanProcessor
|
|
10
|
+
|
|
11
|
+
#: DEFAULT_ALLOWLIST drift across languages would break worker chains;
|
|
12
|
+
#: lockstep tests in each SDK pin this constant at CI time.
|
|
13
|
+
DEFAULT_ALLOWLIST: tuple[str, ...] = (
|
|
14
|
+
"iii.session.id",
|
|
15
|
+
"iii.message.id",
|
|
16
|
+
"iii.function.id",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BaggageSpanProcessor(SpanProcessor):
|
|
21
|
+
|
|
22
|
+
def __init__(self, allowlist: Sequence[str] = DEFAULT_ALLOWLIST) -> None:
|
|
23
|
+
self._allowlist: tuple[str, ...] = tuple(allowlist)
|
|
24
|
+
|
|
25
|
+
def on_start(self, span: Span, parent_context: Context | None = None) -> None:
|
|
26
|
+
# NoOp guard: skip allocation when sampler drops the span.
|
|
27
|
+
if not span.is_recording():
|
|
28
|
+
return
|
|
29
|
+
|
|
30
|
+
for key in self._allowlist:
|
|
31
|
+
value = baggage.get_baggage(key, parent_context)
|
|
32
|
+
if value is not None:
|
|
33
|
+
span.set_attribute(key, str(value))
|
|
34
|
+
|
|
35
|
+
def on_end(self, span: ReadableSpan) -> None: # noqa: ARG002
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
def shutdown(self) -> None:
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
def force_flush(self, timeout_millis: int = 30000) -> bool: # noqa: ARG002
|
|
42
|
+
return True
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class IIIInvocationError(Exception):
|
|
7
|
+
"""Raised when an invocation dispatched by the SDK fails.
|
|
8
|
+
|
|
9
|
+
Subclass by code:
|
|
10
|
+
- ``IIIForbiddenError`` (``code == 'FORBIDDEN'``)
|
|
11
|
+
- ``IIITimeoutError`` (``code == 'TIMEOUT'``)
|
|
12
|
+
|
|
13
|
+
Catch the base to handle every rejection; catch a subclass to react to
|
|
14
|
+
a specific category. ``except Exception`` continues to work because
|
|
15
|
+
``IIIInvocationError`` inherits from ``Exception``.
|
|
16
|
+
|
|
17
|
+
Attributes are read-only after construction. ``stacktrace`` is the
|
|
18
|
+
engine-side trace when the remote handler raised; it may include
|
|
19
|
+
internal file paths and should not be surfaced to end users. ``str(err)``
|
|
20
|
+
intentionally never includes the stacktrace.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
code: str,
|
|
26
|
+
message: str,
|
|
27
|
+
function_id: str | None = None,
|
|
28
|
+
stacktrace: str | None = None,
|
|
29
|
+
invocation_id: str | None = None,
|
|
30
|
+
) -> None:
|
|
31
|
+
super().__init__(f"{code}: {message}")
|
|
32
|
+
self.code = code
|
|
33
|
+
self.message = message
|
|
34
|
+
self.function_id = function_id
|
|
35
|
+
self.stacktrace = stacktrace
|
|
36
|
+
self.invocation_id = invocation_id
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class IIIForbiddenError(IIIInvocationError):
|
|
40
|
+
"""Raised when RBAC denies an invocation. ``code == 'FORBIDDEN'``."""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class IIITimeoutError(IIIInvocationError):
|
|
44
|
+
"""Raised when an invocation exceeds its timeout. ``code == 'TIMEOUT'``."""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _wrap_wire_error(
|
|
48
|
+
error: Any,
|
|
49
|
+
*,
|
|
50
|
+
function_id: str | None,
|
|
51
|
+
invocation_id: str | None,
|
|
52
|
+
) -> IIIInvocationError:
|
|
53
|
+
"""Convert a wire ``ErrorBody``-shaped dict into a typed exception.
|
|
54
|
+
|
|
55
|
+
Dispatches to ``IIIForbiddenError`` / ``IIITimeoutError`` based on
|
|
56
|
+
``error['code']``. Malformed shapes (non-dict, missing fields, non-string
|
|
57
|
+
values) fall back to ``IIIInvocationError(code='UNKNOWN', ...)`` so no
|
|
58
|
+
rejection path prints as a raw dict repr.
|
|
59
|
+
"""
|
|
60
|
+
if isinstance(error, dict):
|
|
61
|
+
raw_code = error.get("code")
|
|
62
|
+
code = raw_code if isinstance(raw_code, str) else "UNKNOWN"
|
|
63
|
+
|
|
64
|
+
raw_message = error.get("message")
|
|
65
|
+
message = raw_message if isinstance(raw_message, str) else "<no message>"
|
|
66
|
+
|
|
67
|
+
raw_stacktrace = error.get("stacktrace")
|
|
68
|
+
stacktrace = raw_stacktrace if isinstance(raw_stacktrace, str) else None
|
|
69
|
+
|
|
70
|
+
cls: type[IIIInvocationError] = {
|
|
71
|
+
"FORBIDDEN": IIIForbiddenError,
|
|
72
|
+
"TIMEOUT": IIITimeoutError,
|
|
73
|
+
}.get(code, IIIInvocationError)
|
|
74
|
+
|
|
75
|
+
return cls(
|
|
76
|
+
code=code,
|
|
77
|
+
message=message,
|
|
78
|
+
function_id=function_id,
|
|
79
|
+
stacktrace=stacktrace,
|
|
80
|
+
invocation_id=invocation_id,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return IIIInvocationError(
|
|
84
|
+
code="UNKNOWN",
|
|
85
|
+
message=str(error),
|
|
86
|
+
function_id=function_id,
|
|
87
|
+
invocation_id=invocation_id,
|
|
88
|
+
)
|