altcodepro-polydb-python 2.3.6__py3-none-any.whl → 2.3.7__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.6
3
+ Version: 2.3.7
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,4 +1,4 @@
1
- altcodepro_polydb_python-2.3.6.dist-info/licenses/LICENSE,sha256=9X8GLocsBwy-5aR5JGOt2SAMDDPs9Qv-YnqmHBHOXrw,1067
1
+ altcodepro_polydb_python-2.3.7.dist-info/licenses/LICENSE,sha256=9X8GLocsBwy-5aR5JGOt2SAMDDPs9Qv-YnqmHBHOXrw,1067
2
2
  polydb/PolyDB.py,sha256=p9eDdvBGosE4fNSSAbtq3tHObdKZ-C2V2Q_ia39Ackk,23397
3
3
  polydb/__init__.py,sha256=UhUzfSvmMgKbV2tSME1ooIyfshIBi7_WyU4xl1tWWiA,1454
4
4
  polydb/advanced_query.py,sha256=cxMB-EB-qT3bWXJlhmjnMCUtrzogORWyoEfS50Dy7go,4280
@@ -24,7 +24,7 @@ polydb/validation.py,sha256=a1o1d02k3c6PWQwkBbw_0nEmIgrdB5RR8OcpNQMn4cA,4810
24
24
  polydb/adapters/AzureBlobStorageAdapter.py,sha256=iNscCIeoWy4YJo3IbI7gNam-NUT7t45ayrlPnYfqlCQ,6409
25
25
  polydb/adapters/AzureFileStorageAdapter.py,sha256=OuZY5P-FTQ36954obJN65oSMqmW3d-7QBmXxVGX0lds,6086
26
26
  polydb/adapters/AzureQueueAdapter.py,sha256=5tslwI0DgvMeb20w57aVSSSKiuCJEKrYjN0kGuFcdUI,5276
27
- polydb/adapters/AzureTableStorageAdapter.py,sha256=gRhopMoczGZYFpt7NI53W70hUY1OZ3nTMknURVNGpeM,23670
27
+ polydb/adapters/AzureTableStorageAdapter.py,sha256=xCHTb78lp2g5gSbsC33McuBKLRJtRB4C07uWnODJkeA,22997
28
28
  polydb/adapters/BlockchainBlobAdapter.py,sha256=BXSDT6rDGGE04qM2-dVNAeWk-VcF82JGHAdUJeYHCbI,3320
29
29
  polydb/adapters/BlockchainKVAdapter.py,sha256=5Egic8QyulgYcy9O12iWKq2EDyVEvnXp_ereYgIvbHk,4546
30
30
  polydb/adapters/BlockchainQueueAdapter.py,sha256=K01klT8Eu8c-y1G9Sg2r0PIjay_at9x27kCTTEHPkNY,4179
@@ -47,12 +47,12 @@ polydb/audit/__init__.py,sha256=m_GE7gjLw00zfHX-1SpkF7QZpRE72HO699ZzKzqD3kc,244
47
47
  polydb/audit/context.py,sha256=-A1FMtmr-2snVAHpTrVT80u-D_MCaqX6AoV4Ku2bz_o,1955
48
48
  polydb/audit/manager.py,sha256=KzaaOf5bDfr4M-CkCAZBG_U_4xIBCKDLRAf3hsm-DAk,1236
49
49
  polydb/audit/models.py,sha256=BgkSEQRbjbourxyGcEeJYIYzozwTM-pqTiSOM_BhWHs,2256
50
- polydb/base/NoSQLKVAdapter.py,sha256=ZbDsYFmJKeNolQxpKlyu7FcVCI15bjia_v-WVxHLsd8,11620
50
+ polydb/base/NoSQLKVAdapter.py,sha256=WHnRg1iJh9BhSmILAdJ5UETi7xMlAAWNE8cfJYQwIOM,11549
51
51
  polydb/base/ObjectStorageAdapter.py,sha256=mNdJnhoB3VqSCQvmcoel5PohrVQw7Nrajdd5suGBOvQ,2242
