altcodepro-polydb-python 2.3.28__py3-none-any.whl → 2.3.30__py3-none-any.whl

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: altcodepro-polydb-python
3
- Version: 2.3.28
3
+ Version: 2.3.30
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
@@ -1,4 +1,4 @@
1
- altcodepro_polydb_python-2.3.28.dist-info/licenses/LICENSE,sha256=9X8GLocsBwy-5aR5JGOt2SAMDDPs9Qv-YnqmHBHOXrw,1067
1
+ altcodepro_polydb_python-2.3.30.dist-info/licenses/LICENSE,sha256=9X8GLocsBwy-5aR5JGOt2SAMDDPs9Qv-YnqmHBHOXrw,1067
2
2
  polydb/PolyDB.py,sha256=MG7-nV59zDvrUQwXNEgV4eetHCeW9TYY9DQqtxu_r7k,23543
3
3
  polydb/__init__.py,sha256=d9KSz888A336904TPEjWPYBtyJLPB0lTdcaOnYp8byY,1535
4
4
  polydb/advanced_query.py,sha256=cxMB-EB-qT3bWXJlhmjnMCUtrzogORWyoEfS50Dy7go,4280
@@ -15,7 +15,7 @@ polydb/multitenancy.py,sha256=9kyY98RpKg8xDy9ejB_MyV_YzF7eZd4uxashw5S8vlg,6408
15
15
  polydb/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  polydb/query.py,sha256=3oWgRXIYLItmd_R-7u78hCdUgUaoiFmjVg38vi2-rVA,6600
17
17
  polydb/registry.py,sha256=RD_elvFXcmhTdCyZDm2f3ej0elxqhArnSJ2aO9k5VCU,2352
18
- polydb/retry.py,sha256=OfmNglrmL6AQlJPmXNdy9A9lRkH0P-vZ5BUuMiUM__4,3795
18
+ polydb/retry.py,sha256=QduWwDyiU-dt1fHFQmm6ltbnaJY2460cDfQ7ZE8wjXs,4466
19
19
  polydb/schema.py,sha256=VrOayX6V6AD2Qh3-lm4ZVPTpI24e4V52IYheZf2rNQ4,5812
20
20
  polydb/security.py,sha256=9ju-hc6Y1sxobCoV_mZ3ZWroUD73LodyTLVMhY_HeKU,16360
21
21
  polydb/types.py,sha256=XB_85Un8_aWt4dSfpjIGotHbK3KBY2WurQGXr9EOxWY,2992
@@ -23,7 +23,7 @@ polydb/utils.py,sha256=tS_KI1vt-VZ6JftbFxbnVxC0xpie1-zYikTGf1nSoFw,3183
23
23
  polydb/validation.py,sha256=a1o1d02k3c6PWQwkBbw_0nEmIgrdB5RR8OcpNQMn4cA,4810
24
24
  polydb/adapters/AzureBlobStorageAdapter.py,sha256=4vD55Z8DBTzBK66jIJbo5bNMY-AQ61MlP0-P2Fv_JgQ,7083
25
25
  polydb/adapters/AzureFileStorageAdapter.py,sha256=VZNprqlBXCuWUgtqClNT-NrQmRf-XFYEiRA2BLbf-Sc,7046
26
- polydb/adapters/AzureQueueAdapter.py,sha256=-MLEEAt2yvYjlB5LqT2TwoRa3rVDHDVzXhm1gykqeDM,8052
26
+ polydb/adapters/AzureQueueAdapter.py,sha256=LsNvrNHUgFF2UwXXTwlOiqK8VdP4htLaFnTu4oxZDlo,9468
27
27
  polydb/adapters/AzureTableStorageAdapter.py,sha256=FCWojIRyOQdDyga5sxbHXBfZMdb3T1xn3Q2VX5klebo,27824
28
28
  polydb/adapters/BlockchainBlobAdapter.py,sha256=D01Yua9mkKfaQrxKYApblIyI6DSP0dtNAh4Tav51HJ4,3299
29
29
  polydb/adapters/BlockchainFileAdapter.py,sha256=G749xOVpG20HuKS8zCgi6PMjoJNu-YXK7zitygjLdzM,8335
@@ -36,7 +36,7 @@ polydb/adapters/GCPFilestoreAdapter.py,sha256=yjFQQwsWYWc8mo8XwMViVTWb5_D--xAyTM
36
36
  polydb/adapters/GCPPubSubAdapter.py,sha256=7XNots2VA0ReEDku-rjg-OTYmftIpx5UgnXYDdXNkOo,8692
