dex-python-sdk 0.12.1__tar.gz → 0.13.10__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 (65) hide show
  1. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/PKG-INFO +29 -1
  2. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/README.md +28 -0
  3. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/__init__.py +2 -0
  4. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/async_client.py +32 -4
  5. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/client.py +32 -4
  6. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/flow.py +63 -0
  7. dex_python_sdk-0.13.10/dex/rpc_invoke_options.py +42 -0
  8. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/pyproject.toml +1 -1
  9. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/LEGACY_NOTICES.md +0 -0
  10. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/LICENSE +0 -0
  11. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/_async_value_hydrator.py +0 -0
  12. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/_async_worker_dispatcher.py +0 -0
  13. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/_async_worker_service.py +0 -0
  14. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/_grpc_errors.py +0 -0
  15. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/_invocation_context.py +0 -0
  16. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/_native.pyi +0 -0
  17. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/_server_protocol.py +0 -0
  18. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/_utils.py +0 -0
  19. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/_value_hydrator.py +0 -0
  20. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/_value_mapper.py +0 -0
  21. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/_worker_dispatcher.py +0 -0
  22. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/_worker_service.py +0 -0
  23. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/async_worker.py +0 -0
  24. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/attribute.py +0 -0
  25. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/attribute_match.py +0 -0
  26. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/blob_cache.py +0 -0
  27. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/channel.py +0 -0
  28. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/client_options.py +0 -0
  29. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/codec.py +0 -0
  30. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/condition.py +0 -0
  31. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/context.py +0 -0
  32. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/dexpb/__init__.py +0 -0
  33. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/dexpb/dex_pb2.py +0 -0
  34. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/dexpb/dex_pb2.pyi +0 -0
  35. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/dexpb/dex_pb2_grpc.py +0 -0
  36. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/flow_config.py +0 -0
  37. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/flow_info.py +0 -0
  38. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/flow_options.py +0 -0
  39. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/flow_result.py +0 -0
  40. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/py.typed +0 -0
  41. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/runtime_errors.py +0 -0
  42. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/step.py +0 -0
  43. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/step_execution.py +0 -0
  44. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/stream.py +0 -0
  45. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/subflow.py +0 -0
  46. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/timer.py +0 -0
  47. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/wait.py +0 -0
  48. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/wait_options.py +0 -0
  49. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/worker.py +0 -0
  50. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/dex/worker_options.py +0 -0
  51. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/sdk-rust/Cargo.lock +0 -0
  52. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/sdk-rust/Cargo.toml +0 -0
  53. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/sdk-rust/crates/dex-blob-cache/Cargo.toml +0 -0
  54. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/sdk-rust/crates/dex-blob-cache/LICENSE +0 -0
  55. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/sdk-rust/crates/dex-blob-cache/src/config.rs +0 -0
  56. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/sdk-rust/crates/dex-blob-cache/src/entry.rs +0 -0
  57. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/sdk-rust/crates/dex-blob-cache/src/error.rs +0 -0
  58. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/sdk-rust/crates/dex-blob-cache/src/format.rs +0 -0
  59. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/sdk-rust/crates/dex-blob-cache/src/lib.rs +0 -0
  60. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/sdk-rust/crates/dex-blob-cache/src/policy.rs +0 -0
  61. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/sdk-rust/crates/dex-blob-cache/src/store.rs +0 -0
  62. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/sdk-rust/crates/dex-blob-cache/tests/blob_cache_integration.rs +0 -0
  63. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/sdk-rust/crates/dex-blob-cache-python/Cargo.toml +0 -0
  64. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/sdk-rust/crates/dex-blob-cache-python/LICENSE +0 -0
  65. {dex_python_sdk-0.12.1 → dex_python_sdk-0.13.10}/sdk-rust/crates/dex-blob-cache-python/src/lib.rs +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dex-python-sdk
3
- Version: 0.12.1
3
+ Version: 0.13.10
4
4
  Requires-Dist: grpcio>=1.83.0
5
5
  Requires-Dist: grpcio-status>=1.83.0
6
6
  Requires-Dist: protobuf>=7.35.1
@@ -58,6 +58,34 @@ atomic commit and Channel deletion validation. Attribute locks add isolation
58
58
  only among cooperating Steps and RPCs using the same lock. Write-only publish
59
59
  and delete operations do not require loading.
60
60
 
