altcodepro-polydb-python 2.3.5__tar.gz → 2.3.6__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.5/src/altcodepro_polydb_python.egg-info → altcodepro_polydb_python-2.3.6}/PKG-INFO +1 -1
  2. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/pyproject.toml +1 -1
  3. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6/src/altcodepro_polydb_python.egg-info}/PKG-INFO +1 -1
  4. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/adapters/AzureTableStorageAdapter.py +66 -25
  5. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/base/NoSQLKVAdapter.py +3 -2
  6. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/cloudDatabaseFactory.py +0 -2
  7. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/databaseFactory.py +8 -4
  8. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/LICENSE +0 -0
  9. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/MANIFEST.in +0 -0
  10. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/README.md +0 -0
  11. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/example_usage.py +0 -0
  12. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/requirements-aws.txt +0 -0
  13. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/requirements-azure.txt +0 -0
  14. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/requirements-dev.txt +0 -0
  15. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/requirements-gcp.txt +0 -0
  16. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/requirements-generic.txt +0 -0
  17. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/requirements.txt +0 -0
  18. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/setup.cfg +0 -0
  19. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/setup.py +0 -0
  20. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/altcodepro_polydb_python.egg-info/SOURCES.txt +0 -0
  21. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/altcodepro_polydb_python.egg-info/dependency_links.txt +0 -0
  22. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/altcodepro_polydb_python.egg-info/requires.txt +0 -0
  23. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/altcodepro_polydb_python.egg-info/top_level.txt +0 -0
  24. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/PolyDB.py +0 -0
  25. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/__init__.py +0 -0
  26. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/adapters/AzureBlobStorageAdapter.py +0 -0
  27. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/adapters/AzureFileStorageAdapter.py +0 -0
  28. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/adapters/AzureQueueAdapter.py +0 -0
  29. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/adapters/BlockchainBlobAdapter.py +0 -0
  30. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/adapters/BlockchainKVAdapter.py +0 -0
  31. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/adapters/BlockchainQueueAdapter.py +0 -0
  32. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/adapters/DynamoDBAdapter.py +0 -0
  33. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/adapters/EFSAdapter.py +0 -0
  34. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/adapters/FirestoreAdapter.py +0 -0
  35. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/adapters/GCPPubSubAdapter.py +0 -0
  36. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/adapters/GCPStorageAdapter.py +0 -0
  37. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/adapters/MongoDBAdapter.py +0 -0
  38. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/adapters/PostgreSQLAdapter.py +0 -0
  39. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/adapters/S3Adapter.py +0 -0
  40. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/adapters/S3CompatibleAdapter.py +0 -0
  41. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/adapters/SQSAdapter.py +0 -0
  42. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/adapters/VercelBlobAdapter.py +0 -0
  43. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/adapters/VercelKVAdapter.py +0 -0
  44. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/adapters/VercelQueueAdapter.py +0 -0
  45. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/adapters/__init__.py +0 -0
  46. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/advanced_query.py +0 -0
  47. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/audit/AuditStorage.py +0 -0
  48. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/audit/__init__.py +0 -0
  49. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/audit/context.py +0 -0
  50. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/audit/manager.py +0 -0
  51. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/audit/models.py +0 -0
  52. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/base/ObjectStorageAdapter.py +0 -0
  53. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/base/QueueAdapter.py +0 -0
  54. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/base/SharedFilesAdapter.py +0 -0
  55. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/base/__init__.py +0 -0
  56. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/batch.py +0 -0
  57. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/cache.py +0 -0
  58. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/decorators.py +0 -0
  59. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/errors.py +0 -0
  60. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/json_safe.py +0 -0
  61. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/models.py +0 -0
  62. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/monitoring.py +0 -0
  63. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/multitenancy.py +0 -0
  64. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/py.typed +0 -0
  65. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/query.py +0 -0
  66. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/registry.py +0 -0
  67. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/retry.py +0 -0
  68. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/schema.py +0 -0
  69. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/security.py +0 -0
  70. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/types.py +0 -0
  71. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/utils.py +0 -0
  72. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/src/polydb/validation.py +0 -0
  73. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/tests/test_aws.py +0 -0
  74. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/tests/test_azure.py +0 -0
  75. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/tests/test_blockchain.py +0 -0
  76. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/tests/test_cloud_factory.py +0 -0
  77. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/tests/test_gcp.py +0 -0
  78. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/tests/test_mongodb.py +0 -0
  79. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/tests/test_multi_engine.py +0 -0
  80. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/tests/test_postgresql.py +0 -0
  81. {altcodepro_polydb_python-2.3.5 → altcodepro_polydb_python-2.3.6}/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.5
