altcodepro-polydb-python 2.3.7__tar.gz → 2.3.8__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 (81) hide show
  1. {altcodepro_polydb_python-2.3.7/src/altcodepro_polydb_python.egg-info → altcodepro_polydb_python-2.3.8}/PKG-INFO +1 -1
  2. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/pyproject.toml +1 -1
  3. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8/src/altcodepro_polydb_python.egg-info}/PKG-INFO +1 -1
  4. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/AzureTableStorageAdapter.py +76 -55
  5. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/base/NoSQLKVAdapter.py +1 -1
  6. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/databaseFactory.py +22 -2
  7. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/LICENSE +0 -0
  8. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/MANIFEST.in +0 -0
  9. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/README.md +0 -0
  10. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/example_usage.py +0 -0
  11. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/requirements-aws.txt +0 -0
  12. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/requirements-azure.txt +0 -0
  13. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/requirements-dev.txt +0 -0
  14. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/requirements-gcp.txt +0 -0
  15. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/requirements-generic.txt +0 -0
  16. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/requirements.txt +0 -0
  17. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/setup.cfg +0 -0
  18. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/setup.py +0 -0
  19. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/altcodepro_polydb_python.egg-info/SOURCES.txt +0 -0
  20. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/altcodepro_polydb_python.egg-info/dependency_links.txt +0 -0
  21. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/altcodepro_polydb_python.egg-info/requires.txt +0 -0
  22. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/altcodepro_polydb_python.egg-info/top_level.txt +0 -0
  23. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/PolyDB.py +0 -0
  24. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/__init__.py +0 -0
  25. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/AzureBlobStorageAdapter.py +0 -0
  26. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/AzureFileStorageAdapter.py +0 -0
  27. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/AzureQueueAdapter.py +0 -0
  28. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/BlockchainBlobAdapter.py +0 -0
  29. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/BlockchainKVAdapter.py +0 -0
  30. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/BlockchainQueueAdapter.py +0 -0
  31. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/DynamoDBAdapter.py +0 -0
  32. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/EFSAdapter.py +0 -0
  33. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/FirestoreAdapter.py +0 -0
  34. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/GCPPubSubAdapter.py +0 -0
  35. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/GCPStorageAdapter.py +0 -0
  36. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/MongoDBAdapter.py +0 -0
  37. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/PostgreSQLAdapter.py +0 -0
  38. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/S3Adapter.py +0 -0
  39. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/S3CompatibleAdapter.py +0 -0
  40. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/SQSAdapter.py +0 -0
  41. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/VercelBlobAdapter.py +0 -0
  42. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/VercelKVAdapter.py +0 -0
  43. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/VercelQueueAdapter.py +0 -0
  44. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/__init__.py +0 -0
  45. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/advanced_query.py +0 -0
  46. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/audit/AuditStorage.py +0 -0
  47. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/audit/__init__.py +0 -0
  48. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/audit/context.py +0 -0
  49. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/audit/manager.py +0 -0
  50. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/audit/models.py +0 -0
  51. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/base/ObjectStorageAdapter.py +0 -0
  52. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/base/QueueAdapter.py +0 -0
  53. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/base/SharedFilesAdapter.py +0 -0
  54. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/base/__init__.py +0 -0
  55. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/batch.py +0 -0
  56. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/cache.py +0 -0
  57. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/cloudDatabaseFactory.py +0 -0
  58. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/decorators.py +0 -0
  59. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/errors.py +0 -0
  60. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/json_safe.py +0 -0
  61. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/models.py +0 -0
  62. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/monitoring.py +0 -0
  63. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/multitenancy.py +0 -0
  64. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/py.typed +0 -0
  65. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/query.py +0 -0
  66. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/registry.py +0 -0
  67. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/retry.py +0 -0
  68. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/schema.py +0 -0
  69. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/security.py +0 -0
  70. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/types.py +0 -0
  71. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/utils.py +0 -0
  72. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/src/polydb/validation.py +0 -0
  73. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/tests/test_aws.py +0 -0
  74. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/tests/test_azure.py +0 -0
  75. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/tests/test_blockchain.py +0 -0
  76. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/tests/test_cloud_factory.py +0 -0
  77. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/tests/test_gcp.py +0 -0
  78. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/tests/test_mongodb.py +0 -0
  79. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/tests/test_multi_engine.py +0 -0
  80. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/tests/test_postgresql.py +0 -0
  81. {altcodepro_polydb_python-2.3.7 → altcodepro_polydb_python-2.3.8}/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.7
3
+ Version: 2.3.8
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"
7
+ version = "2.3.8"
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.7
3
+ Version: 2.3.8
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
@@ -389,6 +389,63 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
389
389
  # Now return the client
390
390
  return self._client.get_table_client(table_name=table_name)
391
391
 
