altcodepro-polydb-python 2.2.2__py3-none-any.whl → 2.2.4__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.
- altcodepro_polydb_python-2.2.4.dist-info/METADATA +489 -0
- altcodepro_polydb_python-2.2.4.dist-info/RECORD +57 -0
- {altcodepro_polydb_python-2.2.2.dist-info → altcodepro_polydb_python-2.2.4.dist-info}/WHEEL +1 -1
- polydb/__init__.py +2 -2
- polydb/adapters/AzureBlobStorageAdapter.py +146 -41
- polydb/adapters/AzureFileStorageAdapter.py +148 -43
- polydb/adapters/AzureQueueAdapter.py +96 -34
- polydb/adapters/AzureTableStorageAdapter.py +462 -119
- polydb/adapters/BlockchainBlobAdapter.py +111 -0
- polydb/adapters/BlockchainKVAdapter.py +152 -0
- polydb/adapters/BlockchainQueueAdapter.py +116 -0
- polydb/adapters/DynamoDBAdapter.py +463 -176
- polydb/adapters/FirestoreAdapter.py +320 -148
- polydb/adapters/GCPPubSubAdapter.py +217 -0
- polydb/adapters/GCPStorageAdapter.py +184 -39
- polydb/adapters/MongoDBAdapter.py +159 -39
- polydb/adapters/PostgreSQLAdapter.py +285 -83
- polydb/adapters/S3Adapter.py +172 -35
- polydb/adapters/S3CompatibleAdapter.py +62 -8
- polydb/adapters/SQSAdapter.py +121 -44
- polydb/adapters/VercelBlobAdapter.py +196 -0
- polydb/adapters/VercelKVAdapter.py +275 -283
- polydb/adapters/VercelQueueAdapter.py +61 -0
- polydb/audit/AuditStorage.py +1 -1
- polydb/base/NoSQLKVAdapter.py +113 -101
- polydb/base/ObjectStorageAdapter.py +42 -6
- polydb/base/QueueAdapter.py +2 -2
- polydb/base/SharedFilesAdapter.py +2 -2
- polydb/cloudDatabaseFactory.py +200 -0
- polydb/databaseFactory.py +434 -101
- polydb/models.py +63 -1
- polydb/query.py +111 -42
- altcodepro_polydb_python-2.2.2.dist-info/METADATA +0 -379
- altcodepro_polydb_python-2.2.2.dist-info/RECORD +0 -52
- polydb/adapters/PubSubAdapter.py +0 -85
- polydb/factory.py +0 -107
- {altcodepro_polydb_python-2.2.2.dist-info → altcodepro_polydb_python-2.2.4.dist-info}/licenses/LICENSE +0 -0
- {altcodepro_polydb_python-2.2.2.dist-info → altcodepro_polydb_python-2.2.4.dist-info}/top_level.txt +0 -0
|
@@ -1,328 +1,320 @@
|
|
|
1
1
|
# src/polydb/adapters/VercelKVAdapter.py
|
|
2
|
+
|
|
2
3
|
import os
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
from
|
|
4
|
+
import json
|
|
5
|
+
import redis
|
|
6
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
7
|
+
|
|
6
8
|
from ..json_safe import json_safe
|
|
7
|
-
from ..errors import NoSQLError,
|
|
9
|
+
from ..errors import NoSQLError, DatabaseError
|
|
8
10
|
from ..retry import retry
|
|
9
11
|
from ..types import JsonDict
|
|
10
12
|
from ..models import PartitionConfig
|
|
13
|
+
from ..base.NoSQLKVAdapter import NoSQLKVAdapter
|
|
11
14
|
|
|
12
15
|
|
|
13
16
|
class VercelKVAdapter(NoSQLKVAdapter):
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
"""
|
|
18
|
+
Vercel KV adapter.
|
|
19
|
+
|
|
20
|
+
Supports:
|
|
21
|
+
• Local Redis (used in tests)
|
|
22
|
+
• Vercel KV REST API (production)
|
|
23
|
+
|
|
24
|
+
Tests run against redis://localhost:6380
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
partition_config: Optional[PartitionConfig] = None,
|
|
30
|
+
kv_url: str = "",
|
|
31
|
+
kv_token: str = "",
|
|
32
|
+
timeout: int = 10,
|
|
33
|
+
):
|
|
19
34
|
super().__init__(partition_config)
|
|
20
|
-
|
|
21
|
-
self.kv_url = os.getenv(
|
|
22
|
-
self.kv_token = os.getenv(
|
|
23
|
-
self.
|
|
24
|
-
|
|
25
|
-
self.
|
|
26
|
-
|
|
35
|
+
|
|
36
|
+
self.kv_url = kv_url or os.getenv("KV_REST_API_URL", "")
|
|
37
|
+
self.kv_token = kv_token or os.getenv("KV_REST_API_TOKEN", "")
|
|
38
|
+
self.timeout = timeout
|
|
39
|
+
|
|
40
|
+
self._redis: Optional[redis.Redis] = None
|
|
41
|
+
|
|
42
|
+
# detect local redis
|
|
43
|
+
if self.kv_url.startswith("redis://"):
|
|
44
|
+
self._redis = redis.from_url(self.kv_url, decode_responses=True)
|
|
45
|
+
|
|
46
|
+
# ------------------------------------------------------------------
|
|
47
|
+
# PUT
|
|
48
|
+
# ------------------------------------------------------------------
|
|
49
|
+
|
|
27
50
|
@retry(max_attempts=3, delay=1.0, exceptions=(NoSQLError,))
|
|
28
51
|
def _put_raw(self, model: type, pk: str, rk: str, data: JsonDict) -> JsonDict:
|
|
29
52
|
try:
|
|
30
|
-
|
|
31
|
-
import json
|
|
32
|
-
import hashlib
|
|
33
|
-
|
|
53
|
+
|
|
34
54
|
key = f"{pk}:{rk}"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
# Store reference in KV
|
|
61
|
-
reference_data = {
|
|
62
|
-
'_pk': pk,
|
|
63
|
-
'_rk': rk,
|
|
64
|
-
'_overflow': True,
|
|
65
|
-
'_blob_key': blob_key,
|
|
66
|
-
'_size': data_size,
|
|
67
|
-
'_checksum': blob_id,
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
response = requests.post(
|
|
71
|
-
f"{self.kv_url}/set/{key}",
|
|
72
|
-
headers={'Authorization': f'Bearer {self.kv_token}'},
|
|
73
|
-
json={'value': json.dumps(reference_data,default=json_safe)},
|
|
74
|
-
timeout=self.timeout
|
|
75
|
-
)
|
|
76
|
-
response.raise_for_status()
|
|
77
|
-
else:
|
|
78
|
-
# Store directly in KV
|
|
79
|
-
response = requests.post(
|
|
80
|
-
f"{self.kv_url}/set/{key}",
|
|
81
|
-
headers={'Authorization': f'Bearer {self.kv_token}'},
|
|
82
|
-
json={'value': json.dumps(data_copy,default=json_safe)},
|
|
83
|
-
timeout=self.timeout
|
|
84
|
-
)
|
|
85
|
-
response.raise_for_status()
|
|
86
|
-
|
|
87
|
-
return {'key': key, '_pk': pk, '_rk': rk}
|
|
55
|
+
|
|
56
|
+
payload = dict(data)
|
|
57
|
+
payload["_pk"] = pk
|
|
58
|
+
payload["_rk"] = rk
|
|
59
|
+
payload["id"] = pk
|
|
60
|
+
|
|
61
|
+
value = json.dumps(payload, default=json_safe)
|
|
62
|
+
|
|
63
|
+
# LOCAL REDIS
|
|
64
|
+
if self._redis:
|
|
65
|
+
self._redis.set(key, value)
|
|
66
|
+
return payload
|
|
67
|
+
|
|
68
|
+
# REST API (vercel production)
|
|
69
|
+
import requests
|
|
70
|
+
|
|
71
|
+
requests.post(
|
|
72
|
+
f"{self.kv_url}/set/{key}",
|
|
73
|
+
headers={"Authorization": f"Bearer {self.kv_token}"},
|
|
74
|
+
json={"value": value},
|
|
75
|
+
timeout=self.timeout,
|
|
76
|
+
).raise_for_status()
|
|
77
|
+
|
|
78
|
+
return payload
|
|
79
|
+
|
|
88
80
|
except Exception as e:
|
|
89
|
-
raise NoSQLError(f"Vercel KV put failed: {
|
|
90
|
-
|
|
81
|
+
raise NoSQLError(f"Vercel KV put failed: {e}")
|
|
82
|
+
|
|
83
|
+
# ------------------------------------------------------------------
|
|
84
|
+
# GET
|
|
85
|
+
# ------------------------------------------------------------------
|
|
86
|
+
|
|
91
87
|
@retry(max_attempts=3, delay=1.0, exceptions=(NoSQLError,))
|
|
92
88
|
def _get_raw(self, model: type, pk: str, rk: str) -> Optional[JsonDict]:
|
|
89
|
+
|
|
93
90
|
try:
|
|
94
|
-
|
|
95
|
-
import json
|
|
96
|
-
import hashlib
|
|
97
|
-
|
|
91
|
+
|
|
98
92
|
key = f"{pk}:{rk}"
|
|
99
|
-
|
|
100
|
-
|
|
93
|
+
|
|
94
|
+
# LOCAL REDIS
|
|
95
|
+
if self._redis:
|
|
96
|
+
|
|
97
|
+
value: Any = self._redis.get(key)
|
|
98
|
+
|
|
99
|
+
if not value:
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
obj = json.loads(value)
|
|
103
|
+
obj.setdefault("id", obj.get("_pk"))
|
|
104
|
+
return obj
|
|
105
|
+
|
|
106
|
+
# REST API
|
|
107
|
+
import requests
|
|
108
|
+
|
|
109
|
+
resp = requests.get(
|
|
101
110
|
f"{self.kv_url}/get/{key}",
|
|
102
|
-
headers={
|
|
103
|
-
timeout=self.timeout
|
|
111
|
+
headers={"Authorization": f"Bearer {self.kv_token}"},
|
|
112
|
+
timeout=self.timeout,
|
|
104
113
|
)
|
|
105
|
-
|
|
106
|
-
if
|
|
114
|
+
|
|
115
|
+
if resp.status_code != 200:
|
|
107
116
|
return None
|
|
108
|
-
|
|
109
|
-
result =
|
|
117
|
+
|
|
118
|
+
result = resp.json().get("result")
|
|
119
|
+
|
|
110
120
|
if not result:
|
|
111
121
|
return None
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
checksum = kv_data.get('_checksum')
|
|
119
|
-
|
|
120
|
-
if blob_key:
|
|
121
|
-
blob_response = requests.get(
|
|
122
|
-
f"https://blob.vercel-storage.com/{blob_key}",
|
|
123
|
-
headers={"Authorization": f"Bearer {self.blob_token}"},
|
|
124
|
-
timeout=self.timeout,
|
|
125
|
-
)
|
|
126
|
-
blob_response.raise_for_status()
|
|
127
|
-
blob_data = blob_response.content
|
|
128
|
-
|
|
129
|
-
# Verify checksum
|
|
130
|
-
actual_checksum = hashlib.md5(blob_data).hexdigest()
|
|
131
|
-
if actual_checksum != checksum:
|
|
132
|
-
raise NoSQLError(f"Checksum mismatch: expected {checksum}, got {actual_checksum}")
|
|
133
|
-
|
|
134
|
-
retrieved = json.loads(blob_data.decode())
|
|
135
|
-
self.logger.debug(f"Retrieved overflow from Blob: {blob_key}")
|
|
136
|
-
return retrieved
|
|
137
|
-
|
|
138
|
-
return kv_data
|
|
122
|
+
|
|
123
|
+
obj = json.loads(result)
|
|
124
|
+
obj.setdefault("id", obj.get("_pk"))
|
|
125
|
+
|
|
126
|
+
return obj
|
|
127
|
+
|
|
139
128
|
except Exception as e:
|
|
140
|
-
raise NoSQLError(f"Vercel KV get failed: {
|
|
141
|
-
|
|
129
|
+
raise NoSQLError(f"Vercel KV get failed: {e}")
|
|
130
|
+
|
|
131
|
+
# ------------------------------------------------------------------
|
|
132
|
+
# QUERY
|
|
133
|
+
# ------------------------------------------------------------------
|
|
134
|
+
|
|
142
135
|
@retry(max_attempts=3, delay=1.0, exceptions=(NoSQLError,))
|
|
143
|
-
def _query_raw(
|
|
136
|
+
def _query_raw(
|
|
137
|
+
self,
|
|
138
|
+
model: type,
|
|
139
|
+
filters: Dict[str, Any],
|
|
140
|
+
limit: Optional[int],
|
|
141
|
+
) -> List[JsonDict]:
|
|
142
|
+
|
|
144
143
|
try:
|
|
144
|
+
|
|
145
|
+
results: List[JsonDict] = []
|
|
146
|
+
|
|
147
|
+
# LOCAL REDIS
|
|
148
|
+
if self._redis:
|
|
149
|
+
|
|
150
|
+
for key in self._redis.scan_iter("*"):
|
|
151
|
+
|
|
152
|
+
value: Any = self._redis.get(key)
|
|
153
|
+
|
|
154
|
+
if not value:
|
|
155
|
+
continue
|
|
156
|
+
|
|
157
|
+
obj = json.loads(value)
|
|
158
|
+
|
|
159
|
+
match = True
|
|
160
|
+
|
|
161
|
+
for k, v in filters.items():
|
|
162
|
+
|
|
163
|
+
if k == "id":
|
|
164
|
+
if obj.get("_pk") != v:
|
|
165
|
+
match = False
|
|
166
|
+
break
|
|
167
|
+
|
|
168
|
+
elif obj.get(k) != v:
|
|
169
|
+
match = False
|
|
170
|
+
break
|
|
171
|
+
|
|
172
|
+
if match:
|
|
173
|
+
obj.setdefault("id", obj.get("_pk"))
|
|
174
|
+
results.append(obj)
|
|
175
|
+
|
|
176
|
+
if limit and len(results) >= limit:
|
|
177
|
+
break
|
|
178
|
+
|
|
179
|
+
return results
|
|
180
|
+
|
|
181
|
+
# REST API fallback
|
|
145
182
|
import requests
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
pattern = f"{filters['_pk']}:*"
|
|
152
|
-
|
|
153
|
-
response = requests.get(
|
|
154
|
-
f"{self.kv_url}/keys/{pattern}",
|
|
155
|
-
headers={'Authorization': f'Bearer {self.kv_token}'},
|
|
156
|
-
timeout=self.timeout
|
|
183
|
+
|
|
184
|
+
resp = requests.get(
|
|
185
|
+
f"{self.kv_url}/keys/*",
|
|
186
|
+
headers={"Authorization": f"Bearer {self.kv_token}"},
|
|
187
|
+
timeout=self.timeout,
|
|
157
188
|
)
|
|
158
|
-
|
|
159
|
-
if
|
|
189
|
+
|
|
190
|
+
if resp.status_code != 200:
|
|
160
191
|
return []
|
|
161
|
-
|
|
162
|
-
keys =
|
|
163
|
-
|
|
164
|
-
# Fetch all matching keys
|
|
165
|
-
results = []
|
|
192
|
+
|
|
193
|
+
keys = resp.json().get("result", [])
|
|
194
|
+
|
|
166
195
|
for key in keys:
|
|
196
|
+
|
|
167
197
|
if limit and len(results) >= limit:
|
|
168
198
|
break
|
|
169
|
-
|
|
170
|
-
|
|
199
|
+
|
|
200
|
+
get_resp = requests.get(
|
|
171
201
|
f"{self.kv_url}/get/{key}",
|
|
172
|
-
headers={
|
|
173
|
-
timeout=self.timeout
|
|
202
|
+
headers={"Authorization": f"Bearer {self.kv_token}"},
|
|
203
|
+
timeout=self.timeout,
|
|
174
204
|
)
|
|
175
|
-
|
|
176
|
-
if
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
205
|
+
|
|
206
|
+
if get_resp.status_code != 200:
|
|
207
|
+
continue
|
|
208
|
+
|
|
209
|
+
result = get_resp.json().get("result")
|
|
210
|
+
|
|
211
|
+
if not result:
|
|
212
|
+
continue
|
|
213
|
+
|
|
214
|
+
obj = json.loads(result)
|
|
215
|
+
|
|
216
|
+
match = True
|
|
217
|
+
|
|
218
|
+
for k, v in filters.items():
|
|
219
|
+
|
|
220
|
+
if k == "id":
|
|
221
|
+
if obj.get("_pk") != v:
|
|
222
|
+
match = False
|
|
223
|
+
break
|
|
224
|
+
|
|
225
|
+
elif obj.get(k) != v:
|
|
226
|
+
match = False
|
|
227
|
+
break
|
|
228
|
+
|
|
229
|
+
if match:
|
|
230
|
+
obj.setdefault("id", obj.get("_pk"))
|
|
231
|
+
results.append(obj)
|
|
232
|
+
|
|
193
233
|
return results
|
|
234
|
+
|
|
194
235
|
except Exception as e:
|
|
195
|
-
raise NoSQLError(f"Vercel KV query failed: {
|
|
196
|
-
|
|
236
|
+
raise NoSQLError(f"Vercel KV query failed: {e}")
|
|
237
|
+
|
|
238
|
+
# ------------------------------------------------------------------
|
|
239
|
+
# DELETE
|
|
240
|
+
# ------------------------------------------------------------------
|
|
241
|
+
|
|
197
242
|
@retry(max_attempts=3, delay=1.0, exceptions=(NoSQLError,))
|
|
198
|
-
def _delete_raw(
|
|
243
|
+
def _delete_raw(
|
|
244
|
+
self,
|
|
245
|
+
model: type,
|
|
246
|
+
pk: str,
|
|
247
|
+
rk: str,
|
|
248
|
+
etag: Optional[str],
|
|
249
|
+
) -> JsonDict:
|
|
250
|
+
|
|
199
251
|
try:
|
|
200
|
-
|
|
201
|
-
import json
|
|
202
|
-
|
|
252
|
+
|
|
203
253
|
key = f"{pk}:{rk}"
|
|
204
|
-
|
|
205
|
-
#
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
)
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
kv_data = json.loads(result)
|
|
217
|
-
|
|
218
|
-
if kv_data.get('_overflow'):
|
|
219
|
-
blob_key = kv_data.get('_blob_key')
|
|
220
|
-
if blob_key:
|
|
221
|
-
blob_response = requests.delete(
|
|
222
|
-
f"https://blob.vercel-storage.com/{blob_key}",
|
|
223
|
-
headers={"Authorization": f"Bearer {self.blob_token}"},
|
|
224
|
-
timeout=self.timeout,
|
|
225
|
-
)
|
|
226
|
-
blob_response.raise_for_status()
|
|
227
|
-
self.logger.debug(f"Deleted overflow Blob: {blob_key}")
|
|
228
|
-
except:
|
|
229
|
-
pass # Key might not exist or no overflow
|
|
230
|
-
|
|
231
|
-
# Delete KV key
|
|
232
|
-
response = requests.delete(
|
|
233
|
-
f"{self.kv_url}/del/{key}",
|
|
234
|
-
headers={'Authorization': f'Bearer {self.kv_token}'},
|
|
235
|
-
timeout=self.timeout
|
|
236
|
-
)
|
|
237
|
-
response.raise_for_status()
|
|
238
|
-
|
|
239
|
-
return {'deleted': True, 'key': key}
|
|
240
|
-
except Exception as e:
|
|
241
|
-
raise NoSQLError(f"Vercel KV delete failed: {str(e)}")
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
class VercelBlobAdapter:
|
|
245
|
-
"""Vercel Blob Storage"""
|
|
246
|
-
|
|
247
|
-
def __init__(self):
|
|
248
|
-
from ..utils import setup_logger
|
|
249
|
-
self.logger = setup_logger(__name__)
|
|
250
|
-
self.blob_token = os.getenv("BLOB_READ_WRITE_TOKEN")
|
|
251
|
-
self.timeout = int(os.getenv("VERCEL_BLOB_TIMEOUT", "10"))
|
|
252
|
-
|
|
253
|
-
@retry(max_attempts=3, delay=1.0, exceptions=(StorageError,))
|
|
254
|
-
def put(self, key: str, data: bytes) -> str:
|
|
255
|
-
try:
|
|
256
|
-
import requests
|
|
257
|
-
|
|
258
|
-
response = requests.put(
|
|
259
|
-
f"https://blob.vercel-storage.com/{key}",
|
|
260
|
-
headers={
|
|
261
|
-
"Authorization": f"Bearer {self.blob_token}",
|
|
262
|
-
"x-content-type": "application/octet-stream",
|
|
263
|
-
},
|
|
264
|
-
data=data,
|
|
265
|
-
timeout=self.timeout,
|
|
266
|
-
)
|
|
267
|
-
response.raise_for_status()
|
|
268
|
-
return response.json()["url"]
|
|
269
|
-
except Exception as e:
|
|
270
|
-
raise StorageError(f"Vercel Blob put failed: {str(e)}")
|
|
271
|
-
|
|
272
|
-
@retry(max_attempts=3, delay=1.0, exceptions=(StorageError,))
|
|
273
|
-
def get(self, key: str) -> bytes:
|
|
274
|
-
try:
|
|
275
|
-
import requests
|
|
276
|
-
|
|
277
|
-
response = requests.get(
|
|
278
|
-
f"https://blob.vercel-storage.com/{key}",
|
|
279
|
-
headers={"Authorization": f"Bearer {self.blob_token}"},
|
|
280
|
-
timeout=self.timeout,
|
|
281
|
-
)
|
|
282
|
-
response.raise_for_status()
|
|
283
|
-
return response.content
|
|
284
|
-
except Exception as e:
|
|
285
|
-
raise StorageError(f"Vercel Blob get failed: {str(e)}")
|
|
286
|
-
|
|
287
|
-
@retry(max_attempts=3, delay=1.0, exceptions=(StorageError,))
|
|
288
|
-
def delete(self, key: str) -> bool:
|
|
289
|
-
try:
|
|
254
|
+
|
|
255
|
+
# LOCAL REDIS
|
|
256
|
+
if self._redis:
|
|
257
|
+
|
|
258
|
+
if not self._redis.exists(key):
|
|
259
|
+
raise DatabaseError(f"Item {pk}/{rk} does not exist")
|
|
260
|
+
|
|
261
|
+
self._redis.delete(key)
|
|
262
|
+
|
|
263
|
+
return {"id": pk}
|
|
264
|
+
|
|
265
|
+
# REST API
|
|
290
266
|
import requests
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
f"
|
|
294
|
-
headers={"Authorization": f"Bearer {self.
|
|
267
|
+
|
|
268
|
+
resp = requests.get(
|
|
269
|
+
f"{self.kv_url}/get/{key}",
|
|
270
|
+
headers={"Authorization": f"Bearer {self.kv_token}"},
|
|
295
271
|
timeout=self.timeout,
|
|
296
272
|
)
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
try:
|
|
305
|
-
import requests
|
|
306
|
-
|
|
307
|
-
response = requests.get(
|
|
308
|
-
f"https://blob.vercel-storage.com/?prefix={prefix}",
|
|
309
|
-
headers={"Authorization": f"Bearer {self.blob_token}"},
|
|
273
|
+
|
|
274
|
+
if resp.status_code != 200:
|
|
275
|
+
raise DatabaseError(f"Item {pk}/{rk} does not exist")
|
|
276
|
+
|
|
277
|
+
requests.delete(
|
|
278
|
+
f"{self.kv_url}/del/{key}",
|
|
279
|
+
headers={"Authorization": f"Bearer {self.kv_token}"},
|
|
310
280
|
timeout=self.timeout,
|
|
311
|
-
)
|
|
312
|
-
|
|
313
|
-
return
|
|
281
|
+
).raise_for_status()
|
|
282
|
+
|
|
283
|
+
return {"id": pk}
|
|
284
|
+
|
|
285
|
+
except DatabaseError:
|
|
286
|
+
raise
|
|
287
|
+
|
|
314
288
|
except Exception as e:
|
|
315
|
-
raise
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
def
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
289
|
+
raise NoSQLError(f"Vercel KV delete failed: {e}")
|
|
290
|
+
|
|
291
|
+
# ------------------------------------------------------------------
|
|
292
|
+
# PAGINATION
|
|
293
|
+
# ------------------------------------------------------------------
|
|
294
|
+
|
|
295
|
+
def query_page(
|
|
296
|
+
self,
|
|
297
|
+
model: type,
|
|
298
|
+
query: Dict[str, Any],
|
|
299
|
+
page_size: int,
|
|
300
|
+
continuation_token: Optional[str] = None,
|
|
301
|
+
) -> Tuple[List[JsonDict], Optional[str]]:
|
|
302
|
+
|
|
303
|
+
rows = self._query_raw(model, query, None)
|
|
304
|
+
|
|
305
|
+
start = 0
|
|
306
|
+
|
|
307
|
+
if continuation_token:
|
|
308
|
+
for i, r in enumerate(rows):
|
|
309
|
+
if r["id"] == continuation_token:
|
|
310
|
+
start = i + 1
|
|
311
|
+
break
|
|
312
|
+
|
|
313
|
+
page = rows[start : start + page_size]
|
|
314
|
+
|
|
315
|
+
next_token = None
|
|
316
|
+
|
|
317
|
+
if start + page_size < len(rows):
|
|
318
|
+
next_token = page[-1]["id"]
|
|
319
|
+
|
|
320
|
+
return page, next_token
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import requests
|
|
4
|
+
from typing import Dict, Any, List
|
|
5
|
+
from ..errors import QueueError
|
|
6
|
+
from ..retry import retry
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class VercelQueueAdapter:
|
|
10
|
+
|
|
11
|
+
def __init__(self, url: str = "", token: str = ""):
|
|
12
|
+
self.url = url or os.getenv("KV_REST_API_URL")
|
|
13
|
+
self.token = token or os.getenv("KV_REST_API_TOKEN")
|
|
14
|
+
|
|
15
|
+
@retry(max_attempts=3, delay=1.0, exceptions=(QueueError,))
|
|
16
|
+
def send(self, message: Dict[str, Any], queue_name: str = "default") -> str:
|
|
17
|
+
try:
|
|
18
|
+
|
|
19
|
+
payload = json.dumps(message)
|
|
20
|
+
|
|
21
|
+
r = requests.post(
|
|
22
|
+
f"{self.url}/xadd/{queue_name}",
|
|
23
|
+
headers={"Authorization": f"Bearer {self.token}"},
|
|
24
|
+
json={"*": payload},
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
r.raise_for_status()
|
|
28
|
+
|
|
29
|
+
return r.json()["result"]
|
|
30
|
+
|
|
31
|
+
except Exception as e:
|
|
32
|
+
raise QueueError(f"Vercel queue send failed: {e}")
|
|
33
|
+
|
|
34
|
+
@retry(max_attempts=3, delay=1.0, exceptions=(QueueError,))
|
|
35
|
+
def get_queue(self, queue_name: str = "default", max_messages: int = 1) -> List[Dict]:
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
|
|
39
|
+
r = requests.get(
|
|
40
|
+
f"{self.url}/xrange/{queue_name}/-/{max_messages}",
|
|
41
|
+
headers={"Authorization": f"Bearer {self.token}"},
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
r.raise_for_status()
|
|
45
|
+
|
|
46
|
+
result = r.json()["result"]
|
|
47
|
+
|
|
48
|
+
messages = []
|
|
49
|
+
|
|
50
|
+
for msg in result:
|
|
51
|
+
data = json.loads(msg[1][0][1])
|
|
52
|
+
data["_id"] = msg[0]
|
|
53
|
+
messages.append(data)
|
|
54
|
+
|
|
55
|
+
return messages
|
|
56
|
+
|
|
57
|
+
except Exception as e:
|
|
58
|
+
raise QueueError(f"Vercel queue receive failed: {e}")
|
|
59
|
+
|
|
60
|
+
def delete(self, message_id: str, queue_name: str = "default") -> bool:
|
|
61
|
+
return True
|