3
+ Version: 2.3.6
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.5"
7
+ version = "2.3.6"
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.5
3
+ Version: 2.3.6
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
@@ -49,7 +49,6 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
49
49
  self,
50
50
  partition_config: Optional[PartitionConfig] = None,
51
51
  connection_string: str = "",
52
- table_name="",
53
52
  container_name="",
54
53
  ):
55
54
  super().__init__(partition_config)
@@ -57,14 +56,12 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
57
56
  self.connection_string = (
58
57
  connection_string or os.getenv("AZURE_STORAGE_CONNECTION_STRING") or ""
59
58
  )
60
- self.table_name = table_name or os.getenv("AZURE_TABLE_NAME", "defaulttable") or ""
61
59
  self.container_name = container_name or os.getenv("AZURE_CONTAINER_NAME", "overflow") or ""
62
60
 
63
61
  if not self.connection_string:
64
62
  raise ConnectionError("AZURE_STORAGE_CONNECTION_STRING must be set")
65
63
 
66
- self._client = None
67
- self._table_client = None
64
+ self._client: Any = None
68
65
  self._blob_service = None
69
66
  self._client_lock = threading.Lock()
70
67
  self._initialize_client()
@@ -77,13 +74,6 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
77
74
  with self._client_lock:
78
75
  if not self._client:
79
76
  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
77
  self._blob_service = BlobServiceClient.from_connection_string(
88
78
  self.connection_string
89
79
  )
@@ -323,13 +313,71 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
323
313
  # -----------------------------
324
314
  # Required NoSQLKVAdapter hooks
325
315
  # -----------------------------
316
+ def _sanitize_table_name(self, name: str) -> str:
317
+ """Convert collection_name to valid Azure Table name (alphanumeric only)."""
318
+ if not name:
319
+ return "defaulttable"
320
+
321
+ # Remove all invalid characters, keep only letters and numbers
322
+ sanitized = re.sub(r"[^a-zA-Z0-9]", "", name)
323
+
324
+ # Must start with a letter
325
+ if sanitized and sanitized[0].isdigit():
326
+ sanitized = "t" + sanitized
327
+
328
+ # Must be 3-63 characters
329
+ if len(sanitized) < 3:
330
+ sanitized = sanitized + "table"
331
+ if len(sanitized) > 63:
332
+ sanitized = sanitized[:63]
333
+
334
+ return sanitized.lower()
335
+
336
+ def _get_table_name(self, model: type) -> str:
337
+ """
338
+ Extract collection_name from UDL model definition.
339
+ """
340
+ # Primary source: UDL definition
341
+ definition = getattr(model, "__udl_definition__", None)
342
+ if definition:
343
+ metadata = getattr(definition, "x_metadata", {}) or {}
344
+ collection_name = metadata.get("collection_name")
345
+ if collection_name:
346
+ return self._sanitize_table_name(collection_name)
347
+
348
+ # Fallback: __polydb__ metadata
349
+ polydb_meta = getattr(model, "__polydb__", None)
350
+ if isinstance(polydb_meta, dict):
351
+ collection = polydb_meta.get("collection") or polydb_meta.get("collection_name")
352
+ if collection:
353
+ return self._sanitize_table_name(str(collection))
354
+
355
+ # Last resort
356
+ return os.getenv("AZURE_TABLE_NAME", "defaulttable") or "defaulttable"
357
+
358
+ def _get_table_client(self, model: type):
359
+ """
360
+ Get table client + automatically create the table if it doesn't exist.
361
+ This is the recommended pattern for Azure Table Storage.
362
+ """
363
+ table_name = self._get_table_name(model)
364
+
365
+ try:
366
+ # This is the key call - creates the table if missing
367
+ self._client.create_table_if_not_exists(table_name)
368
+ logger.info(f"✅ Azure Table ensured/created: {table_name}")
369
+ except Exception as e:
370
+ # TableAlreadyExists is normal and safe to ignore
371
+ if "TableAlreadyExists" not in str(e) and "already exists" not in str(e).lower():
372
+ logger.warning(f"Could not create table {table_name}: {e}")
373
+
374
+ # Now return the client
375
+ return self._client.get_table_client(table_name=table_name)
326
376
 