52
52
  polydb/base/QueueAdapter.py,sha256=jFgyG-SUK4nhRNxm2NbzUbwnA9b_5iAC-ikLSUpXRwk,799
53
53
  polydb/base/SharedFilesAdapter.py,sha256=hvmdNNhNxpN46Ob9RLAi8l46GB6JolYyZWnAMuaJ86g,708
54
54
  polydb/base/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
- altcodepro_polydb_python-2.3.6.dist-info/METADATA,sha256=v7uAPRxexlblZNssyEGLnkcSvJYdaxObGk8WQR2NbzE,11910
56
- altcodepro_polydb_python-2.3.6.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
57
- altcodepro_polydb_python-2.3.6.dist-info/top_level.txt,sha256=WgLFWJoYjUhwvyPxJFl6jYLrVFuBJDX3OABf4ocwk_E,7
58
- altcodepro_polydb_python-2.3.6.dist-info/RECORD,,
55
+ altcodepro_polydb_python-2.3.7.dist-info/METADATA,sha256=tXGVPfaBHIrxUl06aRqE1uOQ0MwstL8OsEH-xzva7uE,11910
56
+ altcodepro_polydb_python-2.3.7.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
57
+ altcodepro_polydb_python-2.3.7.dist-info/top_level.txt,sha256=WgLFWJoYjUhwvyPxJFl6jYLrVFuBJDX3OABf4ocwk_E,7
58
+ altcodepro_polydb_python-2.3.7.dist-info/RECORD,,
@@ -30,6 +30,8 @@ _BASE64_RE = re.compile(r"^[A-Za-z0-9+/]*={0,2}$")
30
30
  # ensures model isolation across the same table
31
31
  _MODEL_FIELD = "__polydb_model__"
32
32
 
33
+ logging.getLogger("azure").setLevel(logging.ERROR)
34
+
33
35
 
34
36
  class AzureTableStorageAdapter(NoSQLKVAdapter):
35
37
  """
@@ -199,33 +201,46 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
199
201
  # -----------------------------
200
202
  # Entity pack/unpack
201
203
  # -----------------------------
202
-
203
204
  def _pack_entity(self, model: type, pk: str, rk: str, data: JsonDict) -> JsonDict:
204
- entity: JsonDict = {"PartitionKey": pk, "RowKey": rk}
205
+ """Pack data into Azure Table Storage entity format.
205
206
 