61
+ ### Invocation-time map instances
62
+
63
+ The `@rpc` decorator remains the registration-time contract for timeout,
64
+ transactionality, locks, and fixed state loads. Pass frozen `RPCInvokeOptions`
65
+ through the keyword-only `options` argument when the caller chooses an exact map
66
+ instance at runtime:
67
+
68
+ ```python
69
+ options = dex.RPCInvokeOptions(
70
+ lock_attribute_map_instances=(profiles.lock(partition_name),),
71
+ load_attribute_map_instances=(profiles.load(partition_name),),
72
+ )
73
+ result = client.invoke_rpc(
74
+ directory.upsert_customer_profile,
75
+ flow_id,
76
+ profile,
77
+ options=options,
78
+ )
79
+ ```
80
+
81
+ `Client` and `AsyncClient` union, sort, and deduplicate these selections with the
82
+ registered ones. Locking and loading are independent; read-modify-write handlers
83
+ need both for the same instance.
84
+
85
+ See the runnable [sequential chunking](../examples/python/dex_examples/patterns/sequentially-chunked-attribute-map)
86
+ and [hash partitioning](../examples/python/dex_examples/patterns/hash-partitioned-attribute-map)
87
+ patterns for complete async call sites.
88
+
61
89
  ## Step and timeout-handler state loading
62
90
 
63
91
  `StepOptions` provides the same five selections independently for `wait_for` and
@@ -42,6 +42,34 @@ atomic commit and Channel deletion validation. Attribute locks add isolation
42
42
  only among cooperating Steps and RPCs using the same lock. Write-only publish
43
43
  and delete operations do not require loading.
44
44
 
45
+ ### Invocation-time map instances
46
+
47
+ The `@rpc` decorator remains the registration-time contract for timeout,
48
+ transactionality, locks, and fixed state loads. Pass frozen `RPCInvokeOptions`
49
+ through the keyword-only `options` argument when the caller chooses an exact map
50
+ instance at runtime:
51
+
52
+ ```python
53
+ options = dex.RPCInvokeOptions(
54
+ lock_attribute_map_instances=(profiles.lock(partition_name),),
55
+ load_attribute_map_instances=(profiles.load(partition_name),),
56
+ )
57
+ result = client.invoke_rpc(
58
+ directory.upsert_customer_profile,
59
+ flow_id,
60
+ profile,
61
+ options=options,
62
+ )
63
+ ```
64
+
65
+ `Client` and `AsyncClient` union, sort, and deduplicate these selections with the
66
+ registered ones. Locking and loading are independent; read-modify-write handlers
67
+ need both for the same instance.
68
+
69
+ See the runnable [sequential chunking](../examples/python/dex_examples/patterns/sequentially-chunked-attribute-map)
70
+ and [hash partitioning](../examples/python/dex_examples/patterns/hash-partitioned-attribute-map)
71
+ patterns for complete async call sites.
72
+
45
73
  ## Step and timeout-handler state loading
46
74
 
47
75
  `StepOptions` provides the same five selections independently for `wait_for` and
@@ -79,6 +79,7 @@ from dex.runtime_errors import (
79
79
  ValueMappingError,
80
80
  WorkerInvocationError,
81
81
  )
82
+ from dex.rpc_invoke_options import RPCInvokeOptions
82
83
  from dex.step import (
83
84
  RetryPolicy,
84
85
  Step,
@@ -169,6 +170,7 @@ __all__ = [
169
170
  "RequestTimeoutError",
170
171
  "PersistenceSchema",
171
172
  "RPCResult",
173
+ "RPCInvokeOptions",
172
174
  "RecoveryErrorInfo",
173
175
  "RetryAfterError",
174
176
  "RpcLockConflictError",
@@ -46,6 +46,7 @@ from dex.flow_options import (
46
46
  )
47
47
  from dex.flow_result import FlowResult, flow_result_from_proto
48
48
  from dex.runtime_errors import DexServiceError, FlowErrorType, LongPollTimeoutError
49
+ from dex.rpc_invoke_options import RPCInvokeOptions
49
50
  from dex.step import RetryPolicy, StepDurability
50
51
  from dex.step_execution import StepExecutionId, TimerId
51
52
  from dex.stream import Stream, StreamMessage, StreamMessagesPage
@@ -214,6 +215,7 @@ class AsyncClient:
214
215
  input: InputT,
215
216
  *,
216
217
  run_id: str = "",
218
+ options: RPCInvokeOptions = RPCInvokeOptions(),
217
219
  ) -> OutputT: ...
218
220
 
219
221
  @overload
@@ -223,6 +225,7 @@ class AsyncClient:
223
225
  flow_id: str,
224
226
  *,
225
227
  run_id: str = "",
228
+ options: RPCInvokeOptions = RPCInvokeOptions(),
226
229
  ) -> OutputT: ...