327
377
  @retry(max_attempts=3, delay=1.0, exceptions=(NoSQLError,))
328
378
  def _put_raw(self, model: type, pk: str, rk: str, data: JsonDict) -> JsonDict:
329
379
  try:
330
- if not self._table_client:
331
- raise NoSQLError("Azure Table client not initialized")
332
-
380
+ self._table_client = self._get_table_client(model)
333
381
  safe_pk = self._sanitize_pk_rk(pk)
334
382
  safe_rk = self._sanitize_pk_rk(rk)
335
383
 
@@ -404,7 +452,7 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
404
452
 
405
453
  elif isinstance(v, datetime):
406
454
  reference_entity[k] = v
407
-
455
+ self._table_client = self._get_table_client(model)
408
456
  self._table_client.upsert_entity(reference_entity)
409
457
 
410
458
  logger.info(f"Stored in blob: {blob_key} ({payload_size // 1024} KB)")
@@ -437,12 +485,9 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
437
485
  @retry(max_attempts=3, delay=1.0, exceptions=(NoSQLError,))
438
486
  def _get_raw(self, model: type, pk: str, rk: str) -> Optional[JsonDict]:
439
487
  try:
440
- if not self._table_client:
441
- return None
442
-
443
488
  safe_pk = self._sanitize_pk_rk(pk)
444
489
  safe_rk = self._sanitize_pk_rk(rk)
445
-
490
+ self._table_client = self._get_table_client(model)
446
491
  entity = self._table_client.get_entity(safe_pk, safe_rk)
447
492
  entity_dict = dict(entity)
448
493
 
@@ -487,9 +532,7 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
487
532
  self, model: type, filters: Dict[str, Any], limit: Optional[int]
488
533
  ) -> List[JsonDict]:
489
534
  try:
490
- if not self._table_client:
491
- return []
492
-
535
+ self._table_client = self._get_table_client(model)
493
536
  # always enforce model filter
494
537
  eff_filters = dict(filters or {})
495
538
  eff_filters[_MODEL_FIELD] = model.__qualname__
@@ -576,9 +619,7 @@ class AzureTableStorageAdapter(NoSQLKVAdapter):
576
619
  @retry(max_attempts=3, delay=1.0, exceptions=(NoSQLError,))
577
620
  def _delete_raw(self, model: type, pk: str, rk: str, etag: Optional[str]) -> JsonDict:
578
621
  try:
579
- if not self._table_client:
580
- return {"deleted": False}
581
-
622
+ self._table_client = self._get_table_client(model)
582
623
  safe_pk = self._sanitize_pk_rk(pk)
583
624
  safe_rk = self._sanitize_pk_rk(rk)
584
625
 
@@ -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", "id")
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:
@@ -257,6 +257,7 @@ class NoSQLKVAdapter:
257
257
  if isinstance(entity_id, dict):
258
258
  pk = entity_id.get("partition_key") or entity_id.get("pk")
259
259
  rk = entity_id.get("row_key") or entity_id.get("rk") or entity_id.get("id")
260
+
260
261
  else:
261
262
  pk, rk = self._get_pk_rk(model, {"id": entity_id})
262
263
 
@@ -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
 
@@ -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: Any
85
- nosql: Any
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) -> Any:
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) -> Any:
295
+ def _nosql(self) -> NoSQLKVAdapter:
292
296
  for e in self._engines:
293
297
  if e.is_default_nosql:
294
298
  return e.nosql()