37
37
  polydb/adapters/GCPStorageAdapter.py,sha256=9yS1Jhcn5_rCRdZ5uOqcRW6Ba-UNb6VOYpwENP-C6Qk,7133
38
38
  polydb/adapters/MongoDBAdapter.py,sha256=Ak8q_qeAaUTe0Yc2oEefmAbzoODjTWVZQOirW-seBQU,7574
39
- polydb/adapters/PostgreSQLAdapter.py,sha256=lUe65kpJkTu6R1Tc1zgl63Fa1PI44UVbWRTKRUSSGak,36579
39
+ polydb/adapters/PostgreSQLAdapter.py,sha256=SOmaNYhbFnw_8MiQAJrM6SSQhEHc0dgrxIHjGpxqK9E,37853
40
40
  polydb/adapters/S3Adapter.py,sha256=5R0zHAL2SkGFjp1L3bp-IU468bXYdSf6nKx974MN104,7586
41
41
  polydb/adapters/S3CompatibleAdapter.py,sha256=jpafqbAjA8-irdXBrfXa1QJySIzrcUQ6UrFt5h5FAEc,7006
42
42
  polydb/adapters/SQSAdapter.py,sha256=1vfbNoqIDy-b8t2xcxy91SoxSYBPFDfUh7yCQWxdS84,5778
@@ -60,7 +60,7 @@ polydb/observability/logging.py,sha256=gv1SshNjaSkmR98Uv7Qbwq3gkeQUzVCA4ichqHKL6
60
60
  polydb/services/__init__.py,sha256=nMolMXzmhejRSm7f42kU9fCHF0GIpqpejN2kywuBsUQ,241
61
61
  polydb/services/compliance_service.py,sha256=-0ncX7PrXy4-NuyWXIv65cLUsqz0jXzb3LtsD8rlrXE,4983
62
62
  polydb/services/security_service.py,sha256=q7GzK6sbMnBBsc4fuL4oXTd-ui2b-MYQyrWTBvdVNSk,5062
63
- altcodepro_polydb_python-2.3.28.dist-info/METADATA,sha256=mCw1bRtSYL37sOtvm-Rn5fQdjfCwLZPcZkAiOk-PN68,12359
64
- altcodepro_polydb_python-2.3.28.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
65
- altcodepro_polydb_python-2.3.28.dist-info/top_level.txt,sha256=WgLFWJoYjUhwvyPxJFl6jYLrVFuBJDX3OABf4ocwk_E,7
66
- altcodepro_polydb_python-2.3.28.dist-info/RECORD,,
63
+ altcodepro_polydb_python-2.3.30.dist-info/METADATA,sha256=p1Tk2tEZiHimDJPShjvq38kJvXyCG44z3k7EdCrK9HU,12359
64
+ altcodepro_polydb_python-2.3.30.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
65
+ altcodepro_polydb_python-2.3.30.dist-info/top_level.txt,sha256=WgLFWJoYjUhwvyPxJFl6jYLrVFuBJDX3OABf4ocwk_E,7
66
+ altcodepro_polydb_python-2.3.30.dist-info/RECORD,,
@@ -110,15 +110,38 @@ class AzureQueueAdapter(QueueAdapter):
110
110
  except Exception as e:
111
111
  raise QueueError(f"Azure Queue send failed: {e}")
112
112
 
113
+ # How long a delivered message stays invisible to other consumers before
114
+ # Azure hands it out again. The SDK's own default is 30s — far shorter
115
+ # than a real task (an LLM generation call alone can run 30-120s+), so
116
+ # a still-in-flight message was becoming visible again and getting
117
+ # picked up by a second worker mid-processing: two concurrent
118
+ # executions of the same task, one of which stomps/duplicates the
119
+ # other's result. 300s covers realistic worst-case processing; ack()
120
+ # deletes the message immediately on completion regardless; a genuinely
121
+ # crashed worker still recovers the message after this timeout.
122
+ DEFAULT_VISIBILITY_TIMEOUT = 300
123
+
113
124
  @retry(max_attempts=3, delay=1.0, exceptions=(QueueError,))
114
- def receive(self, queue_name: str = "default", max_messages: int = 1) -> List[Dict[str, Any]]:
125
+ def receive(
126
+ self,
127
+ queue_name: str = "default",
128
+ max_messages: int = 1,
129
+ visibility_timeout: Optional[int] = None,
130
+ ) -> List[Dict[str, Any]]:
115
131
  """Receive messages"""
116
132
  try:
117
133
 
118
134
  queue_name = self._normalize_queue_name(queue_name)
119
135
  queue_client = self._get_queue(queue_name)
120
136
 
121
- messages = queue_client.receive_messages(max_messages=max_messages)
137
+ messages = queue_client.receive_messages(
138
+ max_messages=max_messages,
139
+ visibility_timeout=(
140
+ visibility_timeout
141
+ if visibility_timeout is not None
142
+ else self.DEFAULT_VISIBILITY_TIMEOUT
143
+ ),
144
+ )
122
145
 
123
146
  results = []
124
147
 
@@ -135,6 +158,12 @@ class AzureQueueAdapter(QueueAdapter):
135
158
  # Azure's visibility timeout elapses — replaying the
136
159
  # same task forever even after it already ran.
137
160
  "receipt_handle": self._encode_receipt(msg.id, msg.pop_receipt),
161
+ # How many times Azure has handed this same message
162
+ # out (1 on first delivery). Lets a consumer (e.g.
163
+ # WorkerPool) detect a poison message that keeps
164
+ # failing to ack/process and dead-letter it instead
165
+ # of retrying forever.
166
+ "dequeue_count": msg.dequeue_count,
138
167
  "body": payload,
139
168
  }
140
169
  )
@@ -87,6 +87,26 @@ class PostgreSQLAdapter:
87
87
  query.setdefault("keepalives_idle", ["30"])
88
88
  query.setdefault("keepalives_interval", ["10"])
89
89
  query.setdefault("keepalives_count", ["5"])
90
+ # connect_timeout only bounds the initial handshake; keepalives
91
+ # only detect a dead *idle* connection. Neither bounds a query
92
+ # that's already in flight and stalls mid-response (packet
93
+ # loss / a silently-dropped connection somewhere on the path
94
+ # to a remote DB) -- that shows up as libpq's own "could not
95
+ # receive data from server: Operation timed out / SSL SYSCALL
96
+ # error", and without a bound the client can block on that
97
+ # recv() for a very long time (Python can't interrupt a
98
+ # blocking C-level syscall, so even Ctrl+C doesn't land until
99
+ # it returns). tcp_user_timeout bounds unacknowledged data at
100
+ # the OS/TCP level; statement_timeout bounds how long Postgres
101
+ # itself will run a query before cancelling it server-side.
102
+ query.setdefault(
103
+ "tcp_user_timeout",
104
+ [os.getenv("POSTGRES_TCP_USER_TIMEOUT_MS", "20000")],
105
+ )
106
+ query.setdefault(
107
+ "options",
108
+ [f"-c statement_timeout={os.getenv('POSTGRES_STATEMENT_TIMEOUT_MS', '20000')}"],
109
+ )
90
110
 
91
111
  new_query = urlencode(query, doseq=True)
92
112
  parsed = parsed._replace(query=new_query)
polydb/retry.py CHANGED
@@ -3,6 +3,7 @@
3
3
  Retry logic with exponential backoff and metrics hooks
4
4
  """
5
5
 
6
+ import os
6
7
  import time
7
8
  import logging
8
9
  from functools import wraps
@@ -74,7 +75,15 @@ def retry(
74
75
  @wraps(func)
75
76
  def wrapper(*args, **kwargs):
76
77
  attempt = 0
77
- current_delay = delay
78
+ # POLYDB_RETRY_DELAY_SECONDS / POLYDB_RETRY_BACKOFF override every
79
+ # call site's hardcoded delay/backoff without touching them
80
+ # individually -- every adapter passes delay=1.0 explicitly, so a
81
+ # transient failure against a high-latency remote DB (e.g. a local
82
+ # dev box talking to a managed Postgres in a different region)
83
+ # costs a full 1s+2s of sleep on top of the already-elevated
84
+ # network round trip, per retry. Unset, behavior is unchanged.
85
+ current_delay = float(os.getenv("POLYDB_RETRY_DELAY_SECONDS", delay))
86
+ backoff_rate = float(os.getenv("POLYDB_RETRY_BACKOFF", backoff))
78
87
 
79
88
  logger = logging.getLogger(__name__)
80
89
 
@@ -110,7 +119,7 @@ def retry(
110
119
  f"Retrying in {current_delay}s..."
111
120
  )
112
121
  time.sleep(current_delay)
113
- current_delay *= backoff
122
+ current_delay *= backoff_rate
114
123
  raise RuntimeError(
115
124
  f"{func.__name__} exhausted {max_attempts} attempts without returning"
116
125
  )