227
230
 
228
231
  @overload
@@ -233,6 +236,7 @@ class AsyncClient:
233
236
  input: InputT,
234
237
  *,
235
238
  run_id: str = "",
239
+ options: RPCInvokeOptions = RPCInvokeOptions(),
236
240
  ) -> None: ...
237
241
 
238
242
  @overload
@@ -242,6 +246,7 @@ class AsyncClient:
242
246
  flow_id: str,
243
247
  *,
244
248
  run_id: str = "",
249
+ options: RPCInvokeOptions = RPCInvokeOptions(),
245
250
  ) -> None: ...
246
251
 
247
252
  async def invoke_rpc(
@@ -251,6 +256,7 @@ class AsyncClient:
251
256
  input: object = None,
252
257
  *,
253
258
  run_id: str = "",
259
+ options: RPCInvokeOptions = RPCInvokeOptions(),
254
260
  ) -> Any:
255
261
  """Invoke a registered RPC and await its typed result.
256
262
 
@@ -267,6 +273,7 @@ class AsyncClient:
267
273
  input: The annotated input, or ``None`` for an input-free RPC.
268
274
  run_id: Optional exact run. With ``""``, the server resolves the current
269
275
  execution.
276
+ options: Additive runtime AttributeMap locks and exact map-instance loads.
270
277
 
271
278
  Returns:
272
279
  The decoded ``RPCResult.output``, or ``None`` for a no-output RPC.
@@ -279,7 +286,12 @@ class AsyncClient:
279
286
  DexServiceError: If the selected path requires an active execution or the
280
287
  service call otherwise fails.
281
288
  """
282
- _, rpc = self.registry._rpc_for_method(rpc_method)
289
+ registered_flow, rpc = self.registry._rpc_for_method(rpc_method)
290
+ (
291
+ invocation_locks,
292
+ invocation_attribute_map_loads,
293
+ invocation_channel_map_loads,
294
+ ) = self.registry._resolve_rpc_invoke_options(registered_flow, options)
283
295
  encoded_input = (
284
296
  self._values.encode(input, rpc.input_codec)
285
297
  if rpc.input_codec is not None
@@ -300,12 +312,28 @@ class AsyncClient:
300
312
  rpc_name=rpc.name,
301
313
  input=encoded_input,
302
314
  timeout_seconds=timeout,
303
- lock_attribute_keys=rpc.locks,
315
+ lock_attribute_keys=tuple(
316
+ sorted(set(rpc.locks + invocation_locks))
317
+ ),
304
318
  request_id=str(uuid4()),
305
319
  is_transactional=rpc.options.is_transactional,
306
- load_attribute_map_instances=rpc.load_attribute_map_instances,
320
+ load_attribute_map_instances=tuple(
321
+ sorted(
322
+ set(
323
+ rpc.load_attribute_map_instances
324
+ + invocation_attribute_map_loads
325
+ )
326
+ )
327
+ ),
307
328
  load_channel_names=rpc.load_channel_names,
308
- load_channel_map_instances=rpc.load_channel_map_instances,
329
+ load_channel_map_instances=tuple(
330
+ sorted(
331
+ set(
332
+ rpc.load_channel_map_instances
333
+ + invocation_channel_map_loads
334
+ )
335
+ )
336
+ ),
309
337
  ),
310
338
  "invoke_rpc",
311
339
  flow_id,
@@ -46,6 +46,7 @@ from dex.flow_options import (
46
46
  )
47
47
  from dex.flow_result import FlowResult, flow_result_from_proto
48
48
  from dex.runtime_errors import DexServiceError, FlowErrorType, LongPollTimeoutError
49
+ from dex.rpc_invoke_options import RPCInvokeOptions
49
50
  from dex.step import RetryPolicy, StepDurability
50
51
  from dex.step_execution import StepExecutionId, TimerId
51
52
  from dex.stream import Stream, StreamMessage, StreamMessagesPage
@@ -213,6 +214,7 @@ class Client:
213
214
  input: InputT,
214
215
  *,
215
216
  run_id: str = "",
217
+ options: RPCInvokeOptions = RPCInvokeOptions(),
216
218
  ) -> OutputT: ...
217
219
 
218
220
  @overload
@@ -222,6 +224,7 @@ class Client:
222
224
  flow_id: str,
223
225
  *,
224
226
  run_id: str = "",
227
+ options: RPCInvokeOptions = RPCInvokeOptions(),
225
228
  ) -> OutputT: ...
226
229
 
227
230
  @overload
