iii-sdk 0.19.0.dev2__tar.gz → 0.19.0.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.
Files changed (61) hide show
  1. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/PKG-INFO +2 -2
  2. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/pyproject.toml +2 -2
  3. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/src/iii/__init__.py +1 -56
  4. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/src/iii/errors.py +10 -26
  5. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/src/iii/iii.py +19 -11
  6. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_errors.py +17 -32
  7. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_invocation_exception.py +2 -2
  8. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_rbac_workers.py +5 -7
  9. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/uv.lock +2 -2
  10. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/.gitignore +0 -0
  11. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/README.md +0 -0
  12. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/src/iii/channels.py +0 -0
  13. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/src/iii/format_utils.py +0 -0
  14. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/src/iii/helpers.py +0 -0
  15. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/src/iii/iii_constants.py +0 -0
  16. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/src/iii/iii_types.py +0 -0
  17. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/src/iii/otel_worker_gauges.py +0 -0
  18. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/src/iii/state.py +0 -0
  19. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/src/iii/stream.py +0 -0
  20. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/src/iii/triggers.py +0 -0
  21. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/src/iii/types.py +0 -0
  22. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/src/iii/utils.py +0 -0
  23. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/src/iii/worker_metrics.py +0 -0
  24. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/conftest.py +0 -0
  25. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_api_triggers.py +0 -0
  26. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_async_api.py +0 -0
  27. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_baggage_span_processor.py +0 -0
  28. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_bridge.py +0 -0
  29. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_channel_close_delay.py +0 -0
  30. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_context_propagation.py +0 -0
  31. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_data_channels.py +0 -0
  32. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_format_utils.py +0 -0
  33. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_healthcheck.py +0 -0
  34. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_helpers.py +0 -0
  35. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_hold_process.py +0 -0
  36. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_http_external_functions_integration.py +0 -0
  37. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_iii_registration_dedup.py +0 -0
  38. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_init_api.py +0 -0
  39. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_logger_function_ids.py +0 -0
  40. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_logger_otel.py +0 -0
  41. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_middleware.py +0 -0
  42. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_payload.py +0 -0
  43. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_pubsub.py +0 -0
  44. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_queue_integration.py +0 -0
  45. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_register_function_args.py +0 -0
  46. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_span_ops.py +0 -0
  47. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_state.py +0 -0
  48. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_stream_models.py +0 -0
  49. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_streams.py +0 -0
  50. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_streams_runtime_annotations.py +0 -0
  51. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_sync_api.py +0 -0
  52. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_telemetry.py +0 -0
  53. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_telemetry_exporters.py +0 -0
  54. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_telemetry_types.py +0 -0
  55. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_trace_helpers.py +0 -0
  56. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_trigger_metadata.py +0 -0
  57. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_trigger_registration_error.py +0 -0
  58. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_trigger_type_lifecycle.py +0 -0
  59. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_utils.py +0 -0
  60. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_worker_metadata.py +0 -0
  61. {iii_sdk-0.19.0.dev2 → iii_sdk-0.19.0.dev4}/tests/test_worker_metrics.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iii-sdk
3
- Version: 0.19.0.dev2
3
+ Version: 0.19.0.dev4
4
4
  Summary: III SDK for Python
5
5
  Project-URL: Homepage, https://github.com/iii-hq/iii
6
6
  Project-URL: Repository, https://github.com/iii-hq/iii
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.10
14
14
  Classifier: Programming Language :: Python :: 3.11
15
15
  Classifier: Programming Language :: Python :: 3.12
16
16
  Requires-Python: >=3.10
17
- Requires-Dist: iii-observability==0.19.0.dev2
17
+ Requires-Dist: iii-observability==0.19.0.dev4
18
18
  Requires-Dist: opentelemetry-api>=1.25
19
19
  Requires-Dist: pydantic>=2.0
