altcodepro-polydb-python 2.3.27__py3-none-any.whl → 2.3.28__py3-none-any.whl

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: altcodepro-polydb-python
3
- Version: 2.3.27
3
+ Version: 2.3.28
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
@@ -1,11 +1,11 @@
1
- altcodepro_polydb_python-2.3.27.dist-info/licenses/LICENSE,sha256=9X8GLocsBwy-5aR5JGOt2SAMDDPs9Qv-YnqmHBHOXrw,1067
1
+ altcodepro_polydb_python-2.3.28.dist-info/licenses/LICENSE,sha256=9X8GLocsBwy-5aR5JGOt2SAMDDPs9Qv-YnqmHBHOXrw,1067
2
2
  polydb/PolyDB.py,sha256=MG7-nV59zDvrUQwXNEgV4eetHCeW9TYY9DQqtxu_r7k,23543
3
3
  polydb/__init__.py,sha256=d9KSz888A336904TPEjWPYBtyJLPB0lTdcaOnYp8byY,1535
4
4
  polydb/advanced_query.py,sha256=cxMB-EB-qT3bWXJlhmjnMCUtrzogORWyoEfS50Dy7go,4280
5
5
  polydb/batch.py,sha256=_DjWZa1ZXYSk6MLKqFe0eT7SYVRZtYNqZb9bI8Y2sao,4566
6
6
  polydb/cache.py,sha256=JBXF1XEK-fY80ar8SDE893Z1Z116YtXAEG0PaJ0Nkcw,7658
7
7
  polydb/cloudDatabaseFactory.py,sha256=Gp6L__YtgrkGahD8B7ItzXMHCoj2ZUGDjXLS9w0TujY,17780
8
- polydb/databaseFactory.py,sha256=Or3usoXizzrvuZ8X8x8AFdJA4Pul96lqsrGgtVkjTQ4,43861
8
+ polydb/databaseFactory.py,sha256=AdEMRtWcwf0elJ9JhrWS5krre-3RVFfIgt0mA93eJJQ,44068
9
9
  polydb/decorators.py,sha256=L_WP2uXP_k8Ac49SUm-mthbM4jWI-XYfHXEyKzumOww,43062
10
10
  polydb/errors.py,sha256=jF_55fbfg6b6mBc116dzU_uBG9qiIKbr5jvmDuJx-Jw,1573
11
11
  polydb/json_safe.py,sha256=R5PrqAGirqjYKPyy-8KH-lSXjLH0FPr2TSGozy4eheU,149
@@ -23,7 +23,7 @@ polydb/utils.py,sha256=tS_KI1vt-VZ6JftbFxbnVxC0xpie1-zYikTGf1nSoFw,3183
23
23
  polydb/validation.py,sha256=a1o1d02k3c6PWQwkBbw_0nEmIgrdB5RR8OcpNQMn4cA,4810
24
24
  polydb/adapters/AzureBlobStorageAdapter.py,sha256=4vD55Z8DBTzBK66jIJbo5bNMY-AQ61MlP0-P2Fv_JgQ,7083
25
25
  polydb/adapters/AzureFileStorageAdapter.py,sha256=VZNprqlBXCuWUgtqClNT-NrQmRf-XFYEiRA2BLbf-Sc,7046
26
- polydb/adapters/AzureQueueAdapter.py,sha256=5PrKAX4OQxUD5nReZKrInF_mjQVdFcj2aYd0Xp-HjjQ,5254
26
+ polydb/adapters/AzureQueueAdapter.py,sha256=-MLEEAt2yvYjlB5LqT2TwoRa3rVDHDVzXhm1gykqeDM,8052
27
27
  polydb/adapters/AzureTableStorageAdapter.py,sha256=FCWojIRyOQdDyga5sxbHXBfZMdb3T1xn3Q2VX5klebo,27824
28
28
  polydb/adapters/BlockchainBlobAdapter.py,sha256=D01Yua9mkKfaQrxKYApblIyI6DSP0dtNAh4Tav51HJ4,3299
29
29
  polydb/adapters/BlockchainFileAdapter.py,sha256=G749xOVpG20HuKS8zCgi6PMjoJNu-YXK7zitygjLdzM,8335
@@ -35,7 +35,7 @@ polydb/adapters/FirestoreAdapter.py,sha256=e3DEODOZ-UAWULLtGTIED-Ym1JrxOKaOLPQVK
35
35
  polydb/adapters/GCPFilestoreAdapter.py,sha256=yjFQQwsWYWc8mo8XwMViVTWb5_D--xAyTMvE-4AOpNM,3006
36
36
  polydb/adapters/GCPPubSubAdapter.py,sha256=7XNots2VA0ReEDku-rjg-OTYmftIpx5UgnXYDdXNkOo,8692
37
37
  polydb/adapters/GCPStorageAdapter.py,sha256=9yS1Jhcn5_rCRdZ5uOqcRW6Ba-UNb6VOYpwENP-C6Qk,7133
38
- polydb/adapters/MongoDBAdapter.py,sha256=vX3SAHDLbTnHABGesES9N-gYSQqPqdqFLJgd7pYWZzw,7471
38
+ polydb/adapters/MongoDBAdapter.py,sha256=Ak8q_qeAaUTe0Yc2oEefmAbzoODjTWVZQOirW-seBQU,7574
39
39
  polydb/adapters/PostgreSQLAdapter.py,sha256=lUe65kpJkTu6R1Tc1zgl63Fa1PI44UVbWRTKRUSSGak,36579
