dex-python-sdk 0.8.0__tar.gz → 0.11.0__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.8.0 → dex_python_sdk-0.11.0}/PKG-INFO +12 -1
  2. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/README.md +11 -0
  3. dex_python_sdk-0.11.0/dex/_server_protocol.py +93 -0
  4. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/async_client.py +9 -3
  5. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/async_worker.py +29 -1
  6. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/client.py +9 -4
  7. dex_python_sdk-0.11.0/dex/dexpb/dex_pb2.py +509 -0
  8. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/dexpb/dex_pb2.pyi +46 -10
  9. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/dexpb/dex_pb2_grpc.py +44 -0
  10. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/worker.py +29 -1
  11. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/pyproject.toml +1 -1
  12. dex_python_sdk-0.8.0/dex/dexpb/dex_pb2.py +0 -503
  13. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/LEGACY_NOTICES.md +0 -0
  14. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/LICENSE +0 -0
  15. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/__init__.py +0 -0
  16. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/_async_value_hydrator.py +0 -0
  17. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/_async_worker_dispatcher.py +0 -0
  18. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/_async_worker_service.py +0 -0
  19. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/_grpc_errors.py +0 -0
  20. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/_invocation_context.py +0 -0
  21. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/_native.pyi +0 -0
  22. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/_utils.py +0 -0
  23. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/_value_hydrator.py +0 -0
  24. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/_value_mapper.py +0 -0
  25. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/_worker_dispatcher.py +0 -0
  26. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/_worker_service.py +0 -0
  27. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/attribute.py +0 -0
  28. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/attribute_match.py +0 -0
  29. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/blob_cache.py +0 -0
  30. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/channel.py +0 -0
  31. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/client_options.py +0 -0
  32. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/codec.py +0 -0
  33. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/condition.py +0 -0
  34. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/context.py +0 -0
  35. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/dexpb/__init__.py +0 -0
  36. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/flow.py +0 -0
  37. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/flow_config.py +0 -0
  38. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/flow_info.py +0 -0
  39. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/flow_options.py +0 -0
  40. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/flow_result.py +0 -0
  41. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/py.typed +0 -0
  42. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/runtime_errors.py +0 -0
  43. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/step.py +0 -0
  44. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/step_execution.py +0 -0
  45. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/stream.py +0 -0
  46. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/subflow.py +0 -0
  47. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/timer.py +0 -0
  48. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/wait.py +0 -0
  49. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/wait_options.py +0 -0
  50. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/dex/worker_options.py +0 -0
  51. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/sdk-rust/Cargo.lock +0 -0
  52. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/sdk-rust/Cargo.toml +0 -0
  53. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/sdk-rust/crates/dex-blob-cache/Cargo.toml +0 -0
  54. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/sdk-rust/crates/dex-blob-cache/LICENSE +0 -0
  55. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/sdk-rust/crates/dex-blob-cache/src/config.rs +0 -0
  56. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/sdk-rust/crates/dex-blob-cache/src/entry.rs +0 -0
  57. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/sdk-rust/crates/dex-blob-cache/src/error.rs +0 -0
  58. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/sdk-rust/crates/dex-blob-cache/src/format.rs +0 -0
  59. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/sdk-rust/crates/dex-blob-cache/src/lib.rs +0 -0
  60. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/sdk-rust/crates/dex-blob-cache/src/policy.rs +0 -0
  61. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/sdk-rust/crates/dex-blob-cache/src/store.rs +0 -0
  62. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/sdk-rust/crates/dex-blob-cache/tests/blob_cache_integration.rs +0 -0
  63. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/sdk-rust/crates/dex-blob-cache-python/Cargo.toml +0 -0
  64. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/sdk-rust/crates/dex-blob-cache-python/LICENSE +0 -0
  65. {dex_python_sdk-0.8.0 → dex_python_sdk-0.11.0}/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.8.0
3
+ Version: 0.11.0
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
@@ -560,6 +560,17 @@ The dispatch `version` input is stamped the same way as a release tag.
560
560
 
