altcodepro-polydb-python 2.3.5__tar.gz → 2.3.7__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.5/src/altcodepro_polydb_python.egg-info → altcodepro_polydb_python-2.3.7}/PKG-INFO +1 -1
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/pyproject.toml +1 -1
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7/src/altcodepro_polydb_python.egg-info}/PKG-INFO +1 -1
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/adapters/AzureTableStorageAdapter.py +131 -116
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/base/NoSQLKVAdapter.py +4 -4
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/cloudDatabaseFactory.py +0 -2
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/databaseFactory.py +8 -4
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/LICENSE +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/MANIFEST.in +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/README.md +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/example_usage.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/requirements-aws.txt +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/requirements-azure.txt +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/requirements-dev.txt +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/requirements-gcp.txt +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/requirements-generic.txt +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/requirements.txt +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/setup.cfg +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/setup.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/altcodepro_polydb_python.egg-info/SOURCES.txt +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/altcodepro_polydb_python.egg-info/dependency_links.txt +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/altcodepro_polydb_python.egg-info/requires.txt +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/altcodepro_polydb_python.egg-info/top_level.txt +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/PolyDB.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/__init__.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/adapters/AzureBlobStorageAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/adapters/AzureFileStorageAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/adapters/AzureQueueAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/adapters/BlockchainBlobAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/adapters/BlockchainKVAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/adapters/BlockchainQueueAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/adapters/DynamoDBAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/adapters/EFSAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/adapters/FirestoreAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/adapters/GCPPubSubAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/adapters/GCPStorageAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/adapters/MongoDBAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/adapters/PostgreSQLAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/adapters/S3Adapter.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/adapters/S3CompatibleAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/adapters/SQSAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/adapters/VercelBlobAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/adapters/VercelKVAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/adapters/VercelQueueAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/adapters/__init__.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/advanced_query.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/audit/AuditStorage.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/audit/__init__.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/audit/context.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/audit/manager.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/audit/models.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/base/ObjectStorageAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/base/QueueAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/base/SharedFilesAdapter.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/base/__init__.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/batch.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/cache.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/decorators.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/errors.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/json_safe.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/models.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/monitoring.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/multitenancy.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/py.typed +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/query.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/registry.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/retry.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/schema.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/security.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/types.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/utils.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/validation.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/tests/test_aws.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/tests/test_azure.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/tests/test_blockchain.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/tests/test_cloud_factory.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/tests/test_gcp.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/tests/test_mongodb.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/tests/test_multi_engine.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/tests/test_postgresql.py +0 -0
- {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/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.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
|
|
@@ -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.7"
|
|
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.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
|
|
@@ -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
|
"""
|
|
@@ -49,7 +51,6 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
|
|
|
49
51
|
self,
|
|
50
52
|
partition_config: Optional[PartitionConfig] = None,
|
|
51
53
|
connection_string: str = "",
|
|
52
|
-
table_name="",
|
|
53
54
|
container_name="",
|
|
54
55
|
):
|
|
55
56
|
super().__init__(partition_config)
|
|
@@ -57,14 +58,12 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
|
|
|
57
58
|
self.connection_string = (
|
|
58
59
|
connection_string or os.getenv("AZURE_STORAGE_CONNECTION_STRING") or ""
|
|
59
60
|
)
|
|
60
|
-
self.table_name = table_name or os.getenv("AZURE_TABLE_NAME", "defaulttable") or ""
|
|
61
61
|
self.container_name = container_name or os.getenv("AZURE_CONTAINER_NAME", "overflow") or ""
|
|
62
62
|
|
|
63
63
|
if not self.connection_string:
|
|
64
64
|
raise ConnectionError("AZURE_STORAGE_CONNECTION_STRING must be set")
|
|
65
65
|
|
|
66
|
-
self._client = None
|
|
67
|
-
self._table_client = None
|
|
66
|
+
self._client: Any = None
|
|
68
67
|
self._blob_service = None
|
|
69
68
|
self._client_lock = threading.Lock()
|
|
70
69
|
self._initialize_client()
|
|
@@ -77,13 +76,6 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
|
|
|
77
76
|
with self._client_lock:
|
|
78
77
|
if not self._client:
|
|
79
78
|
self._client = TableServiceClient.from_connection_string(self.connection_string)
|
|
80
|
-
self._table_client = self._client.get_table_client(self.table_name)
|
|
81
|
-
|
|
82
|
-
try:
|
|
83
|
-
self._client.create_table_if_not_exists(self.table_name)
|
|
84
|
-
except Exception:
|
|
85
|
-
pass
|
|
86
|
-
|
|
87
79
|
self._blob_service = BlobServiceClient.from_connection_string(
|
|
88
80
|
self.connection_string
|
|
89
81
|
)
|
|
@@ -209,33 +201,46 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
|
|
|
209
201
|
# -----------------------------
|
|
210
202
|
# Entity pack/unpack
|
|
211
203
|
# -----------------------------
|
|
212
|
-
|
|
213
204
|
def _pack_entity(self, model: type, pk: str, rk: str, data: JsonDict) -> JsonDict:
|
|
214
|
-
|
|
215
|
-
|
|
205
|
+
"""Pack data into Azure Table Storage entity format.
|
|
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
|
+
}
|
|
216
214
|
entity[_MODEL_FIELD] = model.__qualname__
|
|
217
|
-
|
|
218
215
|
keymap: Dict[str, str] = {}
|
|
219
|
-
revmap: Dict[str, str] = {}
|
|
220
216
|
|
|
221
217
|
for orig_key, orig_val in (data or {}).items():
|
|
222
|
-
|
|
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"):
|
|
223
222
|
continue
|
|
224
223
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
|
233
|
+
|
|
234
|
+
# Remember the original key name so _unpack_entity can restore it
|
|
235
|
+
keymap[skey] = orig_key_str
|
|
235
236
|
|
|
237
|
+
# Encode the value (must return something Azure Table accepts)
|
|
236
238
|
entity[skey] = self._encode_value(orig_val)
|
|
237
239
|
|
|
238
|
-
|
|
240
|
+
# Store keymap only if we actually have fields (internal use)
|
|
241
|
+
if keymap:
|
|
242
|
+
entity["__keymap__"] = json.dumps(keymap, default=json_safe)
|
|
243
|
+
|
|
239
244
|
return entity
|
|
240
245
|
|
|
241
246
|
def _unpack_entity(self, entity: JsonDict) -> JsonDict:
|
|
@@ -323,105 +328,122 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
|
|
|
323
328
|
# -----------------------------
|
|
324
329
|
# Required NoSQLKVAdapter hooks
|
|
325
330
|
# -----------------------------
|
|
331
|
+
def _sanitize_table_name(self, name: str) -> str:
|
|
332
|
+
"""Convert collection_name to valid Azure Table name (alphanumeric only)."""
|
|
333
|
+
if not name:
|
|
334
|
+
return "defaulttable"
|
|
335
|
+
|
|
336
|
+
# Remove all invalid characters, keep only letters and numbers
|
|
337
|
+
sanitized = re.sub(r"[^a-zA-Z0-9]", "", name)
|
|
338
|
+
|
|
339
|
+
# Must start with a letter
|
|
340
|
+
if sanitized and sanitized[0].isdigit():
|
|
341
|
+
sanitized = "t" + sanitized
|
|
342
|
+
|
|
343
|
+
# Must be 3-63 characters
|
|
344
|
+
if len(sanitized) < 3:
|
|
345
|
+
sanitized = sanitized + "table"
|
|
346
|
+
if len(sanitized) > 63:
|
|
347
|
+
sanitized = sanitized[:63]
|
|
348
|
+
|
|
349
|
+
return sanitized.lower()
|
|
350
|
+
|
|
351
|
+
def _get_table_name(self, model: type) -> str:
|
|
352
|
+
"""
|
|
353
|
+
Extract collection_name from UDL model definition.
|
|
354
|
+
"""
|
|
355
|
+
# Primary source: UDL definition
|
|
356
|
+
definition = getattr(model, "__udl_definition__", None)
|
|
357
|
+
if definition:
|
|
358
|
+
metadata = getattr(definition, "x_metadata", {}) or {}
|
|
359
|
+
collection_name = metadata.get("collection_name")
|
|
360
|
+
if collection_name:
|
|
361
|
+
return self._sanitize_table_name(collection_name)
|
|
362
|
+
|
|
363
|
+
# Fallback: __polydb__ metadata
|
|
364
|
+
polydb_meta = getattr(model, "__polydb__", None)
|
|
365
|
+
if isinstance(polydb_meta, dict):
|
|
366
|
+
collection = polydb_meta.get("collection") or polydb_meta.get("collection_name")
|
|
367
|
+
if collection:
|
|
368
|
+
return self._sanitize_table_name(str(collection))
|
|
369
|
+
|
|
370
|
+
# Last resort
|
|
371
|
+
return os.getenv("AZURE_TABLE_NAME", "defaulttable") or "defaulttable"
|
|
372
|
+
|
|
373
|
+
def _get_table_client(self, model: type):
|
|
374
|
+
"""
|
|
375
|
+
Get table client + automatically create the table if it doesn't exist.
|
|
376
|
+
This is the recommended pattern for Azure Table Storage.
|
|
377
|
+
"""
|
|
378
|
+
table_name = self._get_table_name(model)
|
|
379
|
+
|
|
380
|
+
try:
|
|
381
|
+
# This is the key call - creates the table if missing
|
|
382
|
+
self._client.create_table_if_not_exists(table_name)
|
|
383
|
+
logger.info(f"✅ Azure Table ensured/created: {table_name}")
|
|
384
|
+
except Exception as e:
|
|
385
|
+
# TableAlreadyExists is normal and safe to ignore
|
|
386
|
+
if "TableAlreadyExists" not in str(e) and "already exists" not in str(e).lower():
|
|
387
|
+
logger.warning(f"Could not create table {table_name}: {e}")
|
|
388
|
+
|
|
389
|
+
# Now return the client
|
|
390
|
+
return self._client.get_table_client(table_name=table_name)
|
|
326
391
|
|
|
327
392
|
@retry(max_attempts=3, delay=1.0, exceptions=(NoSQLError,))
|
|
328
393
|
def _put_raw(self, model: type, pk: str, rk: str, data: JsonDict) -> JsonDict:
|
|
329
394
|
try:
|
|
330
|
-
|
|
331
|
-
raise NoSQLError("Azure Table client not initialized")
|
|
332
|
-
|
|
395
|
+
self._table_client = self._get_table_client(model)
|
|
333
396
|
safe_pk = self._sanitize_pk_rk(pk)
|
|
334
397
|
safe_rk = self._sanitize_pk_rk(rk)
|
|
335
|
-
|
|
336
398
|
# Pack entity (encoded for Azure Table)
|
|
337
399
|
entity = self._pack_entity(model, safe_pk, safe_rk, data)
|
|
338
|
-
|
|
339
400
|
# ---------------------------------------------------
|
|
340
401
|
# SIZE ESTIMATION (use ORIGINAL payload, not packed)
|
|
341
402
|
# ---------------------------------------------------
|
|
342
403
|
MAX_PROPERTY_CHARS = 30 * 1024 # ~30K safe under UTF-16 32K limit
|
|
343
|
-
MAX_ENTITY_SIZE = 40 * 1024 # conservative total threshold
|
|
344
404
|
|
|
345
405
|
def _is_large_string(val: Any) -> bool:
|
|
346
406
|
return isinstance(val, str) and len(val) > MAX_PROPERTY_CHARS
|
|
347
407
|
|
|
348
|
-
|
|
349
|
-
large_key = None
|
|
350
|
-
|
|
408
|
+
large_val_dict = {}
|
|
351
409
|
for key, value in entity.items():
|
|
352
410
|
if _is_large_string(value):
|
|
353
|
-
has_large_property = True
|
|
354
|
-
large_key = key
|
|
355
411
|
logger.warning(
|
|
356
412
|
f"LARGE PROPERTY DETECTED: {key} length={len(value)} chars → forcing blob overflow"
|
|
357
413
|
)
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
"_size": payload_size,
|
|
389
|
-
"_checksum": checksum,
|
|
390
|
-
"__keymap__": entity.get("__keymap__", "{}"),
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
# Keep only small queryable fields
|
|
394
|
-
for k, v in entity.items():
|
|
395
|
-
if k in ("PartitionKey", "RowKey", "__keymap__", _MODEL_FIELD):
|
|
396
|
-
continue
|
|
397
|
-
if k.startswith("_"):
|
|
398
|
-
continue
|
|
399
|
-
|
|
400
|
-
if isinstance(v, (str, bool, int, float)):
|
|
401
|
-
if isinstance(v, str) and len(v) > 2000:
|
|
402
|
-
continue
|
|
403
|
-
reference_entity[k] = v
|
|
404
|
-
|
|
405
|
-
elif isinstance(v, datetime):
|
|
406
|
-
reference_entity[k] = v
|
|
407
|
-
|
|
408
|
-
self._table_client.upsert_entity(reference_entity)
|
|
409
|
-
|
|
410
|
-
logger.info(f"Stored in blob: {blob_key} ({payload_size // 1024} KB)")
|
|
411
|
-
|
|
412
|
-
restored = self._unpack_entity(entity)
|
|
413
|
-
restored["_overflow"] = True
|
|
414
|
-
restored["_blob_key"] = blob_key
|
|
415
|
-
restored["_checksum"] = checksum
|
|
416
|
-
restored["_size"] = payload_size
|
|
417
|
-
restored["id"] = safe_rk
|
|
418
|
-
|
|
419
|
-
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
|
|
420
444
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
# ---------------------------------------------------
|
|
424
|
-
self._table_client.upsert_entity(entity)
|
|
445
|
+
self._table_client = self._get_table_client(model)
|
|
446
|
+
self._table_client.upsert_entity(reference_entity)
|
|
425
447
|
|
|
426
448
|
restored = self._unpack_entity(entity)
|
|
427
449
|
restored["id"] = safe_rk
|
|
@@ -437,12 +459,9 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
|
|
|
437
459
|
@retry(max_attempts=3, delay=1.0, exceptions=(NoSQLError,))
|
|
438
460
|
def _get_raw(self, model: type, pk: str, rk: str) -> Optional[JsonDict]:
|
|
439
461
|
try:
|
|
440
|
-
if not self._table_client:
|
|
441
|
-
return None
|
|
442
|
-
|
|
443
462
|
safe_pk = self._sanitize_pk_rk(pk)
|
|
444
463
|
safe_rk = self._sanitize_pk_rk(rk)
|
|
445
|
-
|
|
464
|
+
self._table_client = self._get_table_client(model)
|
|
446
465
|
entity = self._table_client.get_entity(safe_pk, safe_rk)
|
|
447
466
|
entity_dict = dict(entity)
|
|
448
467
|
|
|
@@ -487,12 +506,10 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
|
|
|
487
506
|
self, model: type, filters: Dict[str, Any], limit: Optional[int]
|
|
488
507
|
) -> List[JsonDict]:
|
|
489
508
|
try:
|
|
490
|
-
|
|
491
|
-
return []
|
|
492
|
-
|
|
509
|
+
self._table_client = self._get_table_client(model)
|
|
493
510
|
# always enforce model filter
|
|
494
511
|
eff_filters = dict(filters or {})
|
|
495
|
-
eff_filters[_MODEL_FIELD] = model.__qualname__
|
|
512
|
+
# eff_filters[_MODEL_FIELD] = model.__qualname__
|
|
496
513
|
|
|
497
514
|
parts: List[str] = []
|
|
498
515
|
for orig_k, orig_v in eff_filters.items():
|
|
@@ -576,9 +593,7 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
|
|
|
576
593
|
@retry(max_attempts=3, delay=1.0, exceptions=(NoSQLError,))
|
|
577
594
|
def _delete_raw(self, model: type, pk: str, rk: str, etag: Optional[str]) -> JsonDict:
|
|
578
595
|
try:
|
|
579
|
-
|
|
580
|
-
return {"deleted": False}
|
|
581
|
-
|
|
596
|
+
self._table_client = self._get_table_client(model)
|
|
582
597
|
safe_pk = self._sanitize_pk_rk(pk)
|
|
583
598
|
safe_rk = self._sanitize_pk_rk(rk)
|
|
584
599
|
|
{altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/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", "
|
|
40
|
-
rk_field = meta.get("rk_field")
|
|
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,6 +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")
|
|
259
|
+
|
|
260
260
|
else:
|
|
261
261
|
pk, rk = self._get_pk_rk(model, {"id": entity_id})
|
|
262
262
|
|
{altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/cloudDatabaseFactory.py
RENAMED
|
@@ -234,7 +234,6 @@ class CloudDatabaseFactory:
|
|
|
234
234
|
from .adapters.AzureTableStorageAdapter import AzureTableStorageAdapter
|
|
235
235
|
|
|
236
236
|
connection_string = ""
|
|
237
|
-
table_name = ""
|
|
238
237
|
container_name = ""
|
|
239
238
|
|
|
240
239
|
if isinstance(cfg, AzureTableConfig):
|
|
@@ -245,7 +244,6 @@ class CloudDatabaseFactory:
|
|
|
245
244
|
instance = AzureTableStorageAdapter(
|
|
246
245
|
partition_config=partition_config,
|
|
247
246
|
connection_string=connection_string,
|
|
248
|
-
table_name=table_name,
|
|
249
247
|
container_name=container_name,
|
|
250
248
|
)
|
|
251
249
|
|
{altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/databaseFactory.py
RENAMED
|
@@ -20,6 +20,10 @@ from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
|
|
|
20
20
|
|
|
21
21
|
from tenacity import retry, stop_after_attempt, wait_exponential
|
|
22
22
|
|
|
23
|
+
from .adapters.PostgreSQLAdapter import PostgreSQLAdapter
|
|
24
|
+
|
|
25
|
+
from .base.NoSQLKVAdapter import NoSQLKVAdapter
|
|
26
|
+
|
|
23
27
|
from .batch import BatchOperations
|
|
24
28
|
from .cache import CacheWarmer, RedisCacheEngine
|
|
25
29
|
from .monitoring import HealthCheck, MetricsCollector, PerformanceMonitor
|
|
@@ -81,8 +85,8 @@ class EngineOverride:
|
|
|
81
85
|
|
|
82
86
|
@dataclass
|
|
83
87
|
class _ResolvedAdapters:
|
|
84
|
-
sql:
|
|
85
|
-
nosql:
|
|
88
|
+
sql: PostgreSQLAdapter
|
|
89
|
+
nosql: NoSQLKVAdapter
|
|
86
90
|
engine_name: str
|
|
87
91
|
|
|
88
92
|
|
|
@@ -281,14 +285,14 @@ class DatabaseFactory:
|
|
|
281
285
|
return self._engine_by_name[name]
|
|
282
286
|
|
|
283
287
|
@property
|
|
284
|
-
def _sql(self) ->
|
|
288
|
+
def _sql(self) -> PostgreSQLAdapter:
|
|
285
289
|
for e in self._engines:
|
|
286
290
|
if e.is_default_sql:
|
|
287
291
|
return e.sql()
|
|
288
292
|
return self._engines[0].sql()
|
|
289
293
|
|
|
290
294
|
@property
|
|
291
|
-
def _nosql(self) ->
|
|
295
|
+
def _nosql(self) -> NoSQLKVAdapter:
|
|
292
296
|
for e in self._engines:
|
|
293
297
|
if e.is_default_nosql:
|
|
294
298
|
return e.nosql()
|
|
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.5 → altcodepro_polydb_python-2.3.7}/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.5 → altcodepro_polydb_python-2.3.7}/src/polydb/adapters/S3Adapter.py
RENAMED
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/adapters/SQSAdapter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/adapters/__init__.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/advanced_query.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/audit/AuditStorage.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/audit/__init__.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/audit/context.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/audit/manager.py
RENAMED
|
File without changes
|
{altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/audit/models.py
RENAMED
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/base/QueueAdapter.py
RENAMED
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/src/polydb/base/__init__.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
|
{altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/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.5 → altcodepro_polydb_python-2.3.7}/tests/test_cloud_factory.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.7}/tests/test_multi_engine.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|