392
+ def _restore_overflow_properties(self, entity_dict: JsonDict) -> JsonDict:
393
+ """Detect and restore any large properties stored in Blob Storage.
394
+
395
+ Called by both _get_raw and _query_raw.
396
+ """
397
+ restored = {}
398
+
399
+ for k, v in entity_dict.items():
400
+ # Internal fields (starting with _) are kept as-is
401
+ if k.startswith("_"):
402
+ restored[k] = v
403
+ continue
404
+
405
+ # Is this an overflow metadata JSON string?
406
+ if isinstance(v, str) and v.startswith("{") and '"_overflow":' in v:
407
+ try:
408
+ metadata = json.loads(v)
409
+ if not metadata.get("_overflow"):
410
+ restored[k] = v
411
+ continue
412
+
413
+ blob_key = metadata.get("_blob_key")
414
+ checksum = metadata.get("_checksum")
415
+
416
+ if not blob_key:
417
+ restored[k] = v
418
+ continue
419
+
420
+ # Download real value from blob
421
+ blob_data = self._blob_download(blob_key)
422
+
423
+ # Optional but very safe: checksum validation
424
+ if checksum:
425
+ actual_checksum = hashlib.md5(blob_data).hexdigest()
426
+ if actual_checksum != checksum:
427
+ logger.warning(
428
+ f"Checksum mismatch for blob {blob_key} (property '{k}')"
429
+ )
430
+
431
+ # Restore original value
432
+ actual_value = json.loads(blob_data.decode("utf-8"))
433
+ restored[k] = actual_value
434
+ logger.debug(f"Restored large property '{k}' from blob: {blob_key}")
435
+
436
+ continue
437
+
438
+ except Exception as e:
439
+ logger.error(f"Failed to restore overflow property '{k}': {e}")
440
+ # Fall back to raw metadata instead of crashing
441
+ restored[k] = v
442
+ continue
443
+
444
+ # Normal (non-overflow) property
445
+ restored[k] = v
446
+
447
+ return restored
448
+
392
449
  @retry(max_attempts=3, delay=1.0, exceptions=(NoSQLError,))
393
450
  def _put_raw(self, model: type, pk: str, rk: str, data: JsonDict) -> JsonDict:
394
451
  try:
@@ -462,55 +519,37 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
462
519
  safe_pk = self._sanitize_pk_rk(pk)
463
520
  safe_rk = self._sanitize_pk_rk(rk)
464
521
  self._table_client = self._get_table_client(model)
522
+
465
523
  entity = self._table_client.get_entity(safe_pk, safe_rk)
466
524
  entity_dict = dict(entity)
467
525
 
468
- # model isolation
526
+ # Model isolation check
469
527
  if entity_dict.get(_MODEL_FIELD) != model.__qualname__:
470
528
  return None
471
529
 
472
- if entity_dict.get("_overflow"):
473
- blob_key = entity_dict.get("_blob_key")
474
- checksum = entity_dict.get("_checksum")
475
- if not blob_key:
476
- raise NoSQLError("Overflow entity missing _blob_key")
477
-
478
- blob_data = self._blob_download(blob_key)
479
- actual_checksum = hashlib.md5(blob_data).hexdigest()
480
- if checksum and actual_checksum != checksum:
481
- raise NoSQLError(
482
- f"Checksum mismatch: expected {checksum}, got {actual_checksum}"
483
- )
484
-
485
- restored = json.loads(blob_data.decode("utf-8"))
486
- out = self._unpack_entity(restored)
487
- out["_overflow"] = True
488
- out["_blob_key"] = blob_key
489
- out["_checksum"] = checksum
490
- if "id" not in out:
491
- out["id"] = safe_rk
492
- return out
530
+ # Restore any large fields that were moved to blob
531
+ restored_entity = self._restore_overflow_properties(entity_dict)
493
532
 
494
- out = self._unpack_entity(entity_dict)
533
+ out = self._unpack_entity(restored_entity)
495
534
  if "id" not in out:
496
535
  out["id"] = safe_rk
536
+
497
537
  return out
498
538
 
499
539
  except Exception as e:
500
540
  if "ResourceNotFound" in str(e):
501
541
  return None
502
542
  raise NoSQLError(f"Azure Table get failed: {str(e)}")
503
-
543
+
504
544
  @retry(max_attempts=3, delay=1.0, exceptions=(NoSQLError,))
505
545
  def _query_raw(
506
546
  self, model: type, filters: Dict[str, Any], limit: Optional[int]
507
547
  ) -> List[JsonDict]:
508
548
  try:
509
549
  self._table_client = self._get_table_client(model)
510
- # always enforce model filter
511
- eff_filters = dict(filters or {})
512
- # eff_filters[_MODEL_FIELD] = model.__qualname__
513
550
 
551
+ # Build query filter (your original logic kept unchanged)
552
+ eff_filters = dict(filters or {})
514
553
  parts: List[str] = []
515
554
  for orig_k, orig_v in eff_filters.items():
516
555
  if orig_v is None:
@@ -522,7 +561,9 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
522
561
  sk = "RowKey"
523
562
  else:
524
563
  sk = (
525
- self._sanitize_prop_name(orig_k) if orig_k != _MODEL_FIELD else _MODEL_FIELD
564
+ self._sanitize_prop_name(orig_k)
565
+ if orig_k != _MODEL_FIELD
566
+ else _MODEL_FIELD
526
567
  )
527
568
 
528
569
  ev = self._encode_value(orig_v)
@@ -544,40 +585,21 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
544
585
 
545
586
  query_filter = " and ".join(parts) if parts else None
546
587
 
547
- entities = self._table_client.query_entities(query_filter=query_filter) # type: ignore
588
+ entities = self._table_client.query_entities(query_filter=query_filter)
548
589
 
549
590
  results: List[JsonDict] = []
550
591
  count = 0
551
592
  for ent in entities:
552
593
  ent_dict = dict(ent)
553
594
 
554
- # defensive: enforce model even if query_filter omitted
555
- if ent_dict.get(_MODEL_FIELD) != model.__qualname__:
556
- continue
595
+ # Restore any large fields from blob
596
+ restored_entity = self._restore_overflow_properties(ent_dict)
557
597
 
558
- if ent_dict.get("_overflow"):
559
- blob_key = ent_dict.get("_blob_key")
560
- checksum = ent_dict.get("_checksum")
561
- if blob_key:
562
- try:
563
- blob_data = self._blob_download(blob_key)
564
- actual_checksum = hashlib.md5(blob_data).hexdigest()
565
- if checksum and actual_checksum != checksum:
566
- raise NoSQLError("Checksum mismatch")
567
-
568
- restored = json.loads(blob_data.decode("utf-8"))
569
- out = self._unpack_entity(restored)
570
- except Exception as e:
571
- logger.error(f"Blob read failed, falling back to table: {e}")
572
- out = self._unpack_entity(ent_dict)
573
- else:
574
- out = self._unpack_entity(ent_dict)
575
- else:
576
- out = self._unpack_entity(ent_dict)
598
+ out = self._unpack_entity(restored_entity)
577
599
 
578
- # guarantee id
579
- if "id" not in out and out.get("RowKey") is not None:
580
- out["id"] = out["RowKey"]
600
+ # Guarantee 'id' field
601
+ if "id" not in out and "RowKey" in ent_dict:
602
+ out["id"] = ent_dict["RowKey"]
581
603
 
582
604
  results.append(out)
583
605
 
@@ -589,7 +611,6 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
589
611
 
590
612
  except Exception as e:
591
613
  raise NoSQLError(f"Azure Table query failed: {str(e)}")
592
-
593
614
  @retry(max_attempts=3, delay=1.0, exceptions=(NoSQLError,))
594
615
  def _delete_raw(self, model: type, pk: str, rk: str, etag: Optional[str]) -> JsonDict:
595
616
  try:
@@ -258,7 +258,7 @@ class NoSQLKVAdapter:
258
258
  rk = entity_id.get("row_key") or entity_id.get("rk") or entity_id.get("id")
259
259
 
260
260
  else:
261
- pk, rk = self._get_pk_rk(model, {"id": entity_id})
261
+ pk, rk = self._get_pk_rk(model, {"id": entity_id, **data})
262
262
 
263
263
  if not replace:
264
264
  existing = self._get_raw(model, pk, rk) # type: ignore
@@ -554,18 +554,38 @@ class DatabaseFactory:
554
554
  after_plain = None
555
555
  success = False
556
556
  error: Optional[str] = None
557
-
558
557
  def _op() -> JsonDict:
559
558
  nonlocal after_plain, success
560
559
  if self._is_sql(meta, engine_override):
561
560
  result = adapters.sql.update(meta.table, entity_id, data)
562
561
  else:
562
+ pkey = data.get("PartitionKey") or data.get("partition_key") or data.get("pk")
563
+ en_id = entity_id
564
+ if not pkey and before:
565
+ pkey = (
566
+ before.get("PartitionKey")
567
+ or before.get("partition_key")
568
+ or before.get("pk")
569
+ )
570
+ if pkey:
571
+ if isinstance(en_id, dict):
572
+ en_pk = (
573
+ en_id.get("PartitionKey")
574
+ or en_id.get("partition_key")
575
+ or en_id.get("pk")
576
+ )
577
+ if not en_pk:
578
+ en_id["partition_key"] = pkey
579
+ elif isinstance(en_id, str):
580
+ en_id = {"partition_key": pkey, "id": entity_id}
581
+
563
582
  cls = (
564
583
  model
565
584
  if isinstance(model, type)
566
585
  else type(name, (), {"__polydb__": meta.__dict__})
567
586
  )
568
- result = adapters.nosql.patch(cls, entity_id, data, etag=etag, replace=replace)
587
+
588
+ result = adapters.nosql.patch(cls, en_id, data, etag=etag, replace=replace)
569
589
  after_plain = result
570
590
  if self.encryption and encrypted_fields:
571
591
  after_plain = self.encryption.decrypt_fields(result, encrypted_fields)