561
561
  See [CONTRIBUTING.md](../CONTRIBUTING.md#releases-monorepo-tags) for monorepo tag conventions.
562
562
 
563
+ ## Server protocol compatibility
564
+
565
+ Both `Worker` and `AsyncWorker` call `GetServerInfo`, negotiate the highest
566
+ common protocol, synchronize Attribute indexes, and only then bind
567
+ WorkerService. The initial Python SDK interval is `[1,1]`. Missing information,
568
+ invalid or disjoint intervals, and RPC failures stop startup before binding.
569
+
570
+ The diagnostic SDK version comes from Python distribution metadata for
571
+ `dex-python-sdk` and falls back to `dev` when the distribution is unavailable.
572
+ It does not affect protocol compatibility.
573
+
563
574
  ## License
564
575
 
565
576
  [Sustainable Use License 1.0](LICENSE), with legacy portions under their
@@ -544,6 +544,17 @@ The dispatch `version` input is stamped the same way as a release tag.
544
544
 
545
545
  See [CONTRIBUTING.md](../CONTRIBUTING.md#releases-monorepo-tags) for monorepo tag conventions.
546
546
 
547
+ ## Server protocol compatibility
548
+
549
+ Both `Worker` and `AsyncWorker` call `GetServerInfo`, negotiate the highest
550
+ common protocol, synchronize Attribute indexes, and only then bind
551
+ WorkerService. The initial Python SDK interval is `[1,1]`. Missing information,
552
+ invalid or disjoint intervals, and RPC failures stop startup before binding.
553
+
554
+ The diagnostic SDK version comes from Python distribution metadata for
555
+ `dex-python-sdk` and falls back to `dev` when the distribution is unavailable.
556
+ It does not affect protocol compatibility.
557
+
547
558
  ## License
548
559
 
549
560
  [Sustainable Use License 1.0](LICENSE), with legacy portions under their
@@ -0,0 +1,93 @@
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 importlib.metadata import PackageNotFoundError, version
12
+
13
+ import grpc
14
+
15
+ from dex.dexpb import dex_pb2 as pb
16
+
17
+ MINIMUM_SUPPORTED_SERVER_PROTOCOL_VERSION = 1
18
+ MAXIMUM_SUPPORTED_SERVER_PROTOCOL_VERSION = 1
19
+
20
+
21
+ def sdk_version() -> str:
22
+ try:
23
+ return version("dex-python-sdk")
24
+ except PackageNotFoundError:
25
+ return "dev"
26
+
27
+
28
+ def negotiate_server_protocol(server_info: pb.ServerInfo, artifact_version: str) -> int:
29
+ server_minimum = server_info.minimum_supported_protocol_version
30
+ server_current = server_info.current_protocol_version
31
+ if (
32
+ MINIMUM_SUPPORTED_SERVER_PROTOCOL_VERSION == 0
33
+ or MAXIMUM_SUPPORTED_SERVER_PROTOCOL_VERSION == 0
34
+ or MINIMUM_SUPPORTED_SERVER_PROTOCOL_VERSION
35
+ > MAXIMUM_SUPPORTED_SERVER_PROTOCOL_VERSION
36
+ ):
37
+ raise _compatibility_error(
38
+ artifact_version,
39
+ server_info.server_version,
40
+ server_minimum,
41
+ server_current,
42
+ "Python SDK protocol interval is invalid",
43
+ )
44
+ if server_minimum == 0 or server_current == 0 or server_minimum > server_current:
45
+ raise _compatibility_error(
46
+ artifact_version,
47
+ server_info.server_version,
48
+ server_minimum,
49
+ server_current,
50
+ "Server protocol interval is invalid",
51
+ )
52
+ negotiated = min(server_current, MAXIMUM_SUPPORTED_SERVER_PROTOCOL_VERSION)
53
+ if (
54
+ negotiated < server_minimum
55
+ or negotiated < MINIMUM_SUPPORTED_SERVER_PROTOCOL_VERSION
56
+ ):
57
+ raise _compatibility_error(
58
+ artifact_version,
59
+ server_info.server_version,
60
+ server_minimum,
61
+ server_current,
62
+ "protocol intervals do not overlap",
63
+ )
64
+ return negotiated
65
+
66
+
67
+ def server_info_request_error(
68
+ artifact_version: str, failure: grpc.RpcError
69
+ ) -> RuntimeError:
70
+ return RuntimeError(
71
+ f'Python SDK version "{artifact_version}" protocol '
72
+ f"[{MINIMUM_SUPPORTED_SERVER_PROTOCOL_VERSION},"
73
+ f"{MAXIMUM_SUPPORTED_SERVER_PROTOCOL_VERSION}] is incompatible "
74
+ 'with Server version "unknown" protocol [unknown,unknown]: '
75
+ f"GetServerInfo failed: {failure}"
76
+ )
77
+
78
+
79
+ def _compatibility_error(
80
+ artifact_version: str,
81
+ server_version: str,
82
+ server_minimum: int,
83
+ server_current: int,
84
+ reason: str,
85
+ ) -> RuntimeError:
86
+ displayed_server_version = server_version or "unknown"
87
+ return RuntimeError(
88
+ f'Python SDK version "{artifact_version}" protocol '
89
+ f"[{MINIMUM_SUPPORTED_SERVER_PROTOCOL_VERSION},"
90
+ f"{MAXIMUM_SUPPORTED_SERVER_PROTOCOL_VERSION}] is incompatible with "
91
+ f'Server version "{displayed_server_version}" protocol '
92
+ f"[{server_minimum},{server_current}]: {reason}"
93
+ )
@@ -254,13 +254,18 @@ class AsyncClient:
254
254
  """Invoke a registered RPC and await its typed result.
255
255
 
256
256
  Pass the bound method from the registered Flow instance. RPC timeout and
257
- Attribute locks come from ``@rpc`` configuration.
257
+ Attribute locks come from ``@rpc`` configuration. A non-transactional RPC
258
+ without Attribute locks starts from a backend query. If the handler returns no
259
+ durable effects, a retained terminal execution can serve that query. Locks,
260
+ transactional execution, returned effects, or server policy can require an
261
+ active execution.
258
262
 
259
263
  Args:
260
264
  rpc_method: A bound method decorated with ``@rpc``.
261
265
  flow_id: The non-empty target Flow ID.
262
266
  input: The annotated input, or ``None`` for an input-free RPC.
263
- run_id: Optional exact run; ``""`` targets the active run.
267
+ run_id: Optional exact run. With ``""``, the server resolves the current
268
+ execution.
264
269
 
265
270
  Returns:
266
271
  The decoded ``RPCResult.output``, or ``None`` for a no-output RPC.
@@ -270,7 +275,8 @@ class AsyncClient:
270
275
  RpcLockConflictError: If Attribute locks cannot be acquired.
271
276
  WorkerInvocationError: If the application handler fails.
272
277
  ValueMappingError: If input or output mapping fails.
273
- DexServiceError: If the Flow is inactive or the service call fails.
278
+ DexServiceError: If the selected path requires an active execution or the
279
+ service call otherwise fails.
274
280
  """
275
281
  _, rpc = self.registry._rpc_for_method(rpc_method)
276
282
  encoded_input = (
@@ -12,7 +12,13 @@ import asyncio
12
12
  from types import TracebackType
13
13
 
14
14
  import grpc
15
+ from google.protobuf import empty_pb2
15
16
 
17
+ from dex._server_protocol import (
18
+ negotiate_server_protocol,
19
+ sdk_version,
20
+ server_info_request_error,
21
+ )
16
22
  from dex._async_value_hydrator import AsyncValueHydrator
17
23
  from dex._async_worker_dispatcher import AsyncWorkerDispatcher
18
24
  from dex._async_worker_service import AsyncWorkerService
@@ -64,6 +70,8 @@ class AsyncWorker:
64
70
  self._flow_service = dex_pb2_grpc.FlowServiceStub( # type: ignore[no-untyped-call]
65
71
  self._flow_channel
66
72
  )
73
+ self._sdk_version = sdk_version()
74
+ self._negotiated_protocol_version = 0
67
75
  values = ValueMapper(registry.codec_registry)
68
76
  dispatcher = AsyncWorkerDispatcher(
69
77
  registry,
@@ -116,7 +124,7 @@ class AsyncWorker:
116
124
  await self.close()
117
125
 
118
126
  async def start(self) -> None:
119
- """Synchronize Attribute indexes, serve WorkerService, and await shutdown.
127
+ """Negotiate protocol, synchronize Attribute indexes, and serve.
120
128
 
121
129
  ``start`` may be awaited exactly once. It contacts FlowService before binding
122
130
  and completes only after ``stop`` terminates the server.
@@ -126,6 +134,26 @@ class AsyncWorker:
126
134
  """
127
135
  if self._state != "created":
128
136
  raise RuntimeError(f"AsyncWorker cannot start from state {self._state}")
137
+ try:
138
+ server_info = await self._flow_service.GetServerInfo(
139
+ empty_pb2.Empty(),
140
+ timeout=self.options.attribute_index_sync_timeout.total_seconds(),
141
+ )
142
+ except grpc.RpcError as failure:
143
+ self._state = "stopped"
144
+ await self._flow_channel.close(None)
145
+ self._stopped.set()
146
+ raise server_info_request_error(self._sdk_version, failure) from failure
147
+ try:
148
+ self._negotiated_protocol_version = negotiate_server_protocol(
149
+ server_info,
150
+ self._sdk_version,
151
+ )
152
+ except RuntimeError:
153
+ self._state = "stopped"
154
+ await self._flow_channel.close(None)
155
+ self._stopped.set()
156
+ raise
129
157
  try:
130
158
  await self._flow_service.SyncAttributeIndexes(
131
159
  pb.SyncAttributeIndexRequest(
@@ -250,17 +250,21 @@ class Client:
250
250
  *,
251
251
  run_id: str = "",
252
252
  ) -> Any:
253
- """Synchronously invoke a registered RPC on an active Flow.
253
+ """Synchronously invoke a registered RPC on a Flow execution.
254
254
 
255
255
  Pass the bound method from the same registered Flow instance. RPC timeout and
256
256
  Attribute locks come from its ``@rpc`` decorator. The call blocks until the
257
- handler returns, fails, or times out.
257
+ handler returns, fails, or times out. A non-transactional RPC without Attribute
258
+ locks starts from a backend query. If the handler returns no durable effects, a
259
+ retained terminal execution can serve that query. Locks, transactional execution,
260
+ returned effects, or server policy can require an active execution.
258
261
 
259
262
  Args:
260
263
  rpc_method: A bound method decorated with ``@rpc``.
261
264
  flow_id: The non-empty target Flow ID.
262
265
  input: The annotated RPC input, or ``None`` for an input-free RPC.
263
- run_id: Optional exact run; ``""`` targets the active run.
266
+ run_id: Optional exact run. With ``""``, the server resolves the current
267
+ execution.
264
268
 
265
269
  Returns:
266
270
  The decoded ``RPCResult.output``, or ``None`` for a no-output RPC.
@@ -270,7 +274,8 @@ class Client:
270
274
  RpcLockConflictError: If requested Attribute locks cannot be acquired.
271
275
  WorkerInvocationError: If the application handler fails.
272
276
  ValueMappingError: If input or output mapping fails.
273
- DexServiceError: If the Flow is inactive or the service call fails.
277
+ DexServiceError: If the selected path requires an active execution or the
278
+ service call otherwise fails.
274
279
  """
275
280
  _, rpc = self.registry._rpc_for_method(rpc_method)
276
281
  encoded_input = (