207
+ This version is much simpler, more readable, and fixes the fragile
208
+ revmap/skey logic that was likely causing normal fields to disappear.
209
+ """
210
+ entity: JsonDict = {
211
+ "PartitionKey": pk,
212
+ "RowKey": rk,
213
+ }
206
214
  entity[_MODEL_FIELD] = model.__qualname__
207
-
208
215
  keymap: Dict[str, str] = {}
209
- revmap: Dict[str, str] = {}
210
216
 
211
217
  for orig_key, orig_val in (data or {}).items():
212
- if str(orig_key) in self._RESERVED or str(orig_key) in ("PartitionKey", "RowKey"):
218
+ orig_key_str = str(orig_key)
219
+
220
+ # Skip Azure-reserved or special keys
221
+ if orig_key_str in self._RESERVED or orig_key_str in ("PartitionKey", "RowKey"):
213
222
  continue
214
223
 
215
- skey = revmap.get(str(orig_key))
216
- if not skey:
217
- skey = self._sanitize_prop_name(orig_key)
218
- base = skey
219
- i = 1
220
- while skey in entity:
221
- skey = f"{base}_{i}"
222
- i += 1
223
- revmap[str(orig_key)] = skey
224
- keymap[skey] = str(orig_key)
224
+ # Sanitize property name so it is valid for Azure Table Storage
225
+ skey = self._sanitize_prop_name(orig_key)
226
+
227
+ # Prevent key collisions (extremely rare but safe)
228
+ base = skey
229
+ counter = 1
230
+ while skey in entity:
231
+ skey = f"{base}_{counter}"
232
+ counter += 1
225
233
 
234
+ # Remember the original key name so _unpack_entity can restore it
235
+ keymap[skey] = orig_key_str
236
+
237
+ # Encode the value (must return something Azure Table accepts)
226
238
  entity[skey] = self._encode_value(orig_val)
227
239
 
228
- entity["__keymap__"] = json.dumps(keymap, default=json_safe)
240
+ # Store keymap only if we actually have fields (internal use)
241
+ if keymap:
242
+ entity["__keymap__"] = json.dumps(keymap, default=json_safe)
243
+
229
244
  return entity
230
245
 
231
246
  def _unpack_entity(self, entity: JsonDict) -> JsonDict:
@@ -380,96 +395,55 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
380
395
  self._table_client = self._get_table_client(model)
381
396
  safe_pk = self._sanitize_pk_rk(pk)
382
397
  safe_rk = self._sanitize_pk_rk(rk)
383
-
384
398
  # Pack entity (encoded for Azure Table)
385
399
  entity = self._pack_entity(model, safe_pk, safe_rk, data)
386
-
387
400
  # ---------------------------------------------------
388
401
  # SIZE ESTIMATION (use ORIGINAL payload, not packed)
389
402
  # ---------------------------------------------------
390
403
  MAX_PROPERTY_CHARS = 30 * 1024 # ~30K safe under UTF-16 32K limit
391
- MAX_ENTITY_SIZE = 40 * 1024 # conservative total threshold
392
404
 
393
405
  def _is_large_string(val: Any) -> bool:
394
406
  return isinstance(val, str) and len(val) > MAX_PROPERTY_CHARS
395
407
 
396
- has_large_property = False
397
- large_key = None
398
-
408
+ large_val_dict = {}
399
409
  for key, value in entity.items():
400
410
  if _is_large_string(value):
401
- has_large_property = True
402
- large_key = key
403
411
  logger.warning(
404
412
  f"LARGE PROPERTY DETECTED: {key} length={len(value)} chars → forcing blob overflow"
405
413
  )
406
- break
407
-
408
- payload_json = json.dumps(data, default=json_safe)
409
- payload_bytes = payload_json.encode("utf-8")
410
- payload_size = len(payload_bytes)
411
-
412
- force_blob = has_large_property or payload_size > MAX_ENTITY_SIZE
413
-
414
- # ---------------------------------------------------
415
- # BLOB OVERFLOW PATH
416
- # ---------------------------------------------------
417
- if force_blob:
418
- logger.info(
419
- f"Entity exceeds safe limits → using blob storage "
420
- f"(size={payload_size // 1024} KB, large_key={large_key})"
421
- )
422
-
423
- checksum = hashlib.md5(payload_bytes).hexdigest()
424
- blob_key = self._blob_key(safe_pk, safe_rk, checksum)
425
-
426
- # Upload ORIGINAL payload (not packed entity)
427
- self._blob_upload(blob_key, payload_bytes)
428
-
429
- # Build reference entity
430
- reference_entity = {
431
- "PartitionKey": safe_pk,
432
- "RowKey": safe_rk,
433
- _MODEL_FIELD: model.__qualname__,
434
- "_overflow": True,
435
- "_blob_key": blob_key,
436
- "_size": payload_size,
437
- "_checksum": checksum,
438
- "__keymap__": entity.get("__keymap__", "{}"),
439
- }
440
-
441
- # Keep only small queryable fields
442
- for k, v in entity.items():
443
- if k in ("PartitionKey", "RowKey", "__keymap__", _MODEL_FIELD):
444
- continue
445
- if k.startswith("_"):
446
- continue
447
-
448
- if isinstance(v, (str, bool, int, float)):
449
- if isinstance(v, str) and len(v) > 2000:
450
- continue
451
- reference_entity[k] = v
452
-
453
- elif isinstance(v, datetime):
454
- reference_entity[k] = v
455
- self._table_client = self._get_table_client(model)
456
- self._table_client.upsert_entity(reference_entity)
457
-
458
- logger.info(f"Stored in blob: {blob_key} ({payload_size // 1024} KB)")
459
-
460
- restored = self._unpack_entity(entity)
461
- restored["_overflow"] = True
462
- restored["_blob_key"] = blob_key
463
- restored["_checksum"] = checksum
464
- restored["_size"] = payload_size
465
- restored["id"] = safe_rk
466
-
467
- return restored
414
+ payload_json = json.dumps(value, default=json_safe)
415
+ payload_bytes = payload_json.encode("utf-8")
416
+ payload_size = len(payload_bytes)
417
+ checksum = hashlib.md5(payload_bytes).hexdigest()
418
+ val_key = self._sanitize_blob_part(key)
419
+ blob_key = self._blob_key(f"{safe_pk}_{safe_rk}", val_key, checksum)
420
+ self._blob_upload(blob_key, payload_bytes)
421
+ logger.info(f"Stored in blob: {blob_key} ({payload_size // 1024} KB)")
422
+ large_val_dict[key] = {
423
+ "PartitionKey": safe_pk,
424
+ "RowKey": safe_rk,
425
+ _MODEL_FIELD: model.__qualname__,
426
+ "_overflow": True,
427
+ "_blob_key": blob_key,
428
+ "_size": payload_size,
429
+ "_checksum": checksum,
430
+ }
431
+
432
+ reference_entity = {
433
+ _MODEL_FIELD: model.__qualname__,
434
+ }
435
+ for k, v in entity.items():
436
+ if k.startswith("_"):
437
+ continue
438
+ if k in large_val_dict:
439
+ metadata = large_val_dict[k]
440
+ reference_entity[k] = json.dumps(metadata, default=json_safe) # ← JSON string
441
+ logger.info(f"Overflow reference stored for {k} → {metadata['_blob_key']}")
442
+ else:
443
+ reference_entity[k] = v
468
444
 
469
- # ---------------------------------------------------
470
- # NORMAL TABLE STORAGE
471
- # ---------------------------------------------------
472
- self._table_client.upsert_entity(entity)
445
+ self._table_client = self._get_table_client(model)
446
+ self._table_client.upsert_entity(reference_entity)
473
447
 
474
448
  restored = self._unpack_entity(entity)
475
449
  restored["id"] = safe_rk
@@ -535,7 +509,7 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
535
509
  self._table_client = self._get_table_client(model)
536
510
  # always enforce model filter
537
511
  eff_filters = dict(filters or {})
538
- eff_filters[_MODEL_FIELD] = model.__qualname__
512
+ # eff_filters[_MODEL_FIELD] = model.__qualname__
539
513
 
540
514
  parts: List[str] = []
541
515
  for orig_k, orig_v in eff_filters.items():
@@ -36,8 +36,8 @@ class NoSQLKVAdapter:
36
36
  def _get_pk_rk(self, model: type, data: JsonDict) -> Tuple[str, str]:
37
37
  """Extract PK/RK from model metadata"""
38
38
  meta = getattr(model, "__polydb__", {})
39
- pk_field = meta.get("pk_field") or meta.get("partition_key", "tenant_id")
40
- rk_field = meta.get("rk_field") or meta.get("sort_key", "id")
39
+ pk_field = meta.get("pk_field") or meta.get("partition_key", "tenant_id")
40
+ rk_field = meta.get("rk_field") or meta.get("sort_key", "id")
41
41
 
42
42
  if self.partition_config:
43
43
  try:
@@ -217,8 +217,7 @@ class NoSQLKVAdapter:
217
217
  # Protocol implementation
218
218
  def put(self, model: type, data: JsonDict) -> JsonDict:
219
219
  pk, rk = self._get_pk_rk(model, data)
220
- store_data, _ = self._check_overflow(data)
221
- return self._put_raw(model, pk, rk, store_data)
220
+ return self._put_raw(model, pk, rk, data)
222
221
 
223
222
  def query(
224
223
  self,
@@ -257,7 +256,7 @@ class NoSQLKVAdapter:
257
256
  if isinstance(entity_id, dict):
258
257
  pk = entity_id.get("partition_key") or entity_id.get("pk")
259
258
  rk = entity_id.get("row_key") or entity_id.get("rk") or entity_id.get("id")
260
-
259
+
261
260
  else:
262
261
  pk, rk = self._get_pk_rk(model, {"id": entity_id})
263
262