altcodepro-polydb-python 2.3.6__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.
- {altcodepro_polydb_python-2.3.6/src/altcodepro_polydb_python.egg-info → altcodepro_polydb_python-2.3.8}/PKG-INFO +1 -1
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/pyproject.toml +1 -1
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8/src/altcodepro_polydb_python.egg-info}/PKG-INFO +1 -1
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/AzureTableStorageAdapter.py +140 -145
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/base/NoSQLKVAdapter.py +5 -6
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/databaseFactory.py +22 -2
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/LICENSE +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/MANIFEST.in +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/README.md +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/example_usage.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/requirements-aws.txt +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/requirements-azure.txt +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/requirements-dev.txt +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/requirements-gcp.txt +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/requirements-generic.txt +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/requirements.txt +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/setup.cfg +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/setup.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/altcodepro_polydb_python.egg-info/SOURCES.txt +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/altcodepro_polydb_python.egg-info/dependency_links.txt +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/altcodepro_polydb_python.egg-info/requires.txt +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/altcodepro_polydb_python.egg-info/top_level.txt +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/PolyDB.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/__init__.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/AzureBlobStorageAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/AzureFileStorageAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/AzureQueueAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/BlockchainBlobAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/BlockchainKVAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/BlockchainQueueAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/DynamoDBAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/EFSAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/FirestoreAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/GCPPubSubAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/GCPStorageAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/MongoDBAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/PostgreSQLAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/S3Adapter.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/S3CompatibleAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/SQSAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/VercelBlobAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/VercelKVAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/VercelQueueAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/__init__.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/advanced_query.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/audit/AuditStorage.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/audit/__init__.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/audit/context.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/audit/manager.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/audit/models.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/base/ObjectStorageAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/base/QueueAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/base/SharedFilesAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/base/__init__.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/batch.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/cache.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/cloudDatabaseFactory.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/decorators.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/errors.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/json_safe.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/models.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/monitoring.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/multitenancy.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/py.typed +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/query.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/registry.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/retry.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/schema.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/security.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/types.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/utils.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/validation.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/tests/test_aws.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/tests/test_azure.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/tests/test_blockchain.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/tests/test_cloud_factory.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/tests/test_gcp.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/tests/test_mongodb.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/tests/test_multi_engine.py +0 -0
- {altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/tests/test_postgresql.py +0 -0
- {altcodepro_polydb_python-2.3.6 → 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.
|
|
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
|
+
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.
|
|
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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
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:
|
|
@@ -374,102 +389,118 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
|
|
|
374
389
|
# Now return the client
|
|
375
390
|
return self._client.get_table_client(table_name=table_name)
|
|
376
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
|
+
|
|
377
449
|
@retry(max_attempts=3, delay=1.0, exceptions=(NoSQLError,))
|
|
378
450
|
def _put_raw(self, model: type, pk: str, rk: str, data: JsonDict) -> JsonDict:
|
|
379
451
|
try:
|
|
380
452
|
self._table_client = self._get_table_client(model)
|
|
381
453
|
safe_pk = self._sanitize_pk_rk(pk)
|
|
382
454
|
safe_rk = self._sanitize_pk_rk(rk)
|
|
383
|
-
|
|
384
455
|
# Pack entity (encoded for Azure Table)
|
|
385
456
|
entity = self._pack_entity(model, safe_pk, safe_rk, data)
|
|
386
|
-
|
|
387
457
|
# ---------------------------------------------------
|
|
388
458
|
# SIZE ESTIMATION (use ORIGINAL payload, not packed)
|
|
389
459
|
# ---------------------------------------------------
|
|
390
460
|
MAX_PROPERTY_CHARS = 30 * 1024 # ~30K safe under UTF-16 32K limit
|
|
391
|
-
MAX_ENTITY_SIZE = 40 * 1024 # conservative total threshold
|
|
392
461
|
|
|
393
462
|
def _is_large_string(val: Any) -> bool:
|
|
394
463
|
return isinstance(val, str) and len(val) > MAX_PROPERTY_CHARS
|
|
395
464
|
|
|
396
|
-
|
|
397
|
-
large_key = None
|
|
398
|
-
|
|
465
|
+
large_val_dict = {}
|
|
399
466
|
for key, value in entity.items():
|
|
400
467
|
if _is_large_string(value):
|
|
401
|
-
has_large_property = True
|
|
402
|
-
large_key = key
|
|
403
468
|
logger.warning(
|
|
404
469
|
f"LARGE PROPERTY DETECTED: {key} length={len(value)} chars → forcing blob overflow"
|
|
405
470
|
)
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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
|
|
471
|
+
payload_json = json.dumps(value, default=json_safe)
|
|
472
|
+
payload_bytes = payload_json.encode("utf-8")
|
|
473
|
+
payload_size = len(payload_bytes)
|
|
474
|
+
checksum = hashlib.md5(payload_bytes).hexdigest()
|
|
475
|
+
val_key = self._sanitize_blob_part(key)
|
|
476
|
+
blob_key = self._blob_key(f"{safe_pk}_{safe_rk}", val_key, checksum)
|
|
477
|
+
self._blob_upload(blob_key, payload_bytes)
|
|
478
|
+
logger.info(f"Stored in blob: {blob_key} ({payload_size // 1024} KB)")
|
|
479
|
+
large_val_dict[key] = {
|
|
480
|
+
"PartitionKey": safe_pk,
|
|
481
|
+
"RowKey": safe_rk,
|
|
482
|
+
_MODEL_FIELD: model.__qualname__,
|
|
483
|
+
"_overflow": True,
|
|
484
|
+
"_blob_key": blob_key,
|
|
485
|
+
"_size": payload_size,
|
|
486
|
+
"_checksum": checksum,
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
reference_entity = {
|
|
490
|
+
_MODEL_FIELD: model.__qualname__,
|
|
491
|
+
}
|
|
492
|
+
for k, v in entity.items():
|
|
493
|
+
if k.startswith("_"):
|
|
494
|
+
continue
|
|
495
|
+
if k in large_val_dict:
|
|
496
|
+
metadata = large_val_dict[k]
|
|
497
|
+
reference_entity[k] = json.dumps(metadata, default=json_safe) # ← JSON string
|
|
498
|
+
logger.info(f"Overflow reference stored for {k} → {metadata['_blob_key']}")
|
|
499
|
+
else:
|
|
500
|
+
reference_entity[k] = v
|
|
468
501
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
# ---------------------------------------------------
|
|
472
|
-
self._table_client.upsert_entity(entity)
|
|
502
|
+
self._table_client = self._get_table_client(model)
|
|
503
|
+
self._table_client.upsert_entity(reference_entity)
|
|
473
504
|
|
|
474
505
|
restored = self._unpack_entity(entity)
|
|
475
506
|
restored["id"] = safe_rk
|
|
@@ -488,55 +519,37 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
|
|
|
488
519
|
safe_pk = self._sanitize_pk_rk(pk)
|
|
489
520
|
safe_rk = self._sanitize_pk_rk(rk)
|
|
490
521
|
self._table_client = self._get_table_client(model)
|
|
522
|
+
|
|
491
523
|
entity = self._table_client.get_entity(safe_pk, safe_rk)
|
|
492
524
|
entity_dict = dict(entity)
|
|
493
525
|
|
|
494
|
-
#
|
|
526
|
+
# Model isolation check
|
|
495
527
|
if entity_dict.get(_MODEL_FIELD) != model.__qualname__:
|
|
496
528
|
return None
|
|
497
529
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
checksum = entity_dict.get("_checksum")
|
|
501
|
-
if not blob_key:
|
|
502
|
-
raise NoSQLError("Overflow entity missing _blob_key")
|
|
503
|
-
|
|
504
|
-
blob_data = self._blob_download(blob_key)
|
|
505
|
-
actual_checksum = hashlib.md5(blob_data).hexdigest()
|
|
506
|
-
if checksum and actual_checksum != checksum:
|
|
507
|
-
raise NoSQLError(
|
|
508
|
-
f"Checksum mismatch: expected {checksum}, got {actual_checksum}"
|
|
509
|
-
)
|
|
510
|
-
|
|
511
|
-
restored = json.loads(blob_data.decode("utf-8"))
|
|
512
|
-
out = self._unpack_entity(restored)
|
|
513
|
-
out["_overflow"] = True
|
|
514
|
-
out["_blob_key"] = blob_key
|
|
515
|
-
out["_checksum"] = checksum
|
|
516
|
-
if "id" not in out:
|
|
517
|
-
out["id"] = safe_rk
|
|
518
|
-
return out
|
|
530
|
+
# Restore any large fields that were moved to blob
|
|
531
|
+
restored_entity = self._restore_overflow_properties(entity_dict)
|
|
519
532
|
|
|
520
|
-
out = self._unpack_entity(
|
|
533
|
+
out = self._unpack_entity(restored_entity)
|
|
521
534
|
if "id" not in out:
|
|
522
535
|
out["id"] = safe_rk
|
|
536
|
+
|
|
523
537
|
return out
|
|
524
538
|
|
|
525
539
|
except Exception as e:
|
|
526
540
|
if "ResourceNotFound" in str(e):
|
|
527
541
|
return None
|
|
528
542
|
raise NoSQLError(f"Azure Table get failed: {str(e)}")
|
|
529
|
-
|
|
543
|
+
|
|
530
544
|
@retry(max_attempts=3, delay=1.0, exceptions=(NoSQLError,))
|
|
531
545
|
def _query_raw(
|
|
532
546
|
self, model: type, filters: Dict[str, Any], limit: Optional[int]
|
|
533
547
|
) -> List[JsonDict]:
|
|
534
548
|
try:
|
|
535
549
|
self._table_client = self._get_table_client(model)
|
|
536
|
-
# always enforce model filter
|
|
537
|
-
eff_filters = dict(filters or {})
|
|
538
|
-
eff_filters[_MODEL_FIELD] = model.__qualname__
|
|
539
550
|
|
|
551
|
+
# Build query filter (your original logic kept unchanged)
|
|
552
|
+
eff_filters = dict(filters or {})
|
|
540
553
|
parts: List[str] = []
|
|
541
554
|
for orig_k, orig_v in eff_filters.items():
|
|
542
555
|
if orig_v is None:
|
|
@@ -548,7 +561,9 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
|
|
|
548
561
|
sk = "RowKey"
|
|
549
562
|
else:
|
|
550
563
|
sk = (
|
|
551
|
-
self._sanitize_prop_name(orig_k)
|
|
564
|
+
self._sanitize_prop_name(orig_k)
|
|
565
|
+
if orig_k != _MODEL_FIELD
|
|
566
|
+
else _MODEL_FIELD
|
|
552
567
|
)
|
|
553
568
|
|
|
554
569
|
ev = self._encode_value(orig_v)
|
|
@@ -570,40 +585,21 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
|
|
|
570
585
|
|
|
571
586
|
query_filter = " and ".join(parts) if parts else None
|
|
572
587
|
|
|
573
|
-
entities = self._table_client.query_entities(query_filter=query_filter)
|
|
588
|
+
entities = self._table_client.query_entities(query_filter=query_filter)
|
|
574
589
|
|
|
575
590
|
results: List[JsonDict] = []
|
|
576
591
|
count = 0
|
|
577
592
|
for ent in entities:
|
|
578
593
|
ent_dict = dict(ent)
|
|
579
594
|
|
|
580
|
-
#
|
|
581
|
-
|
|
582
|
-
continue
|
|
595
|
+
# Restore any large fields from blob
|
|
596
|
+
restored_entity = self._restore_overflow_properties(ent_dict)
|
|
583
597
|
|
|
584
|
-
|
|
585
|
-
blob_key = ent_dict.get("_blob_key")
|
|
586
|
-
checksum = ent_dict.get("_checksum")
|
|
587
|
-
if blob_key:
|
|
588
|
-
try:
|
|
589
|
-
blob_data = self._blob_download(blob_key)
|
|
590
|
-
actual_checksum = hashlib.md5(blob_data).hexdigest()
|
|
591
|
-
if checksum and actual_checksum != checksum:
|
|
592
|
-
raise NoSQLError("Checksum mismatch")
|
|
593
|
-
|
|
594
|
-
restored = json.loads(blob_data.decode("utf-8"))
|
|
595
|
-
out = self._unpack_entity(restored)
|
|
596
|
-
except Exception as e:
|
|
597
|
-
logger.error(f"Blob read failed, falling back to table: {e}")
|
|
598
|
-
out = self._unpack_entity(ent_dict)
|
|
599
|
-
else:
|
|
600
|
-
out = self._unpack_entity(ent_dict)
|
|
601
|
-
else:
|
|
602
|
-
out = self._unpack_entity(ent_dict)
|
|
598
|
+
out = self._unpack_entity(restored_entity)
|
|
603
599
|
|
|
604
|
-
#
|
|
605
|
-
if "id" not in out and
|
|
606
|
-
out["id"] =
|
|
600
|
+
# Guarantee 'id' field
|
|
601
|
+
if "id" not in out and "RowKey" in ent_dict:
|
|
602
|
+
out["id"] = ent_dict["RowKey"]
|
|
607
603
|
|
|
608
604
|
results.append(out)
|
|
609
605
|
|
|
@@ -615,7 +611,6 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
|
|
|
615
611
|
|
|
616
612
|
except Exception as e:
|
|
617
613
|
raise NoSQLError(f"Azure Table query failed: {str(e)}")
|
|
618
|
-
|
|
619
614
|
@retry(max_attempts=3, delay=1.0, exceptions=(NoSQLError,))
|
|
620
615
|
def _delete_raw(self, model: type, pk: str, rk: str, etag: Optional[str]) -> JsonDict:
|
|
621
616
|
try:
|
{altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/base/NoSQLKVAdapter.py
RENAMED
|
@@ -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
|
|
40
|
-
rk_field = meta.get("rk_field") or
|
|
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
|
-
|
|
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,9 +256,9 @@ 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
|
-
pk, rk = self._get_pk_rk(model, {"id": entity_id})
|
|
261
|
+
pk, rk = self._get_pk_rk(model, {"id": entity_id, **data})
|
|
263
262
|
|
|
264
263
|
if not replace:
|
|
265
264
|
existing = self._get_raw(model, pk, rk) # type: ignore
|
{altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/databaseFactory.py
RENAMED
|
@@ -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
|
-
|
|
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)
|
|
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
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/EFSAdapter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/S3Adapter.py
RENAMED
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/SQSAdapter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/adapters/__init__.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/advanced_query.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/audit/AuditStorage.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/audit/__init__.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/audit/context.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/audit/manager.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/audit/models.py
RENAMED
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/base/QueueAdapter.py
RENAMED
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/base/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/src/polydb/cloudDatabaseFactory.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/tests/test_cloud_factory.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.6 → altcodepro_polydb_python-2.3.8}/tests/test_multi_engine.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|