iii-sdk 0.12.0.dev1__tar.gz → 0.13.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 (66) hide show
  1. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/PKG-INFO +20 -30
  2. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/README.md +15 -22
  3. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/pyproject.toml +5 -9
  4. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/src/iii/__init__.py +29 -10
  5. iii_sdk-0.13.0.dev1/src/iii/baggage_span_processor.py +42 -0
  6. iii_sdk-0.13.0.dev1/src/iii/errors.py +88 -0
  7. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/src/iii/iii.py +217 -361
  8. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/src/iii/iii_types.py +0 -135
  9. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/src/iii/logger.py +5 -8
  10. iii_sdk-0.13.0.dev1/src/iii/payload.py +92 -0
  11. iii_sdk-0.13.0.dev1/src/iii/span_ops.py +38 -0
  12. iii_sdk-0.13.0.dev1/src/iii/stream.py +333 -0
  13. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/src/iii/telemetry.py +97 -131
  14. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/src/iii/telemetry_exporters.py +2 -2
  15. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/src/iii/types.py +1 -20
  16. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_api_triggers.py +95 -10
  17. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_async_api.py +0 -18
  18. iii_sdk-0.13.0.dev1/tests/test_baggage_span_processor.py +162 -0
  19. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_bridge.py +24 -18
  20. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_context_propagation.py +18 -14
  21. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_data_channels.py +37 -29
  22. iii_sdk-0.13.0.dev1/tests/test_errors.py +168 -0
  23. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_healthcheck.py +1 -1
  24. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_http_external_functions_integration.py +30 -16
  25. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_iii_registration_dedup.py +2 -2
  26. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_logger_otel.py +3 -3
  27. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_middleware.py +10 -10
  28. iii_sdk-0.13.0.dev1/tests/test_payload.py +173 -0
  29. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_pubsub.py +3 -3
  30. iii_sdk-0.13.0.dev1/tests/test_queue_integration.py +363 -0
  31. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_rbac_workers.py +158 -17
  32. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_register_function_args.py +57 -71
  33. iii_sdk-0.13.0.dev1/tests/test_span_ops.py +93 -0
  34. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_state.py +126 -6
  35. iii_sdk-0.13.0.dev1/tests/test_stream_models.py +134 -0
  36. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_streams.py +175 -7
  37. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_sync_api.py +14 -34
  38. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_telemetry.py +14 -14
  39. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_telemetry_exporters.py +26 -0
  40. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_trigger_metadata.py +2 -18
  41. iii_sdk-0.13.0.dev1/tests/test_worker_metadata.py +113 -0
  42. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/uv.lock +6 -12
  43. iii_sdk-0.12.0.dev1/src/iii/stream.py +0 -220
  44. iii_sdk-0.12.0.dev1/tests/test_queue_integration.py +0 -136
  45. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/.gitignore +0 -0
  46. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/src/iii/channels.py +0 -0
  47. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/src/iii/format_utils.py +0 -0
  48. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/src/iii/iii_constants.py +0 -0
  49. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/src/iii/otel_worker_gauges.py +0 -0
  50. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/src/iii/state.py +0 -0
  51. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/src/iii/telemetry_types.py +0 -0
  52. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/src/iii/triggers.py +0 -0
  53. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/src/iii/utils.py +0 -0
  54. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/src/iii/worker_metrics.py +0 -0
  55. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/conftest.py +0 -0
  56. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_channel_close_delay.py +0 -0
  57. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_format_utils.py +0 -0
  58. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_hold_process.py +0 -0
  59. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_init_api.py +0 -0
  60. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_invocation_exception.py +0 -0
  61. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_logger_function_ids.py +0 -0
  62. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_streams_runtime_annotations.py +0 -0
  63. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_telemetry_types.py +0 -0
  64. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_trace_helpers.py +0 -0
  65. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.0.dev1}/tests/test_utils.py +0 -0
  66. {iii_sdk-0.12.0.dev1 → iii_sdk-0.13.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.12.0.dev1
3
+ Version: 0.13.0.dev1
4
4
  Summary: III SDK for Python
5
- Project-URL: Homepage, https://github.com/iii-hq/sdk
6
- Project-URL: Repository, https://github.com/iii-hq/sdk
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({"id": "greet"}, greet)
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 | Description |
76
- | ------------------------ | ------------------------------------------------- | ------------------------------------------------------ |
77
- | Initialize | `register_worker(url, options?)` | Create an SDK instance and auto-connect |
78
- | Register function | `iii.register_function({"id": id}, handler)` | Register a function that can be invoked by name |
79
- | Register trigger | `iii.register_trigger({"type": ..., "function_id": ..., "config": ...})` | Bind a trigger (HTTP, cron, queue, etc.) to a function |
80
- | Invoke (await result) | `iii.trigger({"function_id": id, "payload": data})` | Invoke a function and wait for the result |
81
- | Invoke (fire-and-forget) | `iii.trigger({"function_id": id, ..., "action": TriggerAction.Void()})` | Fire-and-forget |
82
- | Shutdown | `iii.shutdown()` | Disconnect and stop background thread |
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({"id": "orders.create"}, create_order)
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.create",
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.create", "payload": {"body": {"item": "widget"}}})
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({"id": "greet"}, greet)
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 | Description |
42
- | ------------------------ | ------------------------------------------------- | ------------------------------------------------------ |
43
- | Initialize | `register_worker(url, options?)` | Create an SDK instance and auto-connect |
44
- | Register function | `iii.register_function({"id": 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
- | Shutdown | `iii.shutdown()` | Disconnect and stop background thread |
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({"id": "orders.create"}, create_order)
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.create",
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.create", "payload": {"body": {"item": "widget"}}})
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.12.0.dev1"
7
+ version = "0.13.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/sdk"
29
- Repository = "https://github.com/iii-hq/sdk"
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
@@ -8,7 +10,6 @@ from .iii_types import (
8
10
  AuthInput,
9
11
  AuthResult,
10
12
  EnqueueResult,
11
- FunctionInfo,
12
13
  HttpAuthConfig,
13
14
  HttpInvocationConfig,
14
15
  MessageType,
@@ -20,9 +21,7 @@ from .iii_types import (
20
21
  OnTriggerTypeRegistrationInput,
21
22
  OnTriggerTypeRegistrationResult,
22
23
  RegisterFunctionFormat,
23
- RegisterFunctionInput,
24
24
  RegisterFunctionMessage,
25
- RegisterServiceInput,
26
25
  RegisterTriggerInput,
27
26
  RegisterTriggerMessage,
28
27
  RegisterTriggerTypeInput,
@@ -30,11 +29,21 @@ from .iii_types import (
30
29
  StreamChannelRef,
31
30
  TriggerActionEnqueue,
32
31
  TriggerActionVoid,
33
- TriggerInfo,
34
32
  TriggerRequest,
35
- TriggerTypeInfo,
36
33
  )
37
34
  from .logger import Logger
35
+ from .payload import (
36
+ REDACTED_PLACEHOLDER,
37
+ redact,
38
+ redact_and_truncate,
39
+ resolve_max_bytes_from_env,
40
+ )
41
+ from .span_ops import (
42
+ current_span_is_recording,
43
+ record_span_event,
44
+ set_current_span_attribute,
45
+ set_current_span_error,
46
+ )
38
47
  from .stream import (
39
48
  IStream,
40
49
  StreamChangeEvent,
@@ -59,9 +68,24 @@ from .types import (
59
68
  from .utils import http
60
69
 
61
70
  __all__ = [
71
+ # Telemetry helpers
72
+ "BaggageSpanProcessor",
73
+ "DEFAULT_ALLOWLIST",
74
+ "REDACTED_PLACEHOLDER",
75
+ "current_span_is_recording",
76
+ "record_span_event",
77
+ "set_current_span_attribute",
78
+ "set_current_span_error",
79
+ "redact",
80
+ "redact_and_truncate",
81
+ "resolve_max_bytes_from_env",
62
82
  # Channels
63
83
  "ChannelReader",
64
84
  "ChannelWriter",
85
+ # Errors
86
+ "IIIForbiddenError",
87
+ "IIIInvocationError",
88
+ "IIITimeoutError",
65
89
  # Core
66
90
  "FunctionRef",
67
91
  "InitOptions",
@@ -82,14 +106,11 @@ __all__ = [
82
106
  "OnTriggerTypeRegistrationResult",
83
107
  # Message types
84
108
  "EnqueueResult",
85
- "FunctionInfo",
86
109
  "HttpAuthConfig",
87
110
  "HttpInvocationConfig",
88
111
  "MessageType",
89
112
  "RegisterFunctionFormat",
90
- "RegisterFunctionInput",
91
113
  "RegisterFunctionMessage",
92
- "RegisterServiceInput",
93
114
  "RegisterTriggerInput",
94
115
  "RegisterTriggerMessage",
95
116
  "RegisterTriggerTypeInput",
@@ -97,9 +118,7 @@ __all__ = [
97
118
  "StreamChannelRef",
98
119
  "TriggerActionEnqueue",
99
120
  "TriggerActionVoid",
100
- "TriggerInfo",
101
121
  "TriggerRequest",
102
- "TriggerTypeInfo",
103
122
  # Logger
104
123
  "Logger",
105
124
  # Triggers
@@ -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
+ )