dex-python-sdk 0.8.0__tar.gz → 0.9.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.9.0}/PKG-INFO +12 -1
  2. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/README.md +11 -0
  3. dex_python_sdk-0.9.0/dex/_server_protocol.py +93 -0
  4. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/async_worker.py +29 -1
  5. dex_python_sdk-0.9.0/dex/dexpb/dex_pb2.py +505 -0
  6. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/dexpb/dex_pb2.pyi +10 -0
  7. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/dexpb/dex_pb2_grpc.py +44 -0
  8. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/worker.py +29 -1
  9. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/pyproject.toml +1 -1
  10. dex_python_sdk-0.8.0/dex/dexpb/dex_pb2.py +0 -503
  11. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/LEGACY_NOTICES.md +0 -0
  12. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/LICENSE +0 -0
  13. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/__init__.py +0 -0
  14. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/_async_value_hydrator.py +0 -0
  15. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/_async_worker_dispatcher.py +0 -0
  16. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/_async_worker_service.py +0 -0
  17. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/_grpc_errors.py +0 -0
  18. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/_invocation_context.py +0 -0
  19. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/_native.pyi +0 -0
  20. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/_utils.py +0 -0
  21. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/_value_hydrator.py +0 -0
  22. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/_value_mapper.py +0 -0
  23. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/_worker_dispatcher.py +0 -0
  24. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/_worker_service.py +0 -0
  25. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/async_client.py +0 -0
  26. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/attribute.py +0 -0
  27. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/attribute_match.py +0 -0
  28. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/blob_cache.py +0 -0
  29. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/channel.py +0 -0
  30. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/client.py +0 -0
  31. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/client_options.py +0 -0
  32. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/codec.py +0 -0
  33. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/condition.py +0 -0
  34. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/context.py +0 -0
  35. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/dexpb/__init__.py +0 -0
  36. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/flow.py +0 -0
  37. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/flow_config.py +0 -0
  38. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/flow_info.py +0 -0
  39. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/flow_options.py +0 -0
  40. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/flow_result.py +0 -0
  41. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/py.typed +0 -0
  42. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/runtime_errors.py +0 -0
  43. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/step.py +0 -0
  44. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/step_execution.py +0 -0
  45. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/stream.py +0 -0
  46. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/subflow.py +0 -0
  47. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/timer.py +0 -0
  48. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/wait.py +0 -0
  49. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/wait_options.py +0 -0
  50. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/dex/worker_options.py +0 -0
  51. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/sdk-rust/Cargo.lock +0 -0
  52. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/sdk-rust/Cargo.toml +0 -0
  53. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/sdk-rust/crates/dex-blob-cache/Cargo.toml +0 -0
  54. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/sdk-rust/crates/dex-blob-cache/LICENSE +0 -0
  55. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/sdk-rust/crates/dex-blob-cache/src/config.rs +0 -0
  56. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/sdk-rust/crates/dex-blob-cache/src/entry.rs +0 -0
  57. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/sdk-rust/crates/dex-blob-cache/src/error.rs +0 -0
  58. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/sdk-rust/crates/dex-blob-cache/src/format.rs +0 -0
  59. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/sdk-rust/crates/dex-blob-cache/src/lib.rs +0 -0
  60. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/sdk-rust/crates/dex-blob-cache/src/policy.rs +0 -0
  61. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/sdk-rust/crates/dex-blob-cache/src/store.rs +0 -0
  62. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.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.9.0}/sdk-rust/crates/dex-blob-cache-python/Cargo.toml +0 -0
  64. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.0}/sdk-rust/crates/dex-blob-cache-python/LICENSE +0 -0
  65. {dex_python_sdk-0.8.0 → dex_python_sdk-0.9.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.9.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
+ )
@@ -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(