altcodepro-polydb-python 2.3.27__tar.gz → 2.3.29__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.
- {altcodepro_polydb_python-2.3.27/src/altcodepro_polydb_python.egg-info → altcodepro_polydb_python-2.3.29}/PKG-INFO +1 -1
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/pyproject.toml +1 -1
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29/src/altcodepro_polydb_python.egg-info}/PKG-INFO +1 -1
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/altcodepro_polydb_python.egg-info/SOURCES.txt +1 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/adapters/AzureQueueAdapter.py +90 -3
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/adapters/MongoDBAdapter.py +3 -1
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/databaseFactory.py +20 -17
- altcodepro_polydb_python-2.3.29/tests/test_azure_queue_receipt_handle.py +185 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/LICENSE +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/MANIFEST.in +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/README.md +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/requirements-aws.txt +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/requirements-azure.txt +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/requirements-dev.txt +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/requirements-gcp.txt +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/requirements-generic.txt +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/requirements.txt +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/setup.cfg +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/setup.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/altcodepro_polydb_python.egg-info/dependency_links.txt +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/altcodepro_polydb_python.egg-info/requires.txt +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/altcodepro_polydb_python.egg-info/top_level.txt +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/PolyDB.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/__init__.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/adapters/AzureBlobStorageAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/adapters/AzureFileStorageAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/adapters/AzureTableStorageAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/adapters/BlockchainBlobAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/adapters/BlockchainFileAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/adapters/BlockchainKVAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/adapters/BlockchainQueueAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/adapters/DynamoDBAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/adapters/EFSAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/adapters/FirestoreAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/adapters/GCPFilestoreAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/adapters/GCPPubSubAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/adapters/GCPStorageAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/adapters/PostgreSQLAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/adapters/S3Adapter.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/adapters/S3CompatibleAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/adapters/SQSAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/adapters/VercelBlobAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/adapters/VercelFileAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/adapters/VercelKVAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/adapters/VercelQueueAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/adapters/__init__.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/advanced_query.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/audit/AuditStorage.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/audit/__init__.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/audit/context.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/audit/manager.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/audit/models.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/base/NoSQLKVAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/base/ObjectStorageAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/base/QueueAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/base/SharedFilesAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/base/__init__.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/batch.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/cache.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/cloudDatabaseFactory.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/decorators.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/errors.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/json_safe.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/models.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/monitoring.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/multitenancy.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/observability/__init__.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/observability/logging.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/py.typed +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/query.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/registry.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/retry.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/schema.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/security.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/services/__init__.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/services/compliance_service.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/services/security_service.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/types.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/utils.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/validation.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/tests/test_atomic_decrement.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/tests/test_aws.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/tests/test_azure.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/tests/test_azure_table_empty_filter.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/tests/test_azure_table_unpack_entity.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/tests/test_blockchain.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/tests/test_cloud_factory.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/tests/test_gcp.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/tests/test_mongodb.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/tests/test_multi_engine.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/tests/test_observability.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/tests/test_postgresql.py +0 -0
- {altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/tests/test_vercel.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: altcodepro-polydb-python
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.29
|
|
4
4
|
Summary: Production-ready multi-cloud database abstraction layer with connection pooling, retry logic, and thread safety
|
|
5
5
|
Author: AltCodePro
|
|
6
6
|
Project-URL: Homepage, https://github.com/altcodepro/polydb-python
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "altcodepro-polydb-python"
|
|
7
|
-
version = "2.3.
|
|
7
|
+
version = "2.3.29"
|
|
8
8
|
description = "Production-ready multi-cloud database abstraction layer with connection pooling, retry logic, and thread safety"
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: altcodepro-polydb-python
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.29
|
|
4
4
|
Summary: Production-ready multi-cloud database abstraction layer with connection pooling, retry logic, and thread safety
|
|
5
5
|
Author: AltCodePro
|
|
6
6
|
Project-URL: Homepage, https://github.com/altcodepro/polydb-python
|
|
@@ -78,6 +78,7 @@ src/polydb/services/security_service.py
|
|
|
78
78
|
tests/test_atomic_decrement.py
|
|
79
79
|
tests/test_aws.py
|
|
80
80
|
tests/test_azure.py
|
|
81
|
+
tests/test_azure_queue_receipt_handle.py
|
|
81
82
|
tests/test_azure_table_empty_filter.py
|
|
82
83
|
tests/test_azure_table_unpack_entity.py
|
|
83
84
|
tests/test_blockchain.py
|
|
@@ -46,6 +46,20 @@ class AzureQueueAdapter(QueueAdapter):
|
|
|
46
46
|
name = re.sub(r"-+", "-", name) # collapse multiple dashes
|
|
47
47
|
return name.strip("-")
|
|
48
48
|
|
|
49
|
+
@staticmethod
|
|
50
|
+
def _encode_receipt(message_id: str, pop_receipt: str) -> str:
|
|
51
|
+
# Azure needs BOTH message_id and pop_receipt to delete/update a
|
|
52
|
+
# message, but the generic queue contract (WorkerPool, SQSAdapter)
|
|
53
|
+
# only ever carries a single opaque `receipt_handle` string end to
|
|
54
|
+
# end. Pack both into one string here so ack()/nack() can recover
|
|
55
|
+
# them without any caller needing to know Azure's two-part scheme.
|
|
56
|
+
return f"{message_id}|{pop_receipt}"
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def _decode_receipt(receipt_handle: str) -> "tuple[str, str]":
|
|
60
|
+
message_id, _, pop_receipt = (receipt_handle or "").partition("|")
|
|
61
|
+
return message_id, pop_receipt
|
|
62
|
+
|
|
49
63
|
def _initialize_client(self) -> None:
|
|
50
64
|
"""Initialize Azure Queue client"""
|
|
51
65
|
from azure.storage.queue import QueueServiceClient
|
|
@@ -96,15 +110,38 @@ class AzureQueueAdapter(QueueAdapter):
|
|
|
96
110
|
except Exception as e:
|
|
97
111
|
raise QueueError(f"Azure Queue send failed: {e}")
|
|
98
112
|
|
|
113
|
+
# How long a delivered message stays invisible to other consumers before
|
|
114
|
+
# Azure hands it out again. The SDK's own default is 30s — far shorter
|
|
115
|
+
# than a real task (an LLM generation call alone can run 30-120s+), so
|
|
116
|
+
# a still-in-flight message was becoming visible again and getting
|
|
117
|
+
# picked up by a second worker mid-processing: two concurrent
|
|
118
|
+
# executions of the same task, one of which stomps/duplicates the
|
|
119
|
+
# other's result. 300s covers realistic worst-case processing; ack()
|
|
120
|
+
# deletes the message immediately on completion regardless; a genuinely
|
|
121
|
+
# crashed worker still recovers the message after this timeout.
|
|
122
|
+
DEFAULT_VISIBILITY_TIMEOUT = 300
|
|
123
|
+
|
|
99
124
|
@retry(max_attempts=3, delay=1.0, exceptions=(QueueError,))
|
|
100
|
-
def receive(
|
|
125
|
+
def receive(
|
|
126
|
+
self,
|
|
127
|
+
queue_name: str = "default",
|
|
128
|
+
max_messages: int = 1,
|
|
129
|
+
visibility_timeout: Optional[int] = None,
|
|
130
|
+
) -> List[Dict[str, Any]]:
|
|
101
131
|
"""Receive messages"""
|
|
102
132
|
try:
|
|
103
133
|
|
|
104
134
|
queue_name = self._normalize_queue_name(queue_name)
|
|
105
135
|
queue_client = self._get_queue(queue_name)
|
|
106
136
|
|
|
107
|
-
messages = queue_client.receive_messages(
|
|
137
|
+
messages = queue_client.receive_messages(
|
|
138
|
+
max_messages=max_messages,
|
|
139
|
+
visibility_timeout=(
|
|
140
|
+
visibility_timeout
|
|
141
|
+
if visibility_timeout is not None
|
|
142
|
+
else self.DEFAULT_VISIBILITY_TIMEOUT
|
|
143
|
+
),
|
|
144
|
+
)
|
|
108
145
|
|
|
109
146
|
results = []
|
|
110
147
|
|
|
@@ -114,6 +151,19 @@ class AzureQueueAdapter(QueueAdapter):
|
|
|
114
151
|
{
|
|
115
152
|
"id": msg.id,
|
|
116
153
|
"pop_receipt": msg.pop_receipt,
|
|
154
|
+
# WorkerPool (and the generic UDL queue contract)
|
|
155
|
+
# reads this key to ack/nack. Without it, ack() is
|
|
156
|
+
# always skipped (falsy receipt_handle), the message
|
|
157
|
+
# is never deleted, and it silently reappears once
|
|
158
|
+
# Azure's visibility timeout elapses — replaying the
|
|
159
|
+
# same task forever even after it already ran.
|
|
160
|
+
"receipt_handle": self._encode_receipt(msg.id, msg.pop_receipt),
|
|
161
|
+
# How many times Azure has handed this same message
|
|
162
|
+
# out (1 on first delivery). Lets a consumer (e.g.
|
|
163
|
+
# WorkerPool) detect a poison message that keeps
|
|
164
|
+
# failing to ack/process and dead-letter it instead
|
|
165
|
+
# of retrying forever.
|
|
166
|
+
"dequeue_count": msg.dequeue_count,
|
|
117
167
|
"body": payload,
|
|
118
168
|
}
|
|
119
169
|
)
|
|
@@ -156,8 +206,14 @@ class AzureQueueAdapter(QueueAdapter):
|
|
|
156
206
|
Preferred usage:
|
|
157
207
|
ack(pop_receipt=..., message_id=...)
|
|
158
208
|
|
|
159
|
-
|
|
209
|
+
Generic callers (WorkerPool, storage_router.queue_ack) only pass a
|
|
210
|
+
single `receipt_handle` positionally and never supply message_id —
|
|
211
|
+
in that case `pop_receipt` here is actually the combined value
|
|
212
|
+
produced by `receive()` (`"<message_id>|<pop_receipt>"`), so decode
|
|
213
|
+
it rather than failing.
|
|
160
214
|
"""
|
|
215
|
+
if not message_id:
|
|
216
|
+
message_id, pop_receipt = self._decode_receipt(pop_receipt)
|
|
161
217
|
if not message_id:
|
|
162
218
|
raise QueueError("AzureQueueAdapter.ack requires message_id")
|
|
163
219
|
queue_name = self._normalize_queue_name(queue_name)
|
|
@@ -166,3 +222,34 @@ class AzureQueueAdapter(QueueAdapter):
|
|
|
166
222
|
queue_name=queue_name,
|
|
167
223
|
pop_receipt=pop_receipt,
|
|
168
224
|
)
|
|
225
|
+
|
|
226
|
+
def nack(
|
|
227
|
+
self,
|
|
228
|
+
queue_name: str,
|
|
229
|
+
ack_id: str,
|
|
230
|
+
*,
|
|
231
|
+
delay: Optional[int] = None,
|
|
232
|
+
) -> bool:
|
|
233
|
+
"""
|
|
234
|
+
Make a message visible again (optionally after `delay` seconds)
|
|
235
|
+
without deleting it, instead of leaving it to reappear only once
|
|
236
|
+
Azure's default visibility timeout elapses.
|
|
237
|
+
|
|
238
|
+
`ack_id` is the combined `"<message_id>|<pop_receipt>"` receipt
|
|
239
|
+
handle produced by `receive()`. Matches the positional order used
|
|
240
|
+
by storage_router.queue_nack: nack(queue_name, ack_id, delay=...).
|
|
241
|
+
"""
|
|
242
|
+
message_id, pop_receipt = self._decode_receipt(ack_id)
|
|
243
|
+
if not message_id:
|
|
244
|
+
raise QueueError("AzureQueueAdapter.nack requires a receipt_handle from receive()")
|
|
245
|
+
try:
|
|
246
|
+
queue_name = self._normalize_queue_name(queue_name)
|
|
247
|
+
queue_client = self._get_queue(queue_name)
|
|
248
|
+
queue_client.update_message(
|
|
249
|
+
message_id,
|
|
250
|
+
pop_receipt=pop_receipt,
|
|
251
|
+
visibility_timeout=delay or 0,
|
|
252
|
+
)
|
|
253
|
+
return True
|
|
254
|
+
except Exception as e:
|
|
255
|
+
raise QueueError(f"Azure Queue nack failed: {e}")
|
|
@@ -50,7 +50,9 @@ class MongoDBAdapter(NoSQLKVAdapter):
|
|
|
50
50
|
self.mongo_uri,
|
|
51
51
|
maxPoolSize=int(os.getenv("MONGODB_MAX_POOL_SIZE", "10")),
|
|
52
52
|
minPoolSize=int(os.getenv("MONGODB_MIN_POOL_SIZE", "1")),
|
|
53
|
-
serverSelectionTimeoutMS=
|
|
53
|
+
serverSelectionTimeoutMS=int(
|
|
54
|
+
os.getenv("MONGODB_SERVER_SELECTION_TIMEOUT_MS", "5000")
|
|
55
|
+
),
|
|
54
56
|
)
|
|
55
57
|
|
|
56
58
|
self._client.server_info()
|
{altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/databaseFactory.py
RENAMED
|
@@ -109,8 +109,8 @@ class EngineOverride:
|
|
|
109
109
|
|
|
110
110
|
@dataclass
|
|
111
111
|
class _ResolvedAdapters:
|
|
112
|
-
sql: PostgreSQLAdapter
|
|
113
|
-
nosql: NoSQLKVAdapter
|
|
112
|
+
sql: Optional[PostgreSQLAdapter]
|
|
113
|
+
nosql: Optional[NoSQLKVAdapter]
|
|
114
114
|
engine_name: str
|
|
115
115
|
|
|
116
116
|
|
|
@@ -233,35 +233,38 @@ class DatabaseFactory:
|
|
|
233
233
|
def _resolve_adapters(
|
|
234
234
|
self, model_name: str, storage: str, override: Optional[EngineOverride] = None
|
|
235
235
|
) -> _ResolvedAdapters:
|
|
236
|
+
# Only construct the adapter this call actually needs. Every caller
|
|
237
|
+
# (create/read/update/upsert/delete/query*) branches on `storage` and
|
|
238
|
+
# touches exactly one of .sql/.nosql — building both unconditionally
|
|
239
|
+
# meant a purely-SQL deployment with no reachable NoSQL backend (or
|
|
240
|
+
# vice versa) failed on every single call, not just ones that needed
|
|
241
|
+
# the missing engine.
|
|
242
|
+
def _build(engine: "EngineConfig") -> _ResolvedAdapters:
|
|
243
|
+
return _ResolvedAdapters(
|
|
244
|
+
sql=engine.sql() if storage == "sql" else None,
|
|
245
|
+
nosql=engine.nosql() if storage == "nosql" else None,
|
|
246
|
+
engine_name=engine.name,
|
|
247
|
+
)
|
|
248
|
+
|
|
236
249
|
if override:
|
|
237
250
|
engine = self._engine_by_name.get(override.engine_name)
|
|
238
251
|
if engine is None:
|
|
239
252
|
raise AdapterConfigurationError(
|
|
240
253
|
f"Unknown engine '{override.engine_name}'. Available: {list(self._engine_by_name)}"
|
|
241
254
|
)
|
|
242
|
-
return
|
|
243
|
-
sql=engine.sql(), nosql=engine.nosql(), engine_name=engine.name
|
|
244
|
-
)
|
|
255
|
+
return _build(engine)
|
|
245
256
|
|
|
246
257
|
for engine in self._engines:
|
|
247
258
|
if storage == "sql" and engine.sql_models and model_name in engine.sql_models:
|
|
248
|
-
return
|
|
249
|
-
sql=engine.sql(), nosql=engine.nosql(), engine_name=engine.name
|
|
250
|
-
)
|
|
259
|
+
return _build(engine)
|
|
251
260
|
if storage == "nosql" and engine.nosql_models and model_name in engine.nosql_models:
|
|
252
|
-
return
|
|
253
|
-
sql=engine.sql(), nosql=engine.nosql(), engine_name=engine.name
|
|
254
|
-
)
|
|
261
|
+
return _build(engine)
|
|
255
262
|
|
|
256
263
|
for engine in self._engines:
|
|
257
264
|
if storage == "sql" and engine.is_default_sql:
|
|
258
|
-
return
|
|
259
|
-
sql=engine.sql(), nosql=engine.nosql(), engine_name=engine.name
|
|
260
|
-
)
|
|
265
|
+
return _build(engine)
|
|
261
266
|
if storage == "nosql" and engine.is_default_nosql:
|
|
262
|
-
return
|
|
263
|
-
sql=engine.sql(), nosql=engine.nosql(), engine_name=engine.name
|
|
264
|
-
)
|
|
267
|
+
return _build(engine)
|
|
265
268
|
|
|
266
269
|
raise AdapterConfigurationError(f"No engine for model='{model_name}' storage='{storage}'")
|
|
267
270
|
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tests/test_azure_queue_receipt_handle.py
|
|
3
|
+
=========================================
|
|
4
|
+
Regression coverage for the receipt_handle contract on AzureQueueAdapter.
|
|
5
|
+
|
|
6
|
+
Bug this guards against: `receive()` used to return only `id` and
|
|
7
|
+
`pop_receipt`, never the `receipt_handle` key that WorkerPool (and every
|
|
8
|
+
other adapter's ack contract) reads. That made
|
|
9
|
+
`WorkerPool._process_message`'s `msg.get("receipt_handle")` always `None`,
|
|
10
|
+
so the ack branch was silently skipped, the message was never deleted, and
|
|
11
|
+
Azure redelivered it once its visibility timeout elapsed — replaying the
|
|
12
|
+
same task forever even after it had already run to completion.
|
|
13
|
+
|
|
14
|
+
These tests mock the Azure SDK entirely (no live Azurite needed) and only
|
|
15
|
+
exercise the adapter's own encode/decode + ack/nack wiring.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import sys
|
|
22
|
+
from unittest.mock import MagicMock, patch
|
|
23
|
+
|
|
24
|
+
import pytest
|
|
25
|
+
|
|
26
|
+
# test_atomic_decrement.py stubs sys.modules["azure"] et al. with bare,
|
|
27
|
+
# path-less ModuleType objects (guarded by `if _mod not in sys.modules`) so
|
|
28
|
+
# it can run without cloud SDKs installed. If it collects before this file
|
|
29
|
+
# (alphabetically it does), that stub wins the module cache for the rest of
|
|
30
|
+
# the process, and the adapter's lazy `from azure.core.exceptions import
|
|
31
|
+
# ...` imports then fail with "'azure' is not a package" -- even though the
|
|
32
|
+
# real azure SDK is installed here. Drop any such stubs so this file always
|
|
33
|
+
# imports the genuine package fresh.
|
|
34
|
+
for _name in list(sys.modules):
|
|
35
|
+
if _name == "azure" or _name.startswith("azure."):
|
|
36
|
+
del sys.modules[_name]
|
|
37
|
+
|
|
38
|
+
from polydb.adapters.AzureQueueAdapter import AzureQueueAdapter
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _make_adapter() -> AzureQueueAdapter:
|
|
42
|
+
# Bypass the real Azure SDK entirely -- when the full suite runs
|
|
43
|
+
# together, other providers' tests leave azure.storage.queue in a state
|
|
44
|
+
# where importing QueueServiceClient from it fails (a pre-existing
|
|
45
|
+
# cross-test import-cache issue, unrelated to this adapter). Skipping
|
|
46
|
+
# `_initialize_client` sidesteps that import altogether; these tests
|
|
47
|
+
# only care about the adapter's own encode/decode + ack/nack wiring.
|
|
48
|
+
with patch.object(AzureQueueAdapter, "_initialize_client", lambda self: None):
|
|
49
|
+
adapter = AzureQueueAdapter(connection_string="UseDevelopmentStorage=true")
|
|
50
|
+
adapter._client = MagicMock()
|
|
51
|
+
return adapter
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _mock_queue_client(adapter: AzureQueueAdapter, queue_name: str = "default") -> MagicMock:
|
|
55
|
+
client = MagicMock()
|
|
56
|
+
adapter._queues[adapter._normalize_queue_name(queue_name)] = client
|
|
57
|
+
return client
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class TestReceiveReturnsReceiptHandle:
|
|
61
|
+
def test_receive_includes_receipt_handle_key(self):
|
|
62
|
+
adapter = _make_adapter()
|
|
63
|
+
client = _mock_queue_client(adapter)
|
|
64
|
+
|
|
65
|
+
raw_msg = MagicMock()
|
|
66
|
+
raw_msg.id = "msg-123"
|
|
67
|
+
raw_msg.pop_receipt = "pop-abc"
|
|
68
|
+
raw_msg.content = json.dumps({"hello": "world"})
|
|
69
|
+
client.receive_messages.return_value = [raw_msg]
|
|
70
|
+
|
|
71
|
+
[received] = adapter.receive(queue_name="default", max_messages=1)
|
|
72
|
+
|
|
73
|
+
assert received["id"] == "msg-123"
|
|
74
|
+
assert received["pop_receipt"] == "pop-abc"
|
|
75
|
+
assert "receipt_handle" in received
|
|
76
|
+
assert received["receipt_handle"] # truthy -- WorkerPool checks `if receipt_handle`
|
|
77
|
+
|
|
78
|
+
def test_receive_surfaces_dequeue_count(self):
|
|
79
|
+
"""WorkerPool's poison-message guard thresholds on this to
|
|
80
|
+
dead-letter a message the queue keeps redelivering."""
|
|
81
|
+
adapter = _make_adapter()
|
|
82
|
+
client = _mock_queue_client(adapter)
|
|
83
|
+
|
|
84
|
+
raw_msg = MagicMock()
|
|
85
|
+
raw_msg.id = "msg-123"
|
|
86
|
+
raw_msg.pop_receipt = "pop-abc"
|
|
87
|
+
raw_msg.content = json.dumps({"hello": "world"})
|
|
88
|
+
raw_msg.dequeue_count = 7
|
|
89
|
+
client.receive_messages.return_value = [raw_msg]
|
|
90
|
+
|
|
91
|
+
[received] = adapter.receive(queue_name="default", max_messages=1)
|
|
92
|
+
|
|
93
|
+
assert received["dequeue_count"] == 7
|
|
94
|
+
|
|
95
|
+
def test_receipt_handle_round_trips_through_decode(self):
|
|
96
|
+
message_id, pop_receipt = AzureQueueAdapter._decode_receipt(
|
|
97
|
+
AzureQueueAdapter._encode_receipt("msg-123", "pop-abc")
|
|
98
|
+
)
|
|
99
|
+
assert message_id == "msg-123"
|
|
100
|
+
assert pop_receipt == "pop-abc"
|
|
101
|
+
|
|
102
|
+
def test_receive_defaults_to_a_long_visibility_timeout(self):
|
|
103
|
+
"""The Azure SDK's own default (30s) is shorter than a real task
|
|
104
|
+
can take (an LLM call alone can run 30-120s+), so a still-in-flight
|
|
105
|
+
message became visible again and got picked up by a second worker
|
|
106
|
+
mid-processing -- two concurrent executions of the same task. This
|
|
107
|
+
guards against that default silently regressing back to 30s (or
|
|
108
|
+
being dropped entirely, which is the SDK's fallback)."""
|
|
109
|
+
adapter = _make_adapter()
|
|
110
|
+
client = _mock_queue_client(adapter)
|
|
111
|
+
client.receive_messages.return_value = []
|
|
112
|
+
|
|
113
|
+
adapter.receive(queue_name="default", max_messages=1)
|
|
114
|
+
|
|
115
|
+
_, kwargs = client.receive_messages.call_args
|
|
116
|
+
assert kwargs["visibility_timeout"] == AzureQueueAdapter.DEFAULT_VISIBILITY_TIMEOUT
|
|
117
|
+
assert kwargs["visibility_timeout"] >= 300
|
|
118
|
+
|
|
119
|
+
def test_receive_honours_an_explicit_visibility_timeout(self):
|
|
120
|
+
adapter = _make_adapter()
|
|
121
|
+
client = _mock_queue_client(adapter)
|
|
122
|
+
client.receive_messages.return_value = []
|
|
123
|
+
|
|
124
|
+
adapter.receive(queue_name="default", max_messages=1, visibility_timeout=45)
|
|
125
|
+
|
|
126
|
+
_, kwargs = client.receive_messages.call_args
|
|
127
|
+
assert kwargs["visibility_timeout"] == 45
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class TestAckAcceptsSingleReceiptHandle:
|
|
131
|
+
def test_ack_with_generic_single_arg_deletes_using_decoded_parts(self):
|
|
132
|
+
"""Mirrors how WorkerPool/storage_router actually call ack():
|
|
133
|
+
queue.ack(receipt_handle, queue_name) -- a single positional value,
|
|
134
|
+
never a separate message_id.
|
|
135
|
+
"""
|
|
136
|
+
adapter = _make_adapter()
|
|
137
|
+
client = _mock_queue_client(adapter)
|
|
138
|
+
|
|
139
|
+
receipt_handle = AzureQueueAdapter._encode_receipt("msg-123", "pop-abc")
|
|
140
|
+
result = adapter.ack(receipt_handle, "default")
|
|
141
|
+
|
|
142
|
+
assert result is True
|
|
143
|
+
client.delete_message.assert_called_once_with("msg-123", "pop-abc")
|
|
144
|
+
|
|
145
|
+
def test_ack_still_supports_explicit_message_id(self):
|
|
146
|
+
adapter = _make_adapter()
|
|
147
|
+
client = _mock_queue_client(adapter)
|
|
148
|
+
|
|
149
|
+
result = adapter.ack("pop-abc", "default", message_id="msg-123")
|
|
150
|
+
|
|
151
|
+
assert result is True
|
|
152
|
+
client.delete_message.assert_called_once_with("msg-123", "pop-abc")
|
|
153
|
+
|
|
154
|
+
def test_ack_raises_if_receipt_handle_has_no_message_id(self):
|
|
155
|
+
adapter = _make_adapter()
|
|
156
|
+
_mock_queue_client(adapter)
|
|
157
|
+
|
|
158
|
+
with pytest.raises(Exception):
|
|
159
|
+
adapter.ack("", "default")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class TestNack:
|
|
163
|
+
def test_nack_updates_visibility_without_deleting(self):
|
|
164
|
+
adapter = _make_adapter()
|
|
165
|
+
client = _mock_queue_client(adapter)
|
|
166
|
+
|
|
167
|
+
receipt_handle = AzureQueueAdapter._encode_receipt("msg-123", "pop-abc")
|
|
168
|
+
result = adapter.nack("default", receipt_handle, delay=30)
|
|
169
|
+
|
|
170
|
+
assert result is True
|
|
171
|
+
client.update_message.assert_called_once_with(
|
|
172
|
+
"msg-123", pop_receipt="pop-abc", visibility_timeout=30
|
|
173
|
+
)
|
|
174
|
+
client.delete_message.assert_not_called()
|
|
175
|
+
|
|
176
|
+
def test_nack_defaults_delay_to_zero(self):
|
|
177
|
+
adapter = _make_adapter()
|
|
178
|
+
client = _mock_queue_client(adapter)
|
|
179
|
+
|
|
180
|
+
receipt_handle = AzureQueueAdapter._encode_receipt("msg-123", "pop-abc")
|
|
181
|
+
adapter.nack("default", receipt_handle)
|
|
182
|
+
|
|
183
|
+
client.update_message.assert_called_once_with(
|
|
184
|
+
"msg-123", pop_receipt="pop-abc", visibility_timeout=0
|
|
185
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/requirements-generic.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/adapters/S3Adapter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/adapters/__init__.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/advanced_query.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/audit/AuditStorage.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/audit/__init__.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/audit/context.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/audit/manager.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/audit/models.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/base/QueueAdapter.py
RENAMED
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/base/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/decorators.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/monitoring.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/multitenancy.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/services/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/src/polydb/validation.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/tests/test_atomic_decrement.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/tests/test_blockchain.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/tests/test_cloud_factory.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/tests/test_multi_engine.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/tests/test_observability.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.27 → altcodepro_polydb_python-2.3.29}/tests/test_postgresql.py
RENAMED
|
File without changes
|
|
File without changes
|