@@ -232,6 +235,7 @@ class Client:
232
235
  input: InputT,
233
236
  *,
234
237
  run_id: str = "",
238
+ options: RPCInvokeOptions = RPCInvokeOptions(),
235
239
  ) -> None: ...
236
240
 
237
241
  @overload
@@ -241,6 +245,7 @@ class Client:
241
245
  flow_id: str,
242
246
  *,
243
247
  run_id: str = "",
248
+ options: RPCInvokeOptions = RPCInvokeOptions(),
244
249
  ) -> None: ...
245
250
 
246
251
  def invoke_rpc(
@@ -250,6 +255,7 @@ class Client:
250
255
  input: object = None,
251
256
  *,
252
257
  run_id: str = "",
258
+ options: RPCInvokeOptions = RPCInvokeOptions(),
253
259
  ) -> Any:
254
260
  """Synchronously invoke a registered RPC on a Flow execution.
255
261
 
@@ -266,6 +272,7 @@ class Client:
266
272
  input: The annotated RPC input, or ``None`` for an input-free RPC.
267
273
  run_id: Optional exact run. With ``""``, the server resolves the current
268
274
  execution.
275
+ options: Additive runtime AttributeMap locks and exact map-instance loads.
269
276
 
270
277
  Returns:
271
278
  The decoded ``RPCResult.output``, or ``None`` for a no-output RPC.
@@ -278,7 +285,12 @@ class Client:
278
285
  DexServiceError: If the selected path requires an active execution or the
279
286
  service call otherwise fails.
280
287
  """
281
- _, rpc = self.registry._rpc_for_method(rpc_method)
288
+ registered_flow, rpc = self.registry._rpc_for_method(rpc_method)
289
+ (
290
+ invocation_locks,
291
+ invocation_attribute_map_loads,
292
+ invocation_channel_map_loads,
293
+ ) = self.registry._resolve_rpc_invoke_options(registered_flow, options)
282
294
  encoded_input = (
283
295
  self._values.encode(input, rpc.input_codec)
284
296
  if rpc.input_codec is not None
@@ -299,12 +311,28 @@ class Client:
299
311
  rpc_name=rpc.name,
300
312
  input=encoded_input,
301
313
  timeout_seconds=timeout,
302
- lock_attribute_keys=rpc.locks,
314
+ lock_attribute_keys=tuple(
315
+ sorted(set(rpc.locks + invocation_locks))
316
+ ),
303
317
  request_id=str(uuid4()),
304
318
  is_transactional=rpc.options.is_transactional,
305
- load_attribute_map_instances=rpc.load_attribute_map_instances,
319
+ load_attribute_map_instances=tuple(
320
+ sorted(
321
+ set(
322
+ rpc.load_attribute_map_instances
323
+ + invocation_attribute_map_loads
324
+ )
325
+ )
326
+ ),
306
327
  load_channel_names=rpc.load_channel_names,
307
- load_channel_map_instances=rpc.load_channel_map_instances,
328
+ load_channel_map_instances=tuple(
329
+ sorted(
330
+ set(
331
+ rpc.load_channel_map_instances
332
+ + invocation_channel_map_loads
333
+ )
334
+ )
335
+ ),
308
336
  ),
309
337
  "invoke_rpc",
310
338
  flow_id,
@@ -41,6 +41,7 @@ from dex.codec import Codec, CodecRegistry
41
41
  from dex.context import AsyncContext, Context
42
42
  from dex.dexpb import dex_pb2 as pb
43
43
  from dex.runtime_errors import FlowDefinitionError
44
+ from dex.rpc_invoke_options import RPCInvokeOptions
44
45
  from dex.step import Step, StepDecision, StepList, StepMovement, StepOutput, _StepDef
45
46
  from dex.stream import Stream
46
47
  from dex.wait import Wait
@@ -969,3 +970,65 @@ class Registry:
969
970
  if registered_function is function:
970
971
  return flow, registered_rpc
971
972
  raise FlowDefinitionError("RPC method is not registered")
