dex-python-sdk 1.14.1__tar.gz → 1.15.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 (70) hide show
  1. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/PKG-INFO +35 -1
  2. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/README.md +34 -0
  3. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/_pull_worker.py +28 -2
  4. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/async_worker.py +17 -0
  5. dex_python_sdk-1.15.0/dex/dexpb/dex_pb2.py +549 -0
  6. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/dexpb/dex_pb2.pyi +73 -26
  7. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/worker.py +17 -0
  8. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/worker_options.py +29 -2
  9. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/pyproject.toml +1 -1
  10. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/sdk-rust/Cargo.lock +1 -0
  11. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/sdk-rust/Cargo.toml +1 -0
  12. dex_python_sdk-1.14.1/dex/dexpb/dex_pb2.py +0 -545
  13. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/LEGACY_NOTICES.md +0 -0
  14. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/LICENSE +0 -0
  15. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/__init__.py +0 -0
  16. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/_async_value_hydrator.py +0 -0
  17. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/_async_worker_dispatcher.py +0 -0
  18. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/_async_worker_service.py +0 -0
  19. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/_credential_transport.py +0 -0
  20. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/_grpc_errors.py +0 -0
  21. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/_invocation_context.py +0 -0
  22. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/_native.pyi +0 -0
  23. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/_server_protocol.py +0 -0
  24. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/_utils.py +0 -0
  25. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/_value_hydrator.py +0 -0
  26. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/_value_mapper.py +0 -0
  27. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/_worker_dispatcher.py +0 -0
  28. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/_worker_service.py +0 -0
  29. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/async_client.py +0 -0
  30. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/attribute.py +0 -0
  31. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/attribute_match.py +0 -0
  32. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/blob_cache.py +0 -0
  33. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/channel.py +0 -0
  34. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/client.py +0 -0
  35. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/client_options.py +0 -0
  36. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/codec.py +0 -0
  37. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/condition.py +0 -0
  38. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/context.py +0 -0
  39. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/credential.py +0 -0
  40. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/dexpb/__init__.py +0 -0
  41. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/dexpb/dex_pb2_grpc.py +0 -0
  42. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/flow.py +0 -0
  43. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/flow_config.py +0 -0
  44. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/flow_info.py +0 -0
  45. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/flow_options.py +0 -0
  46. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/flow_result.py +0 -0
  47. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/pull_registration.py +0 -0
  48. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/py.typed +0 -0
  49. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/rpc_invoke_options.py +0 -0
  50. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/runtime_errors.py +0 -0
  51. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/step.py +0 -0
  52. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/step_execution.py +0 -0
  53. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/stream.py +0 -0
  54. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/subflow.py +0 -0
  55. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/timer.py +0 -0
  56. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/wait.py +0 -0
  57. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/dex/wait_options.py +0 -0
  58. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/sdk-rust/crates/dex-blob-cache/Cargo.toml +0 -0
  59. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/sdk-rust/crates/dex-blob-cache/LICENSE +0 -0
  60. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/sdk-rust/crates/dex-blob-cache/src/config.rs +0 -0
  61. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/sdk-rust/crates/dex-blob-cache/src/entry.rs +0 -0
  62. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/sdk-rust/crates/dex-blob-cache/src/error.rs +0 -0
  63. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/sdk-rust/crates/dex-blob-cache/src/format.rs +0 -0
  64. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/sdk-rust/crates/dex-blob-cache/src/lib.rs +0 -0
  65. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/sdk-rust/crates/dex-blob-cache/src/policy.rs +0 -0
  66. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/sdk-rust/crates/dex-blob-cache/src/store.rs +0 -0
  67. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/sdk-rust/crates/dex-blob-cache/tests/blob_cache_integration.rs +0 -0
  68. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/sdk-rust/crates/dex-blob-cache-python/Cargo.toml +0 -0
  69. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.0}/sdk-rust/crates/dex-blob-cache-python/LICENSE +0 -0
  70. {dex_python_sdk-1.14.1 → dex_python_sdk-1.15.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: 1.14.1
3
+ Version: 1.15.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
@@ -157,6 +157,40 @@ applies before and after readiness. Another registered stream for that queue
157
157
  prevents shutdown. The default is False and retries indefinitely. Always call
158
158
  `stop()` or `close()` afterward; both remain safe after `start()` returns.
159
159
 
160
+ ### Reserved RPC capacity
161
+
162
+ Set `reserved_rpc_tasks` when synchronous RPC latency must stay responsive
163
+ behind long-running Steps. When unset, it reserves one slot if the resolved task
164
+ limit is at least two, otherwise zero. With the CPU-count default, Steps get one
165
+ fewer slot than before. Set it explicitly to zero to opt out. With one slot and
166
+ no explicit reserve, Worker start logs one warning: RPCs may wait behind
167
+ long-running Steps; use a limit of at least two or set the reserve explicitly.
168
+ Flows must stay responsive; Dex cannot prioritize synchronous RPCs like workflow
169
+ engines prioritize reads. The value must be nonnegative and less than
170
+ `max_concurrent_tasks` after its CPU-count default is resolved; invalid values
171
+ fail construction. RPC and Step handlers count together: WaitFor and Execute
172
+ tasks, including local attempts, are declined with `NO_CAPACITY` when the busy
173
+ count reaches the task limit minus the reserve. API-direct and transactional
174
+ InvokeWorkerRPC tasks can use the full limit.
175
+
176
+ ```python
177
+ pull = PullWorkerOptions(max_concurrent_tasks=8, reserved_rpc_tasks=2)
178
+ ```
179
+
180
+ A decline lets Dex try another Worker without consuming an attempt. If all
181
+ Workers remain at their Step limit, a Step can wait until the dispatch wait
182
+ expires, then receive `NO_WORKER_AVAILABLE`, consume an attempt, and follow its
183
+ retry policy, as when all Workers are full. Local attempts wait the local
184
+ dispatch wait, then fall back according to their policy. Each queue still keeps
185
+ its configured idle streams; a reserve decline does not close a stream or change
186
+ registration readiness.
187
+
188
+ Each reserve decline records the Flow type through the dex._pull_worker logger
189
+ at DEBUG level. Export the cumulative `pull_reserved_rpc_declines` Worker value
190
+ to distinguish reserve pressure from full-capacity rejections. The count
191
+ excludes full-capacity rejections and unknown names, never resets, and remains
192
+ available after shutdown. Listening Workers return zero.
193
+
160
194
  ## Flow state I/O
161
195
 
162
196
  Applications read and write Flow state through typed RPCs. Client and AsyncClient
@@ -141,6 +141,40 @@ applies before and after readiness. Another registered stream for that queue
141
141
  prevents shutdown. The default is False and retries indefinitely. Always call
142
142
  `stop()` or `close()` afterward; both remain safe after `start()` returns.
143
143
 
144
+ ### Reserved RPC capacity
145
+
146
+ Set `reserved_rpc_tasks` when synchronous RPC latency must stay responsive
147
+ behind long-running Steps. When unset, it reserves one slot if the resolved task
148
+ limit is at least two, otherwise zero. With the CPU-count default, Steps get one
149
+ fewer slot than before. Set it explicitly to zero to opt out. With one slot and
150
+ no explicit reserve, Worker start logs one warning: RPCs may wait behind
151
+ long-running Steps; use a limit of at least two or set the reserve explicitly.
152
+ Flows must stay responsive; Dex cannot prioritize synchronous RPCs like workflow
153
+ engines prioritize reads. The value must be nonnegative and less than
154
+ `max_concurrent_tasks` after its CPU-count default is resolved; invalid values
155
+ fail construction. RPC and Step handlers count together: WaitFor and Execute
156
+ tasks, including local attempts, are declined with `NO_CAPACITY` when the busy
157
+ count reaches the task limit minus the reserve. API-direct and transactional
158
+ InvokeWorkerRPC tasks can use the full limit.
159
+
160
+ ```python
161
+ pull = PullWorkerOptions(max_concurrent_tasks=8, reserved_rpc_tasks=2)
162
+ ```
163
+
164
+ A decline lets Dex try another Worker without consuming an attempt. If all
165
+ Workers remain at their Step limit, a Step can wait until the dispatch wait
166
+ expires, then receive `NO_WORKER_AVAILABLE`, consume an attempt, and follow its
167
+ retry policy, as when all Workers are full. Local attempts wait the local
168
+ dispatch wait, then fall back according to their policy. Each queue still keeps
169
+ its configured idle streams; a reserve decline does not close a stream or change
170
+ registration readiness.
171
+
172
+ Each reserve decline records the Flow type through the dex._pull_worker logger
173
+ at DEBUG level. Export the cumulative `pull_reserved_rpc_declines` Worker value
174
+ to distinguish reserve pressure from full-capacity rejections. The count
175
+ excludes full-capacity rejections and unknown names, never resets, and remains
176
+ available after shutdown. Listening Workers return zero.
177
+
144
178
  ## Flow state I/O
145
179
 
146
180
  Applications read and write Flow state through typed RPCs. Client and AsyncClient
@@ -94,6 +94,15 @@ class PullWorkerRuntime:
94
94
  self._permanent_error: PullRegistrationError | None = None
95
95
  self._rejection_stop_task: asyncio.Task[None] | None = None
96
96
  self.max_concurrent_tasks = options.max_concurrent_tasks or os.cpu_count() or 1
97
+ self.reserved_rpc_tasks = (
98
+ options.reserved_rpc_tasks
99
+ if options.reserved_rpc_tasks is not None
100
+ else int(self.max_concurrent_tasks >= 2)
101
+ )
102
+ self._should_warn_rpc_capacity = (
103
+ options.reserved_rpc_tasks is None and self.max_concurrent_tasks == 1
104
+ )
105
+ self.reserved_rpc_declines = 0
97
106
  self.idle_streams_per_queue = options.idle_streams_per_queue or 1
98
107
  self.stop_drain_timeout = options.stop_drain_timeout.total_seconds() or 30
99
108
  self._dispatcher = dispatcher
@@ -132,6 +141,10 @@ class PullWorkerRuntime:
132
141
  return None
133
142
 
134
143
  async def run(self) -> None:
144
+ if self._should_warn_rpc_capacity:
145
+ _LOGGER.warning(
146
+ "RPCs may wait behind long-running Steps; use max_concurrent_tasks >= 2 or set reserved_rpc_tasks explicitly"
147
+ )
135
148
  self._loop = asyncio.get_running_loop()
136
149
  self._loop_thread_id = threading.get_ident()
137
150
  self._wake = asyncio.Event()
@@ -193,11 +206,24 @@ class PullWorkerRuntime:
193
206
  _LOGGER.error("Worker task stream failed", exc_info=failure)
194
207
  self.signal()
195
208
 
196
- def accept_task(self) -> bool:
209
+ def accept_task(self, task: pb.PollWorkerTask | None = None) -> bool:
197
210
  if self._stopping is None or self._stopping.is_set():
198
211
  return False
199
212
  if self._busy_tasks >= self.max_concurrent_tasks:
200
213
  return False
214
+ if (
215
+ task is not None
216
+ and task.WhichOneof("request") in ("wait_for", "execute")
217
+ and self.reserved_rpc_tasks > 0
218
+ and self._busy_tasks >= self.max_concurrent_tasks - self.reserved_rpc_tasks
219
+ and self.unknown_task_reason(task) is None
220
+ ):
221
+ self.reserved_rpc_declines += 1
222
+ request = task.wait_for if task.HasField("wait_for") else task.execute
223
+ _LOGGER.debug(
224
+ "decline Step task for reserved RPC capacity: %s", request.flow_type
225
+ )
226
+ return False
201
227
  self._busy_tasks += 1
202
228
  self.signal()
203
229
  return True
@@ -524,7 +550,7 @@ class PullStream:
524
550
  if unknown_reason is not None:
525
551
  await self._reject_task(task, unknown_reason)
526
552
  return
527
- if self.is_busy or self.is_draining or not self.runtime.accept_task():
553
+ if self.is_busy or self.is_draining or not self.runtime.accept_task(task):
528
554
  await self._reject_task(task)
529
555
  return
530
556
  self.is_busy = True
@@ -221,6 +221,23 @@ class AsyncWorker:
221
221
  raise RuntimeError("wait_pull_registered requires a pull Worker")
222
222
  await self._pull.registration.wait_async(timeout)
223
223
 
224
+ @property
225
+ def pull_reserved_rpc_declines(self) -> int:
226
+ """Return cumulative Step declines caused by reserved RPC capacity.
227
+
228
+ Full-capacity rejections and unknown names are excluded. The count never
229
+ resets during this Worker's lifetime and stays available after stop.
230
+ Reading it alongside handlers is safe. Listening Workers return zero.
231
+ Export this value to a monitoring counter to identify reserve pressure.
232
+
233
+ Returns:
234
+ The cumulative reserve-decline count, or zero for a listening Worker.
235
+
236
+ Examples:
237
+ >>> declines = worker.pull_reserved_rpc_declines
238
+ """
239
+ return 0 if self._pull is None else self._pull.reserved_rpc_declines
240
+
224
241
  def pull_registration_state(self) -> PullRegistrationState:
225
242
  """Return an immutable snapshot of pull queue registrations.
226
243