40
40
  polydb/adapters/S3Adapter.py,sha256=5R0zHAL2SkGFjp1L3bp-IU468bXYdSf6nKx974MN104,7586
41
41
  polydb/adapters/S3CompatibleAdapter.py,sha256=jpafqbAjA8-irdXBrfXa1QJySIzrcUQ6UrFt5h5FAEc,7006
@@ -60,7 +60,7 @@ polydb/observability/logging.py,sha256=gv1SshNjaSkmR98Uv7Qbwq3gkeQUzVCA4ichqHKL6
60
60
  polydb/services/__init__.py,sha256=nMolMXzmhejRSm7f42kU9fCHF0GIpqpejN2kywuBsUQ,241
61
61
  polydb/services/compliance_service.py,sha256=-0ncX7PrXy4-NuyWXIv65cLUsqz0jXzb3LtsD8rlrXE,4983
62
62
  polydb/services/security_service.py,sha256=q7GzK6sbMnBBsc4fuL4oXTd-ui2b-MYQyrWTBvdVNSk,5062
63
- altcodepro_polydb_python-2.3.27.dist-info/METADATA,sha256=U0hT_pcZTOTdbyTH2CzkFvasDDyRqnNRxbyB3zeSby0,12359
64
- altcodepro_polydb_python-2.3.27.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
65
- altcodepro_polydb_python-2.3.27.dist-info/top_level.txt,sha256=WgLFWJoYjUhwvyPxJFl6jYLrVFuBJDX3OABf4ocwk_E,7
66
- altcodepro_polydb_python-2.3.27.dist-info/RECORD,,
63
+ altcodepro_polydb_python-2.3.28.dist-info/METADATA,sha256=mCw1bRtSYL37sOtvm-Rn5fQdjfCwLZPcZkAiOk-PN68,12359
64
+ altcodepro_polydb_python-2.3.28.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
65
+ altcodepro_polydb_python-2.3.28.dist-info/top_level.txt,sha256=WgLFWJoYjUhwvyPxJFl6jYLrVFuBJDX3OABf4ocwk_E,7
66
+ altcodepro_polydb_python-2.3.28.dist-info/RECORD,,
@@ -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
@@ -114,6 +128,13 @@ class AzureQueueAdapter(QueueAdapter):
114
128
  {
115
129
  "id": msg.id,
116
130
  "pop_receipt": msg.pop_receipt,
131
+ # WorkerPool (and the generic UDL queue contract)
132
+ # reads this key to ack/nack. Without it, ack() is
133
+ # always skipped (falsy receipt_handle), the message
134
+ # is never deleted, and it silently reappears once
135
+ # Azure's visibility timeout elapses — replaying the
136
+ # same task forever even after it already ran.
137
+ "receipt_handle": self._encode_receipt(msg.id, msg.pop_receipt),
117
138
  "body": payload,
118
139
  }
119
140
  )
@@ -156,8 +177,14 @@ class AzureQueueAdapter(QueueAdapter):
156
177
  Preferred usage:
157
178
  ack(pop_receipt=..., message_id=...)
158
179
 
159
- If message_id is not provided, this will fail safely.
180
+ Generic callers (WorkerPool, storage_router.queue_ack) only pass a
181
+ single `receipt_handle` positionally and never supply message_id —
182
+ in that case `pop_receipt` here is actually the combined value
183
+ produced by `receive()` (`"<message_id>|<pop_receipt>"`), so decode
184
+ it rather than failing.
160
185
  """
186
+ if not message_id:
187
+ message_id, pop_receipt = self._decode_receipt(pop_receipt)
161
188
  if not message_id:
162
189
  raise QueueError("AzureQueueAdapter.ack requires message_id")
163
190
  queue_name = self._normalize_queue_name(queue_name)
@@ -166,3 +193,34 @@ class AzureQueueAdapter(QueueAdapter):
166
193
  queue_name=queue_name,
167
194
  pop_receipt=pop_receipt,
168
195
  )
196
+
197
+ def nack(
198
+ self,
199
+ queue_name: str,
200
+ ack_id: str,
201
+ *,
202
+ delay: Optional[int] = None,
203
+ ) -> bool:
204
+ """
205
+ Make a message visible again (optionally after `delay` seconds)
206
+ without deleting it, instead of leaving it to reappear only once
207
+ Azure's default visibility timeout elapses.
208
+
209
+ `ack_id` is the combined `"<message_id>|<pop_receipt>"` receipt
210
+ handle produced by `receive()`. Matches the positional order used
211
+ by storage_router.queue_nack: nack(queue_name, ack_id, delay=...).
212
+ """
213
+ message_id, pop_receipt = self._decode_receipt(ack_id)
214
+ if not message_id:
215
+ raise QueueError("AzureQueueAdapter.nack requires a receipt_handle from receive()")
216
+ try:
217
+ queue_name = self._normalize_queue_name(queue_name)
218
+ queue_client = self._get_queue(queue_name)
219
+ queue_client.update_message(
220
+ message_id,
221
+ pop_receipt=pop_receipt,
222
+ visibility_timeout=delay or 0,
223
+ )
224
+ return True
225
+ except Exception as e:
226
+ 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=5000,
53
+ serverSelectionTimeoutMS=int(
54
+ os.getenv("MONGODB_SERVER_SELECTION_TIMEOUT_MS", "5000")
55
+ ),
54
56
  )
55
57
 
56
58
  self._client.server_info()
polydb/databaseFactory.py CHANGED
@@ -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 _ResolvedAdapters(
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 _ResolvedAdapters(
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 _ResolvedAdapters(
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 _ResolvedAdapters(
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 _ResolvedAdapters(
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