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
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import mimetypes
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
from ..base.ObjectStorageAdapter import ObjectStorageAdapter
|
|
9
|
+
from ..errors import StorageError
|
|
10
|
+
from ..retry import retry
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class VercelBlobAdapter(ObjectStorageAdapter):
|
|
14
|
+
"""
|
|
15
|
+
Vercel Blob Storage adapter.
|
|
16
|
+
|
|
17
|
+
If BLOB_READ_WRITE_TOKEN is missing, falls back to
|
|
18
|
+
local filesystem storage for testing.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, token: str = "", timeout: int = 10):
|
|
22
|
+
super().__init__()
|
|
23
|
+
|
|
24
|
+
self.token = token or os.getenv("BLOB_READ_WRITE_TOKEN")
|
|
25
|
+
self.timeout = timeout or int(os.getenv("VERCEL_BLOB_TIMEOUT", "10"))
|
|
26
|
+
|
|
27
|
+
# Local fallback storage for tests
|
|
28
|
+
self.local_dir = Path(os.getenv("VERCEL_BLOB_LOCAL_DIR", "/tmp/vercel_blob"))
|
|
29
|
+
self.local_dir.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
|
|
31
|
+
self.local_mode = not bool(self.token)
|
|
32
|
+
|
|
33
|
+
@retry(max_attempts=3, delay=1.0, exceptions=(StorageError,))
|
|
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:
|
|
42
|
+
"""Upload object to Vercel Blob or local fallback and return URL/path"""
|
|
43
|
+
try:
|
|
44
|
+
# --------------------------------------------------
|
|
45
|
+
# Resolve filename
|
|
46
|
+
# --------------------------------------------------
|
|
47
|
+
filename = fileName or os.path.basename(key)
|
|
48
|
+
|
|
49
|
+
# --------------------------------------------------
|
|
50
|
+
# Ensure extension from media_type
|
|
51
|
+
# --------------------------------------------------
|
|
52
|
+
if media_type:
|
|
53
|
+
ext = mimetypes.guess_extension(media_type) or ""
|
|
54
|
+
if ext and not filename.lower().endswith(ext):
|
|
55
|
+
filename += ext
|
|
56
|
+
|
|
57
|
+
# --------------------------------------------------
|
|
58
|
+
# Final blob key
|
|
59
|
+
# --------------------------------------------------
|
|
60
|
+
blob_key = f"{key.rstrip('/')}/{filename}" if fileName else key
|
|
61
|
+
|
|
62
|
+
# --------------------------------------------------
|
|
63
|
+
# Metadata (string-safe)
|
|
64
|
+
# --------------------------------------------------
|
|
65
|
+
safe_metadata = {str(k): str(v) for k, v in (metadata or {}).items()}
|
|
66
|
+
safe_metadata["filename"] = filename
|
|
67
|
+
if media_type:
|
|
68
|
+
safe_metadata["contentType"] = media_type
|
|
69
|
+
|
|
70
|
+
# --------------------------------------------------
|
|
71
|
+
# LOCAL MODE
|
|
72
|
+
# --------------------------------------------------
|
|
73
|
+
if self.local_mode:
|
|
74
|
+
path = self.local_dir / blob_key
|
|
75
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
76
|
+
path.write_bytes(data)
|
|
77
|
+
|
|
78
|
+
meta_path = path.with_suffix(path.suffix + ".metadata.json")
|
|
79
|
+
try:
|
|
80
|
+
import json
|
|
81
|
+
|
|
82
|
+
meta_path.write_text(json.dumps(safe_metadata, ensure_ascii=False, indent=2))
|
|
83
|
+
except Exception:
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
self.logger.debug(f"Stored local Vercel blob: {blob_key}")
|
|
87
|
+
return str(path)
|
|
88
|
+
|
|
89
|
+
# --------------------------------------------------
|
|
90
|
+
# REAL VERCEL
|
|
91
|
+
# --------------------------------------------------
|
|
92
|
+
headers = {
|
|
93
|
+
"Authorization": f"Bearer {self.token}",
|
|
94
|
+
"x-content-type": media_type or "application/octet-stream",
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for k, v in safe_metadata.items():
|
|
98
|
+
headers[f"x-metadata-{k}"] = v
|
|
99
|
+
|
|
100
|
+
response = requests.put(
|
|
101
|
+
f"https://blob.vercel-storage.com/{blob_key}",
|
|
102
|
+
headers=headers,
|
|
103
|
+
data=data,
|
|
104
|
+
timeout=self.timeout,
|
|
105
|
+
)
|
|
106
|
+
response.raise_for_status()
|
|
107
|
+
|
|
108
|
+
result = response.json()
|
|
109
|
+
|
|
110
|
+
self.logger.debug(f"Uploaded Vercel blob: {blob_key}, type={media_type}")
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
result.get("url") or result.get("downloadUrl") or result.get("pathname") or blob_key
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
except Exception as e:
|
|
117
|
+
raise StorageError(f"Vercel Blob put failed: {e}")
|
|
118
|
+
|
|
119
|
+
@retry(max_attempts=3, delay=1.0, exceptions=(StorageError,))
|
|
120
|
+
def get(self, key: str) -> bytes | None:
|
|
121
|
+
try:
|
|
122
|
+
if self.local_mode:
|
|
123
|
+
path = self.local_dir / key
|
|
124
|
+
if not path.exists():
|
|
125
|
+
return None
|
|
126
|
+
return path.read_bytes()
|
|
127
|
+
|
|
128
|
+
response = requests.get(
|
|
129
|
+
f"https://blob.vercel-storage.com/{key}",
|
|
130
|
+
headers={"Authorization": f"Bearer {self.token}"},
|
|
131
|
+
timeout=self.timeout,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
if response.status_code == 404:
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
response.raise_for_status()
|
|
138
|
+
return response.content
|
|
139
|
+
|
|
140
|
+
except StorageError:
|
|
141
|
+
raise
|
|
142
|
+
except Exception as e:
|
|
143
|
+
raise StorageError(f"Vercel Blob get failed: {e}")
|
|
144
|
+
|
|
145
|
+
@retry(max_attempts=3, delay=1.0, exceptions=(StorageError,))
|
|
146
|
+
def delete(self, key: str) -> bool:
|
|
147
|
+
try:
|
|
148
|
+
if self.local_mode:
|
|
149
|
+
path = self.local_dir / key
|
|
150
|
+
if path.exists():
|
|
151
|
+
path.unlink()
|
|
152
|
+
meta_path = path.with_suffix(path.suffix + ".metadata.json")
|
|
153
|
+
if meta_path.exists():
|
|
154
|
+
meta_path.unlink()
|
|
155
|
+
return True
|
|
156
|
+
|
|
157
|
+
response = requests.delete(
|
|
158
|
+
f"https://blob.vercel-storage.com/{key}",
|
|
159
|
+
headers={"Authorization": f"Bearer {self.token}"},
|
|
160
|
+
timeout=self.timeout,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
if response.status_code == 404:
|
|
164
|
+
return False
|
|
165
|
+
|
|
166
|
+
response.raise_for_status()
|
|
167
|
+
return True
|
|
168
|
+
|
|
169
|
+
except Exception as e:
|
|
170
|
+
raise StorageError(f"Vercel Blob delete failed: {e}")
|
|
171
|
+
|
|
172
|
+
@retry(max_attempts=3, delay=1.0, exceptions=(StorageError,))
|
|
173
|
+
def list(self, prefix: str = "") -> List[str]:
|
|
174
|
+
try:
|
|
175
|
+
if self.local_mode:
|
|
176
|
+
results: List[str] = []
|
|
177
|
+
for p in self.local_dir.rglob("*"):
|
|
178
|
+
if p.is_file() and not p.name.endswith(".metadata.json"):
|
|
179
|
+
rel = str(p.relative_to(self.local_dir))
|
|
180
|
+
if rel.startswith(prefix):
|
|
181
|
+
results.append(rel)
|
|
182
|
+
return results
|
|
183
|
+
|
|
184
|
+
response = requests.get(
|
|
185
|
+
f"https://blob.vercel-storage.com/?prefix={prefix}",
|
|
186
|
+
headers={"Authorization": f"Bearer {self.token}"},
|
|
187
|
+
timeout=self.timeout,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
response.raise_for_status()
|
|
191
|
+
|
|
192
|
+
blobs = response.json().get("blobs", [])
|
|
193
|
+
return [b.get("pathname") for b in blobs if b.get("pathname")]
|
|
194
|
+
|
|
195
|
+
except Exception as e:
|
|
196
|
+
raise StorageError(f"Vercel Blob list failed: {e}")
|