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
polydb/base/NoSQLKVAdapter.py
CHANGED
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
import hashlib
|
|
5
5
|
import json
|
|
6
6
|
import threading
|
|
7
|
-
from typing import Any, Dict, List, Optional, Tuple, Union, TYPE_CHECKING
|
|
7
|
+
from typing import Any, Dict, List, Optional, Tuple, Union, TYPE_CHECKING, cast
|
|
8
8
|
|
|
9
9
|
from ..json_safe import json_safe
|
|
10
10
|
|
|
@@ -19,107 +19,117 @@ if TYPE_CHECKING:
|
|
|
19
19
|
|
|
20
20
|
class NoSQLKVAdapter:
|
|
21
21
|
"""Base with auto-overflow and LINQ support"""
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
def __init__(self, partition_config: Optional[PartitionConfig] = None):
|
|
24
24
|
from ..utils import setup_logger
|
|
25
|
+
|
|
25
26
|
self.logger = setup_logger(self.__class__.__name__)
|
|
26
27
|
self.partition_config = partition_config
|
|
27
28
|
self.object_storage = None
|
|
28
29
|
self._lock = threading.Lock()
|
|
29
30
|
self.max_size = 1024 * 1024 # 1MB
|
|
30
|
-
|
|
31
|
+
|
|
31
32
|
def _get_pk_rk(self, model: type, data: JsonDict) -> Tuple[str, str]:
|
|
32
33
|
"""Extract PK/RK from model metadata"""
|
|
33
|
-
meta = getattr(model,
|
|
34
|
-
pk_field = meta.get(
|
|
35
|
-
rk_field = meta.get(
|
|
36
|
-
|
|
34
|
+
meta = getattr(model, "__polydb__", {})
|
|
35
|
+
pk_field = meta.get("pk_field", "id")
|
|
36
|
+
rk_field = meta.get("rk_field")
|
|
37
|
+
|
|
37
38
|
if self.partition_config:
|
|
38
39
|
try:
|
|
39
40
|
pk = self.partition_config.partition_key_template.format(**data)
|
|
40
41
|
except KeyError:
|
|
41
42
|
pk = f"default_{data.get(pk_field, hashlib.md5(json.dumps(data, sort_keys=True,default=json_safe).encode()).hexdigest()[:8])}"
|
|
42
43
|
else:
|
|
43
|
-
pk = str(data.get(pk_field,
|
|
44
|
-
|
|
44
|
+
pk = str(data.get(pk_field, "default"))
|
|
45
|
+
|
|
45
46
|
if rk_field and rk_field in data:
|
|
46
47
|
rk = str(data[rk_field])
|
|
47
48
|
elif self.partition_config and self.partition_config.row_key_template:
|
|
48
49
|
try:
|
|
49
50
|
rk = self.partition_config.row_key_template.format(**data)
|
|
50
51
|
except KeyError:
|
|
51
|
-
rk = hashlib.md5(
|
|
52
|
+
rk = hashlib.md5(
|
|
53
|
+
json.dumps(data, sort_keys=True, default=json_safe).encode()
|
|
54
|
+
).hexdigest()
|
|
52
55
|
else:
|
|
53
|
-
rk = data.get(
|
|
54
|
-
|
|
56
|
+
rk = data.get(
|
|
57
|
+
"id",
|
|
58
|
+
hashlib.md5(
|
|
59
|
+
json.dumps(data, sort_keys=True, default=json_safe).encode()
|
|
60
|
+
).hexdigest(),
|
|
61
|
+
)
|
|
62
|
+
|
|
55
63
|
return str(pk), str(rk)
|
|
56
|
-
|
|
64
|
+
|
|
57
65
|
@retry(max_attempts=3, delay=1.0, exceptions=(NoSQLError,))
|
|
58
66
|
def _check_overflow(self, data: JsonDict) -> Tuple[JsonDict, Optional[str]]:
|
|
59
67
|
"""Check size and store in blob if needed"""
|
|
60
|
-
data_bytes = json.dumps(data,default=json_safe).encode()
|
|
68
|
+
data_bytes = json.dumps(data, default=json_safe).encode()
|
|
61
69
|
data_size = len(data_bytes)
|
|
62
|
-
|
|
70
|
+
|
|
63
71
|
if data_size > self.max_size:
|
|
64
72
|
with self._lock:
|
|
65
73
|
if not self.object_storage:
|
|
66
|
-
from ..
|
|
74
|
+
from ..cloudDatabaseFactory import CloudDatabaseFactory
|
|
75
|
+
|
|
67
76
|
factory = CloudDatabaseFactory()
|
|
68
77
|
self.object_storage = factory.get_object_storage()
|
|
69
|
-
|
|
78
|
+
|
|
70
79
|
blob_id = hashlib.md5(data_bytes).hexdigest()
|
|
71
80
|
blob_key = f"overflow/{blob_id}.json"
|
|
72
|
-
|
|
81
|
+
|
|
73
82
|
try:
|
|
74
83
|
self.object_storage.put(blob_key, data_bytes)
|
|
75
84
|
except Exception as e:
|
|
76
85
|
raise StorageError(f"Overflow storage failed: {str(e)}")
|
|
77
|
-
|
|
86
|
+
|
|
78
87
|
return {
|
|
79
88
|
"_overflow": True,
|
|
80
89
|
"_blob_key": blob_key,
|
|
81
90
|
"_size": data_size,
|
|
82
|
-
"_checksum": blob_id
|
|
91
|
+
"_checksum": blob_id,
|
|
83
92
|
}, blob_key
|
|
84
|
-
|
|
93
|
+
|
|
85
94
|
return data, None
|
|
86
|
-
|
|
95
|
+
|
|
87
96
|
@retry(max_attempts=3, delay=1.0, exceptions=(StorageError,))
|
|
88
97
|
def _retrieve_overflow(self, data: JsonDict) -> JsonDict:
|
|
89
98
|
"""Retrieve from blob if overflow"""
|
|
90
99
|
if not data.get("_overflow"):
|
|
91
100
|
return data
|
|
92
|
-
|
|
101
|
+
|
|
93
102
|
with self._lock:
|
|
94
103
|
if not self.object_storage:
|
|
95
|
-
from ..
|
|
104
|
+
from ..cloudDatabaseFactory import CloudDatabaseFactory
|
|
105
|
+
|
|
96
106
|
factory = CloudDatabaseFactory()
|
|
97
107
|
self.object_storage = factory.get_object_storage()
|
|
98
|
-
|
|
108
|
+
|
|
99
109
|
try:
|
|
100
110
|
blob_data = self.object_storage.get(data["_blob_key"])
|
|
101
111
|
retrieved = json.loads(blob_data.decode())
|
|
102
|
-
|
|
112
|
+
|
|
103
113
|
# Verify checksum
|
|
104
114
|
checksum = hashlib.md5(blob_data).hexdigest()
|
|
105
115
|
if checksum != data.get("_checksum"):
|
|
106
116
|
raise StorageError("Checksum mismatch on overflow retrieval")
|
|
107
|
-
|
|
117
|
+
|
|
108
118
|
return retrieved
|
|
109
119
|
except Exception as e:
|
|
110
120
|
raise StorageError(f"Overflow retrieval failed: {str(e)}")
|
|
111
|
-
|
|
121
|
+
|
|
112
122
|
def _apply_filters(self, results: List[JsonDict], builder: QueryBuilder) -> List[JsonDict]:
|
|
113
123
|
"""Apply filters in-memory for NoSQL"""
|
|
114
124
|
if not builder.filters:
|
|
115
125
|
return results
|
|
116
|
-
|
|
126
|
+
|
|
117
127
|
filtered = []
|
|
118
128
|
for item in results:
|
|
119
129
|
match = True
|
|
120
130
|
for f in builder.filters:
|
|
121
131
|
value = item.get(f.field)
|
|
122
|
-
|
|
132
|
+
|
|
123
133
|
if f.operator == Operator.EQ and value != f.value:
|
|
124
134
|
match = False
|
|
125
135
|
elif f.operator == Operator.NE and value == f.value:
|
|
@@ -138,101 +148,99 @@ class NoSQLKVAdapter:
|
|
|
138
148
|
match = False
|
|
139
149
|
elif f.operator == Operator.CONTAINS and (not value or f.value not in str(value)):
|
|
140
150
|
match = False
|
|
141
|
-
elif f.operator == Operator.STARTS_WITH and (
|
|
151
|
+
elif f.operator == Operator.STARTS_WITH and (
|
|
152
|
+
not value or not str(value).startswith(f.value)
|
|
153
|
+
):
|
|
142
154
|
match = False
|
|
143
|
-
elif f.operator == Operator.ENDS_WITH and (
|
|
155
|
+
elif f.operator == Operator.ENDS_WITH and (
|
|
156
|
+
not value or not str(value).endswith(f.value)
|
|
157
|
+
):
|
|
144
158
|
match = False
|
|
145
|
-
|
|
159
|
+
|
|
146
160
|
if not match:
|
|
147
161
|
break
|
|
148
|
-
|
|
162
|
+
|
|
149
163
|
if match:
|
|
150
164
|
filtered.append(item)
|
|
151
|
-
|
|
165
|
+
|
|
152
166
|
return filtered
|
|
153
|
-
|
|
167
|
+
|
|
154
168
|
def _apply_ordering(self, results: List[JsonDict], builder: QueryBuilder) -> List[JsonDict]:
|
|
155
169
|
"""Apply ordering"""
|
|
156
170
|
if not builder.order_by_fields:
|
|
157
171
|
return results
|
|
158
|
-
|
|
172
|
+
|
|
159
173
|
for field, desc in reversed(builder.order_by_fields):
|
|
160
|
-
results = sorted(
|
|
161
|
-
|
|
162
|
-
key=lambda x: x.get(field, ''),
|
|
163
|
-
reverse=desc
|
|
164
|
-
)
|
|
165
|
-
|
|
174
|
+
results = sorted(results, key=lambda x: x.get(field, ""), reverse=desc)
|
|
175
|
+
|
|
166
176
|
return results
|
|
167
|
-
|
|
177
|
+
|
|
168
178
|
def _apply_pagination(self, results: List[JsonDict], builder: QueryBuilder) -> List[JsonDict]:
|
|
169
179
|
"""Apply skip/take"""
|
|
170
180
|
if builder.skip_count:
|
|
171
|
-
results = results[builder.skip_count:]
|
|
172
|
-
|
|
181
|
+
results = results[builder.skip_count :]
|
|
182
|
+
|
|
173
183
|
if builder.take_count:
|
|
174
|
-
results = results[:builder.take_count]
|
|
175
|
-
|
|
184
|
+
results = results[: builder.take_count]
|
|
185
|
+
|
|
176
186
|
return results
|
|
177
|
-
|
|
187
|
+
|
|
178
188
|
def _apply_projection(self, results: List[JsonDict], builder: QueryBuilder) -> List[JsonDict]:
|
|
179
|
-
|
|
180
|
-
|
|
189
|
+
|
|
190
|
+
fields = builder.selected_fields
|
|
191
|
+
if not fields:
|
|
181
192
|
return results
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
193
|
+
|
|
194
|
+
field_set = set(fields)
|
|
195
|
+
|
|
196
|
+
return [{k: v for k, v in item.items() if k in field_set} for item in results]
|
|
197
|
+
|
|
188
198
|
# Abstract methods to implement
|
|
189
199
|
def _put_raw(self, model: type, pk: str, rk: str, data: JsonDict) -> JsonDict:
|
|
190
200
|
raise NotImplementedError
|
|
191
|
-
|
|
201
|
+
|
|
192
202
|
def _get_raw(self, model: type, pk: str, rk: str) -> Optional[JsonDict]:
|
|
193
203
|
raise NotImplementedError
|
|
194
|
-
|
|
195
|
-
def _query_raw(
|
|
204
|
+
|
|
205
|
+
def _query_raw(
|
|
206
|
+
self, model: type, filters: Dict[str, Any], limit: Optional[int]
|
|
207
|
+
) -> List[JsonDict]:
|
|
196
208
|
raise NotImplementedError
|
|
197
|
-
|
|
209
|
+
|
|
198
210
|
def _delete_raw(self, model: type, pk: str, rk: str, etag: Optional[str]) -> JsonDict:
|
|
199
211
|
raise NotImplementedError
|
|
200
|
-
|
|
212
|
+
|
|
201
213
|
# Protocol implementation
|
|
202
214
|
def put(self, model: type, data: JsonDict) -> JsonDict:
|
|
203
215
|
pk, rk = self._get_pk_rk(model, data)
|
|
204
216
|
store_data, _ = self._check_overflow(data)
|
|
205
217
|
return self._put_raw(model, pk, rk, store_data)
|
|
206
|
-
|
|
218
|
+
|
|
207
219
|
def query(
|
|
208
220
|
self,
|
|
209
221
|
model: type,
|
|
210
222
|
query: Optional[Lookup] = None,
|
|
211
223
|
limit: Optional[int] = None,
|
|
212
224
|
no_cache: bool = False,
|
|
213
|
-
cache_ttl: Optional[int] = None
|
|
225
|
+
cache_ttl: Optional[int] = None,
|
|
214
226
|
) -> List[JsonDict]:
|
|
215
227
|
results = self._query_raw(model, query or {}, limit)
|
|
216
228
|
return [self._retrieve_overflow(r) for r in results]
|
|
217
|
-
|
|
229
|
+
|
|
218
230
|
def query_page(
|
|
219
|
-
self,
|
|
220
|
-
model: type,
|
|
221
|
-
query: Lookup,
|
|
222
|
-
page_size: int,
|
|
223
|
-
continuation_token: Optional[str] = None
|
|
231
|
+
self, model: type, query: Lookup, page_size: int, continuation_token: Optional[str] = None
|
|
224
232
|
) -> Tuple[List[JsonDict], Optional[str]]:
|
|
225
233
|
# Basic implementation - override per provider
|
|
226
234
|
offset = int(continuation_token) if continuation_token else 0
|
|
227
235
|
results = self.query(model, query, limit=page_size + 1)
|
|
228
|
-
|
|
236
|
+
|
|
229
237
|
has_more = len(results) > page_size
|
|
230
238
|
if has_more:
|
|
231
239
|
results = results[:page_size]
|
|
232
|
-
|
|
240
|
+
|
|
233
241
|
next_token = str(offset + page_size) if has_more else None
|
|
234
242
|
return results, next_token
|
|
235
|
-
|
|
243
|
+
|
|
236
244
|
def patch(
|
|
237
245
|
self,
|
|
238
246
|
model: type,
|
|
@@ -240,64 +248,68 @@ class NoSQLKVAdapter:
|
|
|
240
248
|
data: JsonDict,
|
|
241
249
|
*,
|
|
242
250
|
etag: Optional[str] = None,
|
|
243
|
-
replace: bool = False
|
|
251
|
+
replace: bool = False,
|
|
244
252
|
) -> JsonDict:
|
|
245
253
|
if isinstance(entity_id, dict):
|
|
246
|
-
pk = entity_id.get(
|
|
247
|
-
rk = entity_id.get(
|
|
254
|
+
pk = entity_id.get("partition_key") or entity_id.get("pk")
|
|
255
|
+
rk = entity_id.get("row_key") or entity_id.get("rk") or entity_id.get("id")
|
|
248
256
|
else:
|
|
249
|
-
pk, rk = self._get_pk_rk(model, {
|
|
250
|
-
|
|
257
|
+
pk, rk = self._get_pk_rk(model, {"id": entity_id})
|
|
258
|
+
|
|
251
259
|
if not replace:
|
|
252
|
-
existing = self._get_raw(model, pk, rk)
|
|
260
|
+
existing = self._get_raw(model, pk, rk) # type: ignore
|
|
253
261
|
if existing:
|
|
254
262
|
existing = self._retrieve_overflow(existing)
|
|
255
263
|
existing.update(data)
|
|
256
264
|
data = existing
|
|
257
|
-
|
|
265
|
+
|
|
258
266
|
store_data, _ = self._check_overflow(data)
|
|
259
|
-
return self._put_raw(model, pk, rk, store_data)
|
|
260
|
-
|
|
267
|
+
return self._put_raw(model, pk, rk, store_data) # type: ignore
|
|
268
|
+
|
|
261
269
|
def upsert(self, model: type, data: JsonDict, *, replace: bool = False) -> JsonDict:
|
|
262
270
|
return self.put(model, data)
|
|
263
|
-
|
|
271
|
+
|
|
264
272
|
def delete(
|
|
265
|
-
self,
|
|
266
|
-
model: type,
|
|
267
|
-
entity_id: Union[Any, Lookup],
|
|
268
|
-
*,
|
|
269
|
-
etag: Optional[str] = None
|
|
273
|
+
self, model: type, entity_id: Union[Any, Lookup], *, etag: Optional[str] = None
|
|
270
274
|
) -> JsonDict:
|
|
271
275
|
if isinstance(entity_id, dict):
|
|
272
|
-
pk = entity_id.get(
|
|
273
|
-
rk = entity_id.get(
|
|
276
|
+
pk = entity_id.get("partition_key") or entity_id.get("pk")
|
|
277
|
+
rk = entity_id.get("row_key") or entity_id.get("rk") or entity_id.get("id")
|
|
274
278
|
else:
|
|
275
|
-
pk, rk = self._get_pk_rk(model, {
|
|
276
|
-
|
|
277
|
-
return self._delete_raw(model, pk, rk, etag)
|
|
278
|
-
|
|
279
|
+
pk, rk = self._get_pk_rk(model, {"id": entity_id})
|
|
280
|
+
|
|
281
|
+
return self._delete_raw(model, pk, rk, etag) # type: ignore
|
|
282
|
+
|
|
283
|
+
def query_linq_rows(self, model: type, builder: QueryBuilder) -> List[JsonDict]:
|
|
284
|
+
"""
|
|
285
|
+
Typed wrapper for queries that return rows.
|
|
286
|
+
Use this when builder.count_only is False.
|
|
287
|
+
"""
|
|
288
|
+
result = self.query_linq(model, builder)
|
|
289
|
+
return cast(List[JsonDict], result)
|
|
290
|
+
|
|
279
291
|
def query_linq(self, model: type, builder: QueryBuilder) -> Union[List[JsonDict], int]:
|
|
280
292
|
"""LINQ-style query"""
|
|
281
293
|
results = self._query_raw(model, {}, None)
|
|
282
294
|
results = [self._retrieve_overflow(r) for r in results]
|
|
283
|
-
|
|
295
|
+
|
|
284
296
|
results = self._apply_filters(results, builder)
|
|
285
|
-
|
|
297
|
+
|
|
286
298
|
if builder.count_only:
|
|
287
299
|
return len(results)
|
|
288
|
-
|
|
300
|
+
|
|
289
301
|
results = self._apply_ordering(results, builder)
|
|
290
302
|
results = self._apply_pagination(results, builder)
|
|
291
303
|
results = self._apply_projection(results, builder)
|
|
292
|
-
|
|
304
|
+
|
|
293
305
|
if builder.distinct:
|
|
294
306
|
seen = set()
|
|
295
307
|
unique = []
|
|
296
308
|
for r in results:
|
|
297
|
-
key = json.dumps(r, sort_keys=True,default=json_safe)
|
|
309
|
+
key = json.dumps(r, sort_keys=True, default=json_safe)
|
|
298
310
|
if key not in seen:
|
|
299
311
|
seen.add(key)
|
|
300
312
|
unique.append(r)
|
|
301
313
|
results = unique
|
|
302
|
-
|
|
303
|
-
return results
|
|
314
|
+
|
|
315
|
+
return results
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
from
|
|
1
|
+
from ..errors import StorageError
|
|
2
|
+
from ..utils import setup_logger
|
|
2
3
|
from abc import ABC, abstractmethod
|
|
3
|
-
from typing import List, Optional
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
class ObjectStorageAdapter(ABC):
|
|
@@ -10,19 +11,34 @@ class ObjectStorageAdapter(ABC):
|
|
|
10
11
|
self.logger = setup_logger(self.__class__.__name__)
|
|
11
12
|
|
|
12
13
|
def put(
|
|
13
|
-
self,
|
|
14
|
+
self,
|
|
15
|
+
key: str,
|
|
16
|
+
data: bytes,
|
|
17
|
+
fileName: str = "",
|
|
18
|
+
optimize: bool = True,
|
|
19
|
+
media_type: Optional[str] = None,
|
|
20
|
+
metadata: Dict[str, Any] | None = None,
|
|
14
21
|
) -> str:
|
|
15
22
|
"""Store object with optional optimization"""
|
|
16
23
|
if optimize and media_type:
|
|
17
24
|
data = self._optimize_media(data, media_type)
|
|
18
|
-
return self._put_raw(
|
|
25
|
+
return self._put_raw(
|
|
26
|
+
key=key, data=data, fileName=fileName, media_type=media_type, metadata=metadata
|
|
27
|
+
)
|
|
19
28
|
|
|
20
29
|
def _optimize_media(self, data: bytes, media_type: str) -> bytes:
|
|
21
30
|
"""Optimize images and videos - placeholder for implementation"""
|
|
22
31
|
return data
|
|
23
32
|
|
|
24
33
|
@abstractmethod
|
|
25
|
-
def _put_raw(
|
|
34
|
+
def _put_raw(
|
|
35
|
+
self,
|
|
36
|
+
key: str,
|
|
37
|
+
data: bytes,
|
|
38
|
+
fileName: str = "",
|
|
39
|
+
media_type: Optional[str] = None,
|
|
40
|
+
metadata: Dict[str, Any] | None = None,
|
|
41
|
+
) -> str:
|
|
26
42
|
"""Provider-specific put"""
|
|
27
43
|
pass
|
|
28
44
|
|
|
@@ -39,4 +55,24 @@ class ObjectStorageAdapter(ABC):
|
|
|
39
55
|
@abstractmethod
|
|
40
56
|
def list(self, prefix: str = "") -> List[str]:
|
|
41
57
|
"""List objects with prefix"""
|
|
42
|
-
pass
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
def upload(self, key: str, data: bytes, **kwargs) -> str:
|
|
61
|
+
"""
|
|
62
|
+
Alias for put().
|
|
63
|
+
Accepts kwargs so callers can pass optimize/media_type without breaking.
|
|
64
|
+
"""
|
|
65
|
+
return self.put(key, data, **kwargs)
|
|
66
|
+
|
|
67
|
+
def download(self, key: str) -> bytes:
|
|
68
|
+
"""
|
|
69
|
+
Alias for get() but guarantees bytes or raises.
|
|
70
|
+
This matches how your tests expect download() to behave.
|
|
71
|
+
"""
|
|
72
|
+
if not key:
|
|
73
|
+
raise StorageError("Key cannot be empty")
|
|
74
|
+
|
|
75
|
+
data = self.get(key)
|
|
76
|
+
if data is None:
|
|
77
|
+
raise StorageError(f"Object not found: {key}")
|
|
78
|
+
return data
|
polydb/base/QueueAdapter.py
CHANGED
|
@@ -22,6 +22,6 @@ class QueueAdapter(ABC):
|
|
|
22
22
|
pass
|
|
23
23
|
|
|
24
24
|
@abstractmethod
|
|
25
|
-
def delete(self, message_id: str, queue_name: str = "default") -> bool:
|
|
25
|
+
def delete(self, message_id: str, queue_name: str = "default", pop_receipt: str = "") -> bool:
|
|
26
26
|
"""Delete message from queue"""
|
|
27
|
-
pass
|
|
27
|
+
pass
|
|
@@ -17,7 +17,7 @@ class SharedFilesAdapter(ABC):
|
|
|
17
17
|
pass
|
|
18
18
|
|
|
19
19
|
@abstractmethod
|
|
20
|
-
def read(self, path: str) -> bytes:
|
|
20
|
+
def read(self, path: str) -> bytes | None:
|
|
21
21
|
"""Read file"""
|
|
22
22
|
pass
|
|
23
23
|
|
|
@@ -29,4 +29,4 @@ class SharedFilesAdapter(ABC):
|
|
|
29
29
|
@abstractmethod
|
|
30
30
|
def list(self, directory: str = "/") -> List[str]:
|
|
31
31
|
"""List files in directory"""
|
|
32
|
-
pass
|
|
32
|
+
pass
|