20
20
  Requires-Dist: websockets>=12.0
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "iii-sdk"
7
- version = "0.19.0.dev2"
7
+ version = "0.19.0.dev4"
8
8
  description = "III SDK for Python"
9
9
  authors = [{ name = "III" }]
10
10
  license = { text = "Apache-2.0" }
@@ -23,7 +23,7 @@ dependencies = [
23
23
  "websockets>=12.0",
24
24
  "pydantic>=2.0",
25
25
  "opentelemetry-api>=1.25",
26
- "iii-observability==0.19.0.dev2",
26
+ "iii-observability==0.19.0.dev4",
27
27
  ]
28
28
 
29
29
  [project.urls]
@@ -1,34 +1,7 @@
1
1
  """III SDK for Python."""
2
2
 
3
- from iii_observability import (
4
- DEFAULT_ALLOWLIST,
5
- REDACTED_PLACEHOLDER,
6
- BaggageSpanProcessor,
7
- Logger,
8
- OtelConfig,
9
- ReconnectionConfig,
10
- current_span_id,
11
- current_span_is_recording,
12
- current_trace_id,
13
- execute_traced_request,
14
- extract_baggage,
15
- extract_traceparent,
16
- flush_otel,
17
- init_otel,
18
- inject_baggage,
19
- inject_traceparent,
20
- record_span_event,
21
- redact,
22
- redact_and_truncate,
23
- resolve_max_bytes_from_env,
24
- set_current_span_attribute,
25
- set_current_span_error,
26
- shutdown_otel,
27
- with_span,
28
- )
29
-
30
3
  from .channels import ChannelReader, ChannelWriter
31
- from .errors import IIIForbiddenError, IIIInvocationError, IIITimeoutError
4
+ from .errors import IIIInvocationError
32
5
  from .format_utils import extract_request_format, extract_response_format, python_type_to_format
33
6
  from .iii import TriggerAction, register_worker
34
7
  from .iii_constants import FunctionRef, InitOptions, TelemetryOptions