973
+
974
+ def _resolve_rpc_invoke_options(
975
+ self,
976
+ flow: _RegisteredFlow,
977
+ options: RPCInvokeOptions,
978
+ ) -> tuple[tuple[str, ...], tuple[str, ...], tuple[str, ...]]:
979
+ lock_attribute_map_instances: list[str] = []
980
+ for lock in options.lock_attribute_map_instances:
981
+ if not isinstance(lock, AttributeLock) or not isinstance(
982
+ lock.attribute, AttributeMap
983
+ ):
984
+ raise FlowDefinitionError(
985
+ "RPCInvokeOptions lock must target an AttributeMap instance"
986
+ )
987
+ if lock.instance is None:
988
+ raise FlowDefinitionError(
989
+ "RPCInvokeOptions AttributeMap lock needs an instance"
990
+ )
991
+ self._require_rpc_invoke_definition(flow, lock.attribute, "AttributeMap")
992
+ lock_attribute_map_instances.append(
993
+ lock.attribute.load(lock.instance).physical_name
994
+ )
995
+
996
+ load_attribute_map_instances: list[str] = []
997
+ for attribute_map_load in options.load_attribute_map_instances:
998
+ if not isinstance(attribute_map_load, AttributeMapLoad):
999
+ raise FlowDefinitionError(
1000
+ "RPCInvokeOptions has an invalid AttributeMap load"
1001
+ )
1002
+ self._require_rpc_invoke_definition(
1003
+ flow, attribute_map_load.attribute_map, "AttributeMap"
1004
+ )
1005
+ load_attribute_map_instances.append(attribute_map_load.physical_name)
1006
+
1007
+ load_channel_map_instances: list[str] = []
1008
+ for channel_map_load in options.load_channel_map_instances:
1009
+ if not isinstance(channel_map_load, ChannelMapLoad):
1010
+ raise FlowDefinitionError(
1011
+ "RPCInvokeOptions has an invalid ChannelMap load"
1012
+ )
1013
+ self._require_rpc_invoke_definition(
1014
+ flow, channel_map_load.channel_map, "ChannelMap"
1015
+ )
1016
+ load_channel_map_instances.append(channel_map_load.physical_name)
1017
+
1018
+ return (
1019
+ tuple(sorted(set(lock_attribute_map_instances))),
1020
+ tuple(sorted(set(load_attribute_map_instances))),
1021
+ tuple(sorted(set(load_channel_map_instances))),
1022
+ )
1023
+
1024
+ @staticmethod
1025
+ def _require_rpc_invoke_definition(
1026
+ flow: _RegisteredFlow,
1027
+ definition: object,
1028
+ kind: str,
1029
+ ) -> None:
1030
+ registered = flow.persistence.get(getattr(definition, "name", ""))
1031
+ if registered is not definition:
1032
+ raise FlowDefinitionError(
1033
+ f"Flow {flow.name} does not register {kind} definition"
1034
+ )
@@ -0,0 +1,42 @@
1
+ # Copyright (c) 2026 Super Durable, Inc.
2
+ #
3
+ # Licensed under the Sustainable Use License 1.0.
4
+ # You may not use this file except in compliance with the License.
5
+ # See the LICENSE file in the repository root.
6
+ #
7
+ # SPDX-License-Identifier: LicenseRef-Sustainable-Use-1.0
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass
12
+
13
+ from dex.attribute import AttributeLock, AttributeMapLoad
14
+ from dex.channel import ChannelMapLoad
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class RPCInvokeOptions:
19
+ """Add runtime-selected map instances to one RPC invocation.
20
+
21
+ Dex unions these selections with the RPC decorator's fixed locks and state loads,
22
+ then sorts and deduplicates their physical names. Locking and loading are
23
+ independent. A read-modify-write handler must include the same AttributeMap
24
+ instance in both collections.
25
+
26
+ Attributes:
27
+ lock_attribute_map_instances: AttributeMap instance locks for this call.
28
+ load_attribute_map_instances: Exact AttributeMap instance snapshots.
29
+ load_channel_map_instances: Exact ChannelMap pending-message snapshots.
30
+
31
+ Examples:
32
+ >>> options = RPCInvokeOptions(
33
+ ... lock_attribute_map_instances=(profiles.lock(partition),),
34
+ ... load_attribute_map_instances=(profiles.load(partition),),
35
+ ... )
36
+ >>> client.invoke_rpc(directory.upsert_customer_profile, flow_id, profile,
37
+ ... options=options)
38
+ """
39
+
40
+ lock_attribute_map_instances: tuple[AttributeLock, ...] = ()
41
+ load_attribute_map_instances: tuple[AttributeMapLoad, ...] = ()
42
+ load_channel_map_instances: tuple[ChannelMapLoad, ...] = ()
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "dex-python-sdk"
3
3
  # Publish uses tag sdk-python/v*; CI stamps this for release builds.
4
- version = "0.12.1"
4
+ version = "0.13.10"
5
5
  description = "Python SDK for the Dex workflow engine"
6
6
  authors = [{name = "Super Durable"}]
7
7
  readme = "README.md"