altcodepro-polydb-python 2.3.16__tar.gz → 2.3.17__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 (83) hide show
  1. {altcodepro_polydb_python-2.3.16/src/altcodepro_polydb_python.egg-info → altcodepro_polydb_python-2.3.17}/PKG-INFO +1 -1
  2. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/pyproject.toml +1 -1
  3. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17/src/altcodepro_polydb_python.egg-info}/PKG-INFO +1 -1
  4. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/databaseFactory.py +51 -11
  5. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/retry.py +23 -0
  6. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/LICENSE +0 -0
  7. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/MANIFEST.in +0 -0
  8. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/README.md +0 -0
  9. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/requirements-aws.txt +0 -0
  10. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/requirements-azure.txt +0 -0
  11. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/requirements-dev.txt +0 -0
  12. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/requirements-gcp.txt +0 -0
  13. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/requirements-generic.txt +0 -0
  14. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/requirements.txt +0 -0
  15. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/setup.cfg +0 -0
  16. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/setup.py +0 -0
  17. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/altcodepro_polydb_python.egg-info/SOURCES.txt +0 -0
  18. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/altcodepro_polydb_python.egg-info/dependency_links.txt +0 -0
  19. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/altcodepro_polydb_python.egg-info/requires.txt +0 -0
  20. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/altcodepro_polydb_python.egg-info/top_level.txt +0 -0
  21. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/PolyDB.py +0 -0
  22. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/__init__.py +0 -0
  23. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/adapters/AzureBlobStorageAdapter.py +0 -0
  24. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/adapters/AzureFileStorageAdapter.py +0 -0
  25. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/adapters/AzureQueueAdapter.py +0 -0
  26. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/adapters/AzureTableStorageAdapter.py +0 -0
  27. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/adapters/BlockchainBlobAdapter.py +0 -0
  28. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/adapters/BlockchainFileAdapter.py +0 -0
  29. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/adapters/BlockchainKVAdapter.py +0 -0
  30. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/adapters/BlockchainQueueAdapter.py +0 -0
  31. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/adapters/DynamoDBAdapter.py +0 -0
  32. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/adapters/EFSAdapter.py +0 -0
  33. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/adapters/FirestoreAdapter.py +0 -0
  34. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/adapters/GCPFilestoreAdapter.py +0 -0
  35. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/adapters/GCPPubSubAdapter.py +0 -0
  36. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/adapters/GCPStorageAdapter.py +0 -0
  37. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/adapters/MongoDBAdapter.py +0 -0
  38. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/adapters/PostgreSQLAdapter.py +0 -0
  39. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/adapters/S3Adapter.py +0 -0
  40. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/adapters/S3CompatibleAdapter.py +0 -0
  41. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/adapters/SQSAdapter.py +0 -0
  42. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/adapters/VercelBlobAdapter.py +0 -0
  43. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/adapters/VercelFileAdapter.py +0 -0
  44. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/adapters/VercelKVAdapter.py +0 -0
  45. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/adapters/VercelQueueAdapter.py +0 -0
  46. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/adapters/__init__.py +0 -0
  47. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/advanced_query.py +0 -0
  48. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/audit/AuditStorage.py +0 -0
  49. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/audit/__init__.py +0 -0
  50. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/audit/context.py +0 -0
  51. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/audit/manager.py +0 -0
  52. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/audit/models.py +0 -0
  53. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/base/NoSQLKVAdapter.py +0 -0
  54. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/base/ObjectStorageAdapter.py +0 -0
  55. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/base/QueueAdapter.py +0 -0
  56. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/base/SharedFilesAdapter.py +0 -0
  57. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/base/__init__.py +0 -0
  58. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/batch.py +0 -0
  59. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/cache.py +0 -0
  60. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/cloudDatabaseFactory.py +0 -0
  61. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/decorators.py +0 -0
  62. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/errors.py +0 -0
  63. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/json_safe.py +0 -0
  64. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/models.py +0 -0
  65. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/monitoring.py +0 -0
  66. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/multitenancy.py +0 -0
  67. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/py.typed +0 -0
  68. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/query.py +0 -0
  69. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/registry.py +0 -0
  70. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/schema.py +0 -0
  71. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/security.py +0 -0
  72. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/types.py +0 -0
  73. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/utils.py +0 -0
  74. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/src/polydb/validation.py +0 -0
  75. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/tests/test_aws.py +0 -0
  76. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/tests/test_azure.py +0 -0
  77. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/tests/test_blockchain.py +0 -0
  78. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/tests/test_cloud_factory.py +0 -0
  79. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/tests/test_gcp.py +0 -0
  80. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/tests/test_mongodb.py +0 -0
  81. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/tests/test_multi_engine.py +0 -0
  82. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/tests/test_postgresql.py +0 -0
  83. {altcodepro_polydb_python-2.3.16 → altcodepro_polydb_python-2.3.17}/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.16
3
+ Version: 2.3.17
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.16"
7
+ version = "2.3.17"
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.16
3
+ Version: 2.3.17
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
@@ -34,7 +34,7 @@ from .audit.manager import AuditManager
34
34
  from .audit.context import AuditContext
35
35
  from .query import Operator, QueryBuilder
36
36
  from .cloudDatabaseFactory import CloudDatabaseFactory
37
-
37
+ import re as _re
38
38
  logger = logging.getLogger(__name__)
39
39
 