@@ -80,40 +53,14 @@ from .types import (
80
53
  from .utils import http
81
54
 
82
55
  __all__ = [
83
- # Telemetry helpers
84
- "BaggageSpanProcessor",
85
- "DEFAULT_ALLOWLIST",
86
- "REDACTED_PLACEHOLDER",
87
- "current_span_id",
88
- "current_span_is_recording",
89
- "current_trace_id",
90
- "execute_traced_request",
91
- "extract_baggage",
92
- "extract_traceparent",
93
- "flush_otel",
94
- "init_otel",
95
- "inject_baggage",
96
- "inject_traceparent",
97
- "record_span_event",
98
- "redact",
99
- "redact_and_truncate",
100
- "resolve_max_bytes_from_env",
101
- "set_current_span_attribute",
102
- "set_current_span_error",
103
- "shutdown_otel",
104
- "with_span",
105
56
  # Channels
106
57
  "ChannelReader",
107
58
  "ChannelWriter",
108
59
  # Errors
109
- "IIIForbiddenError",
110
60
  "IIIInvocationError",
111
- "IIITimeoutError",
112
61
  # Core
113
62
  "FunctionRef",
114
63
  "InitOptions",
115
- "OtelConfig",
116
- "ReconnectionConfig",
117
64
  "register_worker",
118
65
  "TelemetryOptions",
119
66
  "TriggerAction",
@@ -142,8 +89,6 @@ __all__ = [
142
89
  "TriggerActionEnqueue",
143
90
  "TriggerActionVoid",
144
91
  "TriggerRequest",
145
- # Logger
146
- "Logger",
147
92
  # Triggers
148
93
  "Trigger",
149
94
  "TriggerConfig",
@@ -6,13 +6,10 @@ from typing import Any
6
6
  class IIIInvocationError(Exception):
7
7
  """Raised when an invocation dispatched by the SDK fails.
8
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``.
9
+ Inspect ``err.code`` to react to a specific category (e.g.
10
+ ``'FORBIDDEN'`` for RBAC denials, ``'TIMEOUT'`` for timeouts). Catch
11
+ this class to handle every rejection. ``except Exception`` continues to
12
+ work because ``IIIInvocationError`` inherits from ``Exception``.
16
13
 
17
14
  Attributes are read-only after construction. ``stacktrace`` is the
18
15
  engine-side trace when the remote handler raised; it may include
@@ -36,26 +33,18 @@ class IIIInvocationError(Exception):
36
33
  self.invocation_id = invocation_id
37
34
 
38
35
 
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
36
  def _wrap_wire_error(
48
37
  error: Any,
49
38
  *,
50
39
  function_id: str | None,
51
40
  invocation_id: str | None,
52
41
  ) -> IIIInvocationError:
53
- """Convert a wire ``ErrorBody``-shaped dict into a typed exception.
42
+ """Convert a wire ``ErrorBody``-shaped dict into an ``IIIInvocationError``.
54
43
 
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.
44
+ The ``code`` field distinguishes categories (e.g. ``'FORBIDDEN'``,
45
+ ``'TIMEOUT'``). Malformed shapes (non-dict, missing fields, non-string
46
+ values) fall back to ``code='UNKNOWN'`` so no rejection path prints as a
47
+ raw dict repr.
59
48
  """
60
49
  if isinstance(error, dict):
61
50
  raw_code = error.get("code")
@@ -67,12 +56,7 @@ def _wrap_wire_error(
67
56
  raw_stacktrace = error.get("stacktrace")
68
57
  stacktrace = raw_stacktrace if isinstance(raw_stacktrace, str) else None
69
58
 
70
- cls: type[IIIInvocationError] = {
71
- "FORBIDDEN": IIIForbiddenError,
72
- "TIMEOUT": IIITimeoutError,
73
- }.get(code, IIIInvocationError)
74
-
75
- return cls(
59
+ return IIIInvocationError(
76
60
  code=code,
77
61
  message=message,
78
62
  function_id=function_id,
@@ -18,7 +18,7 @@ from iii_observability import OtelConfig
18
18
  from websockets.asyncio.client import ClientConnection
19
19
 
20
20
  from .channels import ChannelReader, ChannelWriter
21
- from .errors import IIIInvocationError, IIITimeoutError, _wrap_wire_error
21
+ from .errors import IIIInvocationError, _wrap_wire_error
22
22
  from .format_utils import extract_request_format, extract_response_format
23
23
  from .iii_constants import (
24
24
  DEFAULT_RECONNECTION_CONFIG,
@@ -482,6 +482,7 @@ class III:
482
482
 
483
483
  async def _invoke_with_otel_context(
484
484
  self,
485
+ function_id: str,
485
486
  handler: Callable[[Any], Awaitable[Any]],
486
487
  data: Any,
487
488
  traceparent: str | None,
@@ -509,10 +510,16 @@ class III:
509
510
  )
510
511
  payload_max_bytes = resolve_max_bytes_from_env()
511
512
 
513
+ # INTERNAL and named `execute` (not `call`/`trigger`): the engine
514
+ # already emits the SERVER `call <fn>` span for this hop AND a
515
+ # `trigger <fn>` span from fire_triggers. Reusing either name would
516
+ # duplicate an engine span under the worker's service. `execute` is
517
+ # unique, so the worker handler span reads as a clean internal child
518
+ # of the engine's call span (and is collapsible by a single rule).
512
519
  with tracer.start_as_current_span(
513
- f"call {handler.__name__}",
520
+ f"execute {function_id}",
514
521
  context=parent_ctx,
515
- kind=trace.SpanKind.SERVER,
522
+ kind=trace.SpanKind.INTERNAL,
516
523
  ) as span:
517
524
  if trace_payloads and span.is_recording():
518
525
  input_json, input_truncated = redact_and_truncate(data, payload_max_bytes)
@@ -620,7 +627,7 @@ class III:
620
627
  if not invocation_id:
621
628
  task = asyncio.create_task(
622
629
  self._invoke_with_otel_context(
623
- func.handler, resolved_data, traceparent, baggage
630
+ path, func.handler, resolved_data, traceparent, baggage
624
631
  )
625
632
  )
626
633
  task.add_done_callback(self._log_task_exception)
@@ -628,6 +635,7 @@ class III:
628
635
 
629
636
  try:
630
637
  result, response_traceparent = await self._invoke_with_otel_context(
638
+ path,
631
639
  func.handler,
632
640
  resolved_data,
633
641
  traceparent,
@@ -1073,9 +1081,9 @@ class III:
1073
1081
  actions.
1074
1082
 
1075
1083
  Raises:
1076
- IIITimeoutError: If the invocation times out. ``code == 'TIMEOUT'``.
1077
- IIIForbiddenError: If RBAC denies the invocation. ``code == 'FORBIDDEN'``.
1078
- IIIInvocationError: For any other engine rejection.
1084
+ IIIInvocationError: For any engine rejection. Inspect ``code``:
1085
+ ``'TIMEOUT'`` if the invocation timed out, ``'FORBIDDEN'`` if
1086
+ RBAC denied it.
1079
1087
 
1080
1088
  Examples:
1081
1089
  >>> result = iii.trigger({'function_id': 'greet', 'payload': {'name': 'World'}})
@@ -1100,9 +1108,9 @@ class III:
1100
1108
  The result of the function invocation, or ``None`` for void calls.
1101
1109
 
1102
1110
  Raises:
1103
- IIITimeoutError: If the invocation times out. ``code == 'TIMEOUT'``.
1104
- IIIForbiddenError: If RBAC denies the invocation. ``code == 'FORBIDDEN'``.
1105
- IIIInvocationError: For any other engine rejection.
1111
+ IIIInvocationError: For any engine rejection. Inspect ``code``:
1112
+ ``'TIMEOUT'`` if the invocation timed out, ``'FORBIDDEN'`` if
1113
+ RBAC denied it.
1106
1114
 
1107
1115
  Examples:
1108
1116
  >>> result = await iii.trigger_async({'function_id': 'greet', 'payload': {'name': 'World'}})
@@ -1163,7 +1171,7 @@ class III:
1163
1171
  return await asyncio.wait_for(future, timeout=timeout_secs)
1164
1172
  except asyncio.TimeoutError:
1165
1173
  self._pending.pop(invocation_id, None)
1166
- raise IIITimeoutError(
1174
+ raise IIIInvocationError(
1167
1175
  code="TIMEOUT",
1168
1176
  message=f"invocation timed out after {timeout_ms}ms",
1169
1177
  function_id=function_id,
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import pytest
6
6
 
7
- from iii import IIIForbiddenError, IIIInvocationError, IIITimeoutError
7
+ from iii import IIIInvocationError
8
8
  from iii.errors import _wrap_wire_error
9
9
 
10
10
 
@@ -57,34 +57,19 @@ class TestIIIInvocationError:
57
57
  assert str(err) == "TIMEOUT: gone"
58
58
 
59
59
 
60
- class TestSubclassHierarchy:
61
- def test_forbidden_is_invocation_error(self) -> None:
62
- err = IIIForbiddenError(code="FORBIDDEN", message="x")
63
- assert isinstance(err, IIIInvocationError)
64
- assert isinstance(err, IIIForbiddenError)
65
- assert isinstance(err, Exception)
66
-
67
- def test_timeout_is_invocation_error(self) -> None:
68
- err = IIITimeoutError(code="TIMEOUT", message="x")
69
- assert isinstance(err, IIIInvocationError)
70
- assert isinstance(err, IIITimeoutError)
71
- assert isinstance(err, Exception)
60
+ class TestCodeDiscrimination:
61
+ def test_categories_discriminated_by_code(self) -> None:
62
+ """Categories are distinguished by ``code``, not by subclass."""
63
+ for code in ("FORBIDDEN", "TIMEOUT", "UNKNOWN"):
64
+ err = IIIInvocationError(code=code, message="x")
65
+ assert isinstance(err, IIIInvocationError)
66
+ assert isinstance(err, Exception)
67
+ assert err.code == code
72
68
 
73
- def test_except_ordering_catches_subclass_first(self) -> None:
74
- """`except IIIForbiddenError` fires before `except IIIInvocationError`."""
75
- caught: str | None = None
76
- try:
77
- raise IIIForbiddenError(code="FORBIDDEN", message="x")
78
- except IIIForbiddenError:
79
- caught = "forbidden"
80
- except IIIInvocationError:
81
- caught = "base"
82
- assert caught == "forbidden"
83
-
84
- def test_base_catches_every_subclass(self) -> None:
69
+ def test_base_catches_every_category(self) -> None:
85
70
  for err in (
86
- IIIForbiddenError(code="FORBIDDEN", message="x"),
87
- IIITimeoutError(code="TIMEOUT", message="x"),
71
+ IIIInvocationError(code="FORBIDDEN", message="x"),
72
+ IIIInvocationError(code="TIMEOUT", message="x"),
88
73
  IIIInvocationError(code="UNKNOWN", message="x"),
89
74
  ):
90
75
  try:
@@ -95,30 +80,30 @@ class TestSubclassHierarchy:
95
80
  def test_except_exception_still_works(self) -> None:
96
81
  """Migration guarantee: existing `except Exception:` handlers still catch."""
97
82
  try:
98
- raise IIIForbiddenError(code="FORBIDDEN", message="x")
83
+ raise IIIInvocationError(code="FORBIDDEN", message="x")
99
84
  except Exception as got:
100
85
  assert isinstance(got, IIIInvocationError)
101
86
 
102
87
 
103
88
  class TestWrapWireError:
104
- def test_forbidden_dict_dispatches_to_forbidden_error(self) -> None:
89
+ def test_forbidden_dict_sets_forbidden_code(self) -> None:
105
90
  err = _wrap_wire_error(
106
91
  {"code": "FORBIDDEN", "message": "not allowed"},
107
92
  function_id="engine::functions::list",
108
93
  invocation_id="inv-1",
109
94
  )
110
- assert isinstance(err, IIIForbiddenError)
95
+ assert type(err) is IIIInvocationError
111
96
  assert err.code == "FORBIDDEN"
112
97
  assert err.function_id == "engine::functions::list"
113
98
  assert err.invocation_id == "inv-1"
114
99
 
115
- def test_timeout_dict_dispatches_to_timeout_error(self) -> None:
100
+ def test_timeout_dict_sets_timeout_code(self) -> None:
116
101
  err = _wrap_wire_error(
117
102
  {"code": "TIMEOUT", "message": "gone"},
118
103
  function_id="api::slow",
119
104
  invocation_id=None,
120
105
  )
121
- assert isinstance(err, IIITimeoutError)
106
+ assert type(err) is IIIInvocationError
122
107
  assert err.code == "TIMEOUT"
123
108
 
124
109
  def test_unknown_code_falls_back_to_base(self) -> None:
@@ -34,7 +34,7 @@ async def test_invoke_with_otel_context_records_exception_with_stacktrace():
34
34
  raise ValueError("test invocation error")
35
35
 
36
36
  with pytest.raises(_TraceContextError) as exc_info:
37
- await client._invoke_with_otel_context(failing_handler, {"key": "value"}, None, None)
37
+ await client._invoke_with_otel_context("test.fn", failing_handler, {"key": "value"}, None, None)
38
38
  assert isinstance(exc_info.value.__cause__, ValueError)
39
39
  assert "test invocation error" in str(exc_info.value.__cause__)
40
40
 
@@ -81,7 +81,7 @@ async def test_invoke_with_otel_context_success_no_exception():
81
81
  async def success_handler(data):
82
82
  return {"result": "ok"}
83
83
 
84
- result, traceparent = await client._invoke_with_otel_context(success_handler, {"key": "value"}, None, None)
84
+ result, traceparent = await client._invoke_with_otel_context("test.fn", success_handler, {"key": "value"}, None, None)
85
85
 
86
86
  assert result == {"result": "ok"}
87
87
 
@@ -8,7 +8,6 @@ import pytest
8
8
  from iii import (
9
9
  AuthInput,
10
10
  AuthResult,
11
- IIIForbiddenError,
12
11
  IIIInvocationError,
13
12
  InitOptions,
14
13
  MiddlewareFunctionInput,
@@ -350,22 +349,21 @@ class TestRbacWorkers:
350
349
  iii_client.shutdown()
351
350
 
352
351
  def test_forbidden_wrapped_as_typed_error(self, iii_server):
353
- """FORBIDDEN rejections surface as IIIForbiddenError with function_id set
354
- and the engine's remediation phrase in the message."""
352
+ """FORBIDDEN rejections surface as IIIInvocationError (code='FORBIDDEN')
353
+ with function_id set and the engine's remediation phrase in the message."""
355
354
  iii_client = register_worker(
356
355
  EW_URL,
357
356
  InitOptions(otel={"enabled": False}, headers={"x-test-token": "valid-token"}),
358
357
  )
359
358
 
360
359
  try:
361
- with pytest.raises(IIIForbiddenError) as excinfo:
360
+ with pytest.raises(IIIInvocationError) as excinfo:
362
361
  iii_client.trigger({
363
362
  "function_id": "test::ew::private",
364
363
  "payload": {},
365
364
  })
366
365
 
367
366
  err = excinfo.value
368
- assert isinstance(err, IIIInvocationError) # base class
369
367
  assert err.code == "FORBIDDEN"
370
368
  assert err.function_id == "test::ew::private"
371
369
  assert "FORBIDDEN" in str(err)
@@ -432,7 +430,7 @@ class TestRbacWorkers:
432
430
  try:
433
431
  def handler(data: dict) -> dict:
434
432
  # If the carve-out regresses, this nested trigger surfaces
435
- # IIIForbiddenError and the handler propagates it as a failure.
433
+ # IIIInvocationError (code='FORBIDDEN') and the handler propagates it as a failure.
436
434
  iii_client.trigger({
437
435
  "function_id": "engine::log::info",
438
436
  "payload": {
@@ -473,7 +471,7 @@ class TestRbacWorkers:
473
471
 
474
472
  try:
475
473
  # No exception == carve-out is working. If this raises
476
- # IIIForbiddenError, the carve-out regressed.
474
+ # IIIInvocationError (code='FORBIDDEN'), the carve-out regressed.
477
475
  iii_client.trigger({
478
476
  "function_id": "engine::log::info",
479
477
  "payload": {"message": "carve-out direct invocation"},
@@ -546,7 +546,7 @@ wheels = [
546
546
 
547
547
  [[package]]
548
548
  name = "iii-observability"
549
- version = "0.16.0.dev3"
549
+ version = "0.18.0"
550
550
  source = { editable = "../observability" }
551
551
  dependencies = [
552
552
  { name = "httpx" },
@@ -572,7 +572,7 @@ provides-extras = ["dev"]
572
572
 
573
573
  [[package]]
574
574
  name = "iii-sdk"
575
- version = "0.16.0.dev3"
575
+ version = "0.18.0"
576
576
  source = { editable = "." }
577
577
  dependencies = [
578
578
  { name = "iii-observability" },
File without changes
File without changes