40
40
  _DEFAULT_RETRY = retry(
@@ -43,7 +43,32 @@ _DEFAULT_RETRY = retry(
43
43
  reraise=True,
44
44
  )
45
45
 
46
+ _UNIQUE_VIOLATION_MARKERS = (
47
+ "23505", # Postgres SQLSTATE
48
+ "duplicate key value violates", # Postgres message
49
+ "unique constraint", # Postgres, generic
50
+ "UniqueViolation", # psycopg / SQLAlchemy class name
51
+ "Duplicate entry", # MySQL
52
+ "UNIQUE constraint failed", # SQLite
53
+ )
54
+ _UNIQUE_KEY_RE = _re.compile(r"Key \(([^)]+)\)=")
55
+
56
+
57
+ def _is_unique_violation(exc: BaseException) -> bool:
58
+ s = str(exc)
59
+ return any(m in s for m in _UNIQUE_VIOLATION_MARKERS)
60
+
46
61
 
62
+ def _parse_unique_violation_columns(exc: BaseException) -> list[str]:
63
+ """
64
+ Pull the conflicting column names out of a Postgres unique-violation error.
65
+ Postgres formats them as: Key (col1, col2)=(val1, val2) already exists.
66
+ Returns [] if the message doesn't carry that detail.
67
+ """
68
+ m = _UNIQUE_KEY_RE.search(str(exc))
69
+ if not m:
70
+ return []
71
+ return [c.strip() for c in m.group(1).split(",") if c.strip()]
47
72
  # ═══════════════════════════════════════════════════════════════════════════════
48
73
  # ENGINE CONFIG
49
74
  # ═══════════════════════════════════════════════════════════════════════════════
@@ -359,9 +384,7 @@ class DatabaseFactory:
359
384
  logger.error("Audit recording failed: %s", exc)
360
385
 
361
386
  def _run(self, fn: Callable[[], Any]) -> Any:
362
- if not self._enable_retries:
363
- return fn()
364
- return _DEFAULT_RETRY(fn)()
387
+ return fn()
365
388
 
366
389
  def _is_sql(self, meta: ModelMeta, override: Optional[EngineOverride] = None) -> bool:
367
390
  if override and override.force_sql:
@@ -398,14 +421,32 @@ class DatabaseFactory:
398
421
  def _op() -> JsonDict:
399
422
  nonlocal after_plain, success, entity_id
400
423
  if self._is_sql(meta, engine_override):
401
- result = adapters.sql.insert(meta.table, data)
424
+ try:
425
+ result = adapters.sql.insert(meta.table, data)
426
+ except Exception as exc:
427
+ if not _is_unique_violation(exc):
428
+ raise
429
+ # Half-ran scenario / re-activation / replay. The record already
430
+ # exists with these unique-key columns. Preserve idempotent
431
+ # "create or update" semantics by routing to UPDATE keyed on the
432
+ # exact columns that conflicted (parsed from the Postgres error).
433
+ conflict_cols = _parse_unique_violation_columns(exc)
434
+ if not conflict_cols or not all(c in data for c in conflict_cols):
435
+ # Can't determine the conflict — re-raise so the caller sees it.
436
+ raise
437
+ where = {c: data[c] for c in conflict_cols}
438
+ logger.warning(
439
+ "insert %s hit unique violation on %s — falling through to update",
440
+ meta.table, conflict_cols,
441
+ )
442
+ # Drop the conflict columns from the UPDATE SET clause — they're
443
+ # already the matching key.
444
+ update_data = {k: v for k, v in data.items() if k not in conflict_cols}
445
+ result = adapters.sql.update(meta.table, where, update_data)
402
446
  else:
403
447
  result = adapters.nosql.put(
404
- (
405
- model
406
- if isinstance(model, type)
407
- else type(name, (), {"__polydb__": meta.__dict__})
408
- ),
448
+ model if isinstance(model, type)
449
+ else type(name, (), {"__polydb__": meta.__dict__}),
409
450
  data,
410
451
  )
411
452
  entity_id = result.get("id")
@@ -416,7 +457,6 @@ class DatabaseFactory:
416
457
  if self._enable_cache and self._cache:
417
458
  self._cache.invalidate(name)
418
459
  return after_plain
419
-
420
460
  try:
421
461
  monitor = (
422
462
  PerformanceMonitor(self.metrics, "create", name, None) if self.metrics else None
@@ -3,10 +3,33 @@
3
3
  Retry logic with exponential backoff and metrics hooks
4
4
  """
5
5
 
6
+ import functools
6
7
  import time
7
8
  import logging
8
9
  from functools import wraps
9
10
  from typing import Callable, Optional, Tuple, Type
11
+ logger = logging.getLogger(__name__)
12
+
13
+ _NON_RETRYABLE_MARKERS = (
14
+ "23505", # Postgres unique_violation
15
+ "23503", # Postgres foreign_key_violation
16
+ "23502", # Postgres not_null_violation
17
+ "23514", # Postgres check_violation
18
+ "duplicate key value violates",
19
+ "unique constraint",
20
+ "UniqueViolation",
21
+ "Duplicate entry",
22
+ "UNIQUE constraint failed",
23
+ "PropertyValueTooLarge",
24
+ "ResourceNotFound",
25
+ "InvalidArgument",
26
+ "AuthenticationFailed",
27
+ )
28
+
29
+
30
+ def _is_non_retryable(exc: BaseException) -> bool:
31
+ s = str(exc)
32
+ return any(m in s for m in _NON_RETRYABLE_MARKERS)
10
33
 
11
34
 
12
35
  # Metrics hooks for enterprise monitoring