altcodepro-polydb-python 2.3.9__py3-none-any.whl → 2.3.10__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.
Files changed (29) hide show
  1. {altcodepro_polydb_python-2.3.9.dist-info → altcodepro_polydb_python-2.3.10.dist-info}/METADATA +11 -1
  2. altcodepro_polydb_python-2.3.10.dist-info/RECORD +61 -0
  3. polydb/adapters/AzureBlobStorageAdapter.py +92 -90
  4. polydb/adapters/AzureFileStorageAdapter.py +74 -74
  5. polydb/adapters/AzureQueueAdapter.py +9 -5
  6. polydb/adapters/AzureTableStorageAdapter.py +5 -5
  7. polydb/adapters/BlockchainBlobAdapter.py +1 -1
  8. polydb/adapters/BlockchainFileAdapter.py +217 -0
  9. polydb/adapters/BlockchainKVAdapter.py +4 -3
  10. polydb/adapters/BlockchainQueueAdapter.py +3 -2
  11. polydb/adapters/DynamoDBAdapter.py +12 -3
  12. polydb/adapters/EFSAdapter.py +45 -19
  13. polydb/adapters/FirestoreAdapter.py +15 -13
  14. polydb/adapters/GCPFilestoreAdapter.py +77 -0
  15. polydb/adapters/GCPPubSubAdapter.py +4 -4
  16. polydb/adapters/GCPStorageAdapter.py +78 -117
  17. polydb/adapters/PostgreSQLAdapter.py +2 -1
  18. polydb/adapters/S3Adapter.py +5 -2
  19. polydb/adapters/S3CompatibleAdapter.py +87 -57
  20. polydb/adapters/SQSAdapter.py +5 -2
  21. polydb/adapters/VercelFileAdapter.py +29 -0
  22. polydb/audit/__init__.py +1 -1
  23. polydb/base/SharedFilesAdapter.py +5 -5
  24. polydb/cloudDatabaseFactory.py +37 -66
  25. polydb/databaseFactory.py +23 -7
  26. altcodepro_polydb_python-2.3.9.dist-info/RECORD +0 -58
  27. {altcodepro_polydb_python-2.3.9.dist-info → altcodepro_polydb_python-2.3.10.dist-info}/WHEEL +0 -0
  28. {altcodepro_polydb_python-2.3.9.dist-info → altcodepro_polydb_python-2.3.10.dist-info}/licenses/LICENSE +0 -0
  29. {altcodepro_polydb_python-2.3.9.dist-info → altcodepro_polydb_python-2.3.10.dist-info}/top_level.txt +0 -0
@@ -5,9 +5,6 @@ import os
5
5
  import threading
6
6
  from typing import Any, Dict, List, Optional
7
7
 
8
- from google.cloud import storage
9
- from google.api_core.exceptions import NotFound
10
-
11
8
  from ..base.ObjectStorageAdapter import ObjectStorageAdapter
12
9
  from ..errors import StorageError, ConnectionError
13
10
  from ..retry import retry
@@ -17,13 +14,11 @@ class GCPStorageAdapter(ObjectStorageAdapter):
17
14
  """
18
15
  Production-grade Google Cloud Storage adapter.
19
16
 
20
- Features
21
- --------
22
- - Thread-safe client initialization
23
- - Automatic bucket creation
24
- - Emulator support (fake-gcs-server)
25
- - Retry support
26
- - Structured logging
17
+ - One client, many buckets (resolved + cached per call)
18
+ - Automatic bucket creation, emulator support (fake-gcs-server)
19
+ - put/get/delete are symmetric: a blob is stored at `key` and fetched at `key`
20
+ - per-call `container_name` overrides the bucket (generic name kept for
21
+ cross-provider parity)
27
22
  """
28
23
 
29
24
  def __init__(self, project_id: str, endpoint: Optional[str], bucket_name: Optional[str] = None):
@@ -31,22 +26,21 @@ class GCPStorageAdapter(ObjectStorageAdapter):
31
26
 
32
27
  self.bucket_name: str = bucket_name or os.getenv("GCS_BUCKET_NAME", "default")
33
28
  self.project_id: str = project_id or os.getenv("GOOGLE_CLOUD_PROJECT", "polydb-test")
34
-
35
29
  self._endpoint: Optional[str] = endpoint or os.getenv("GCS_ENDPOINT")
36
30
 
37
- self._client: Optional[storage.Client] = None
38
- self._bucket: Optional[storage.Bucket] = None
39
-
31
+ self._client = None
32
+ self._buckets: Dict[str, Any] = {}
40
33
  self._lock = threading.Lock()
41
34
 
42
35
  self._initialize_client()
43
36
 
44
37
  # ------------------------------------------------------------------
45
- # Client initialization
38
+ # Client / bucket resolution
46
39
  # ------------------------------------------------------------------
47
-
48
40
  def _initialize_client(self) -> None:
49
- """Initialize GCS client once (thread-safe)"""
41
+ """Initialize the shared GCS client once (thread-safe)."""
42
+ from google.cloud import storage # lazy: only required for this provider
43
+
50
44
  try:
51
45
  with self._lock:
52
46
  if self._client:
@@ -61,27 +55,61 @@ class GCPStorageAdapter(ObjectStorageAdapter):
61
55
  else:
62
56
  self._client = storage.Client(project=self.project_id)
63
57
 
64
- self._bucket = self._client.bucket(self.bucket_name)
58
+ self.logger.info(f"GCS client initialized (project={self.project_id})")
59
+ except Exception as e:
60
+ raise ConnectionError(f"Failed to initialize GCS: {str(e)}")
65
61
 
66
- # Ensure bucket exists
67
- try:
68
- if not self._bucket.exists():
69
- self._bucket = self._client.create_bucket(self.bucket_name)
70
- self.logger.info(f"Created GCS bucket: {self.bucket_name}")
71
- except Exception:
72
- # fake-gcs-server does not support bucket.exists()
73
- pass
62
+ def _get_bucket(self, container_name: Optional[str] = None):
63
+ """Resolve (and cache) a bucket, auto-creating it."""
64
+ if self._client is None:
65
+ raise ConnectionError("GCS client not initialized")
74
66
 
75
- self.logger.info(
76
- f"GCS initialized (bucket={self.bucket_name}, project={self.project_id})"
77
- )
67
+ name = container_name or self.bucket_name
78
68
 
79
- except Exception as e:
80
- raise ConnectionError(f"Failed to initialize GCS: {str(e)}")
69
+ cached = self._buckets.get(name)
70
+ if cached is not None:
71
+ return cached
72
+
73
+ with self._lock:
74
+ cached = self._buckets.get(name) # re-check under lock
75
+ if cached is not None:
76
+ return cached
77
+
78
+ bucket = self._client.bucket(name)
79
+ try:
80
+ if not bucket.exists():
81
+ bucket = self._client.create_bucket(name)
82
+ self.logger.info(f"Created GCS bucket: {name}")
83
+ except Exception:
84
+ # fake-gcs-server does not support bucket.exists()
85
+ pass
86
+
87
+ self._buckets[name] = bucket
88
+ return bucket
81
89
 
82
90
  # ------------------------------------------------------------------
83
- # Put object
91
+ # Put
84
92
  # ------------------------------------------------------------------
93
+ def put(
94
+ self,
95
+ key: str,
96
+ data: bytes,
97
+ fileName: str = "",
98
+ optimize: bool = True,
99
+ media_type: Optional[str] = None,
100
+ metadata: Dict[str, Any] | None = None,
101
+ container_name: Optional[str] = None,
102
+ ) -> str:
103
+ if optimize and media_type:
104
+ data = self._optimize_media(data, media_type)
105
+ return self._put_raw(
106
+ key=key,
107
+ data=data,
108
+ fileName=fileName,
109
+ media_type=media_type,
110
+ metadata=metadata,
111
+ container_name=container_name,
112
+ )
85
113
 
86
114
  @retry(max_attempts=3, delay=1.0, exceptions=(StorageError,))
87
115
  def _put_raw(
@@ -91,135 +119,68 @@ class GCPStorageAdapter(ObjectStorageAdapter):
91
119
  fileName: str = "",
92
120
  media_type: Optional[str] = None,
93
121
  metadata: Dict[str, Any] | None = None,
122
+ container_name: Optional[str] = None,
94
123
  ) -> str:
95
- """Upload object to GCS with media type, metadata, filename handling"""
124
+ """Upload object. Stored at `key` so get/delete find it by the same key."""
96
125
  try:
97
- if not self._bucket:
98
- raise ConnectionError("GCS bucket not initialized")
126
+ bucket = self._get_bucket(container_name)
127
+ name = container_name or self.bucket_name
99
128
 
100
- # --------------------------------------------------
101
- # Resolve filename
102
- # --------------------------------------------------
103
- filename = fileName or os.path.basename(key)
104
-
105
- # --------------------------------------------------
106
- # Ensure extension from media_type
107
- # --------------------------------------------------
129
+ # filename is metadata only — it must NOT alter the blob key
130
+ filename = fileName or os.path.basename(key) or key
108
131
  if media_type:
109
132
  ext = mimetypes.guess_extension(media_type) or ""
110
133
  if ext and not filename.lower().endswith(ext):
111
134
  filename += ext
112
135
 
113
- # --------------------------------------------------
114
- # Final blob key
115
- # --------------------------------------------------
116
- blob_key = f"{key.rstrip('/')}/{filename}" if fileName else key
117
-
118
- blob = self._bucket.blob(blob_key)
136
+ blob = bucket.blob(key)
119
137
 
120
- # --------------------------------------------------
121
- # Metadata (must be string)
122
- # --------------------------------------------------
123
138
  safe_metadata = {k: str(v) for k, v in (metadata or {}).items()}
124
139
  safe_metadata["filename"] = filename
125
-
126
140
  blob.metadata = safe_metadata
127
141
 
128
- # --------------------------------------------------
129
- # Upload with content type
130
- # --------------------------------------------------
131
- blob.upload_from_string(
132
- data,
133
- content_type=media_type or "application/octet-stream",
134
- )
142
+ blob.upload_from_string(data, content_type=media_type or "application/octet-stream")
143
+ blob.patch() # persist metadata
135
144
 
136
- # Persist metadata (required in GCS)
137
- blob.patch()
138
-
139
- self.logger.debug(f"GCS uploaded blob: {blob_key}, type={media_type}")
140
-
141
- # --------------------------------------------------
142
- # Return public URL
143
- # --------------------------------------------------
144
- return f"https://storage.googleapis.com/{self.bucket_name}/{blob_key}"
145
+ self.logger.debug(f"GCS uploaded blob: {name}/{key}, type={media_type}")
146
+ return f"https://storage.googleapis.com/{name}/{key}"
145
147
 
146
148
  except Exception as e:
147
149
  raise StorageError(f"GCS put failed: {str(e)}")
148
150
 
149
151
  # ------------------------------------------------------------------
150
- # Get object
152
+ # Get / Delete / List
151
153
  # ------------------------------------------------------------------
152
-
153
154
  @retry(max_attempts=3, delay=1.0, exceptions=(StorageError,))
154
- def get(self, key: str) -> Optional[bytes]:
155
- """Download object from GCS"""
155
+ def get(self, key: str, container_name: Optional[str] = None) -> Optional[bytes]:
156
156
  try:
157
- if not self._bucket:
158
- raise ConnectionError("GCS bucket not initialized")
159
-
160
- blob = self._bucket.blob(key)
161
-
157
+ blob = self._get_bucket(container_name).blob(key)
162
158
  if not blob.exists():
163
159
  return None
164
-
165
160
  data = blob.download_as_bytes()
166
-
167
161
  self.logger.debug(f"GCS downloaded blob: {key}")
168
-
169
162
  return data
170
-
171
- except NotFound:
172
- return None
173
-
174
163
  except Exception as e:
175
164
  raise StorageError(f"GCS get failed: {str(e)}")
176
165
 
177
- # ------------------------------------------------------------------
178
- # Delete object
179
- # ------------------------------------------------------------------
180
-
181
166
  @retry(max_attempts=3, delay=1.0, exceptions=(StorageError,))
182
- def delete(self, key: str) -> bool:
183
- """Delete object from GCS"""
167
+ def delete(self, key: str, container_name: Optional[str] = None) -> bool:
184
168
  try:
185
- if not self._bucket:
186
- raise ConnectionError("GCS bucket not initialized")
187
-
188
- blob = self._bucket.blob(key)
189
-
169
+ blob = self._get_bucket(container_name).blob(key)
190
170
  if not blob.exists():
191
171
  return False
192
-
193
172
  blob.delete()
194
-
195
173
  self.logger.debug(f"GCS deleted blob: {key}")
196
-
197
174
  return True
198
-
199
- except NotFound:
200
- return False
201
-
202
175
  except Exception as e:
203
176
  raise StorageError(f"GCS delete failed: {str(e)}")
204
177
 
205
- # ------------------------------------------------------------------
206
- # List objects
207
- # ------------------------------------------------------------------
208
-
209
178
  @retry(max_attempts=3, delay=1.0, exceptions=(StorageError,))
210
- def list(self, prefix: str = "") -> List[str]:
211
- """List objects with optional prefix"""
179
+ def list(self, prefix: str = "", container_name: Optional[str] = None) -> List[str]:
212
180
  try:
213
- if not self._bucket:
214
- raise ConnectionError("GCS bucket not initialized")
215
-
216
- blobs = self._bucket.list_blobs(prefix=prefix)
217
-
181
+ blobs = self._get_bucket(container_name).list_blobs(prefix=prefix)
218
182
  results = [blob.name for blob in blobs]
219
-
220
183
  self.logger.debug(f"GCS listed {len(results)} blobs (prefix={prefix})")
221
-
222
184
  return results
223
-
224
185
  except Exception as e:
225
186
  raise StorageError(f"GCS list failed: {str(e)}")
@@ -8,7 +8,7 @@ import hashlib
8
8
  from contextlib import contextmanager
9
9
  import json
10
10
  from datetime import datetime, date
11
- from psycopg2.extras import Json
11
+
12
12
 
13
13
  from ..errors import DatabaseError, ConnectionError
14
14
  from ..retry import retry
@@ -111,6 +111,7 @@ class PostgreSQLAdapter:
111
111
  - Decimal -> convert to float
112
112
  - everything else -> pass as-is
113
113
  """
114
+ from psycopg2.extras import Json
114
115
 
115
116
  if v is None:
116
117
  return None
@@ -11,8 +11,6 @@ import os
11
11
  import threading
12
12
  from typing import Any, Dict, List, Optional
13
13
 
14
- import boto3
15
- from botocore.exceptions import ClientError
16
14
 
17
15
  from ..base.ObjectStorageAdapter import ObjectStorageAdapter
18
16
  from ..errors import StorageError, ConnectionError
@@ -44,6 +42,8 @@ class S3Adapter(ObjectStorageAdapter):
44
42
 
45
43
  def _initialize_client(self):
46
44
  """Initialize S3 client once"""
45
+ import boto3
46
+
47
47
  try:
48
48
  with self._lock:
49
49
  if self._client:
@@ -72,6 +72,9 @@ class S3Adapter(ObjectStorageAdapter):
72
72
 
73
73
  def _ensure_bucket_exists(self):
74
74
  """Create bucket if it doesn't exist (safe for AWS + LocalStack)"""
75
+ import boto3
76
+ from botocore.exceptions import ClientError
77
+
75
78
  if not self._client:
76
79
  return
77
80
 
@@ -3,28 +3,35 @@ import mimetypes
3
3
  import os
4
4
  import threading
5
5
  from typing import Any, Dict, List, Optional
6
+
6
7
  from ..base.ObjectStorageAdapter import ObjectStorageAdapter
7
8
  from ..errors import StorageError, ConnectionError
8
9
  from ..retry import retry
9
10
 
10
11
 
11
12
  class S3CompatibleAdapter(ObjectStorageAdapter):
12
- """S3-compatible storage (MinIO, DigitalOcean Spaces) with client reuse"""
13
+ """S3-compatible storage (AWS S3, MinIO, DigitalOcean Spaces) with client reuse.
14
+
15
+ - put/get/delete are symmetric: a blob is stored at `key` and fetched at `key`
16
+ - per-call `container_name` overrides the bucket (generic name kept for
17
+ cross-provider parity with the Azure adapter)
18
+ - get() returns None when the object does not exist
19
+ """
13
20
 
14
- def __init__(self):
21
+ def __init__(self, bucket_name: str = ""):
15
22
  super().__init__()
16
23
  self.endpoint = os.getenv("S3_ENDPOINT_URL")
17
24
  self.access_key = os.getenv("S3_ACCESS_KEY")
18
25
  self.secret_key = os.getenv("S3_SECRET_KEY")
19
- self.bucket_name = os.getenv("S3_BUCKET_NAME", "default")
26
+ self.bucket_name = bucket_name or os.getenv("S3_BUCKET_NAME", "default")
20
27
  self._client = None
21
28
  self._lock = threading.Lock()
22
29
  self._initialize_client()
23
30
 
24
31
  def _initialize_client(self):
25
- """Initialize S3-compatible client once"""
32
+ """Initialize S3-compatible client once."""
26
33
  try:
27
- import boto3
34
+ import boto3 # lazy: boto3 is only required for this provider
28
35
 
29
36
  with self._lock:
30
37
  if not self._client:
@@ -38,6 +45,50 @@ class S3CompatibleAdapter(ObjectStorageAdapter):
38
45
  except Exception as e:
39
46
  raise ConnectionError(f"Failed to initialize S3-compatible client: {str(e)}")
40
47
 
48
+ # ------------------------------------------------------------------
49
+ # helpers
50
+ # ------------------------------------------------------------------
51
+ def _bucket(self, container_name: Optional[str] = None) -> str:
52
+ return container_name or self.bucket_name
53
+
54
+ @staticmethod
55
+ def _is_not_found(exc: Exception) -> bool:
56
+ # inspect botocore ClientError without importing botocore at module load
57
+ resp = getattr(exc, "response", None)
58
+ if isinstance(resp, dict):
59
+ code = str(resp.get("Error", {}).get("Code", ""))
60
+ return code in {"NoSuchKey", "404"}
61
+ return False
62
+
63
+ def _url(self, bucket: str, key: str) -> str:
64
+ if self.endpoint: # MinIO / Spaces / custom endpoint (path-style)
65
+ return f"{self.endpoint.rstrip('/')}/{bucket}/{key}"
66
+ return f"https://{bucket}.s3.amazonaws.com/{key}" # AWS default (virtual-host)
67
+
68
+ # ------------------------------------------------------------------
69
+ # put
70
+ # ------------------------------------------------------------------
71
+ def put(
72
+ self,
73
+ key: str,
74
+ data: bytes,
75
+ fileName: str = "",
76
+ optimize: bool = True,
77
+ media_type: Optional[str] = None,
78
+ metadata: Dict[str, Any] | None = None,
79
+ container_name: Optional[str] = None,
80
+ ) -> str:
81
+ if optimize and media_type:
82
+ data = self._optimize_media(data, media_type)
83
+ return self._put_raw(
84
+ key=key,
85
+ data=data,
86
+ fileName=fileName,
87
+ media_type=media_type,
88
+ metadata=metadata,
89
+ container_name=container_name,
90
+ )
91
+
41
92
  @retry(max_attempts=3, delay=1.0, exceptions=(StorageError,))
42
93
  def _put_raw(
43
94
  self,
@@ -46,99 +97,78 @@ class S3CompatibleAdapter(ObjectStorageAdapter):
46
97
  fileName: str = "",
47
98
  media_type: Optional[str] = None,
48
99
  metadata: Dict[str, Any] | None = None,
100
+ container_name: Optional[str] = None,
49
101
  ) -> str:
50
- """Upload object to S3-compatible storage with metadata and media type"""
102
+ """Upload object. Stored at `key` so get/delete find it by the same key."""
51
103
  try:
52
104
  if not self._client:
53
105
  self._initialize_client()
54
106
 
55
- # --------------------------------------------------
56
- # Resolve filename
57
- # --------------------------------------------------
58
- filename = fileName or os.path.basename(key)
107
+ bucket = self._bucket(container_name)
59
108
 
60
- # --------------------------------------------------
61
- # Ensure extension from media_type
62
- # --------------------------------------------------
109
+ # filename is metadata only — it must NOT alter the object key
110
+ filename = fileName or os.path.basename(key) or key
63
111
  if media_type:
64
112
  ext = mimetypes.guess_extension(media_type) or ""
65
113
  if ext and not filename.lower().endswith(ext):
66
114
  filename += ext
67
115
 
68
- # --------------------------------------------------
69
- # Final key
70
- # --------------------------------------------------
71
- blob_key = f"{key.rstrip('/')}/{filename}" if fileName else key
72
-
73
- # --------------------------------------------------
74
- # Metadata (string only)
75
- # --------------------------------------------------
76
116
  safe_metadata = {k: str(v) for k, v in (metadata or {}).items()}
77
117
  safe_metadata["filename"] = filename
78
118
 
79
- # --------------------------------------------------
80
- # Upload
81
- # --------------------------------------------------
82
119
  self._client.put_object( # type: ignore
83
- Bucket=self.bucket_name,
84
- Key=blob_key,
120
+ Bucket=bucket,
121
+ Key=key,
85
122
  Body=data,
86
123
  ContentType=media_type or "application/octet-stream",
87
124
  Metadata=safe_metadata,
88
125
  )
89
126
 
90
- self.logger.debug(f"S3 uploaded: {blob_key}, type={media_type}")
91
-
92
- # --------------------------------------------------
93
- # Return URL
94
- # --------------------------------------------------
95
- if self.endpoint:
96
- # MinIO / Spaces / custom endpoint
97
- url = f"{self.endpoint.rstrip('/')}/{self.bucket_name}/{blob_key}"
98
- else:
99
- # AWS S3 default
100
- url = f"https://{self.bucket_name}.s3.amazonaws.com/{blob_key}"
101
-
102
- return url
127
+ self.logger.debug(f"S3 uploaded: {bucket}/{key}, type={media_type}")
128
+ return self._url(bucket, key)
103
129
 
104
130
  except Exception as e:
105
131
  raise StorageError(f"S3-compatible put failed: {str(e)}")
106
132
 
133
+ # ------------------------------------------------------------------
134
+ # get / delete / list
135
+ # ------------------------------------------------------------------
107
136
  @retry(max_attempts=3, delay=1.0, exceptions=(StorageError,))
108
- def get(self, key: str) -> bytes | None:
109
- """Get object"""
137
+ def get(self, key: str, container_name: Optional[str] = None) -> bytes | None:
110
138
  try:
111
139
  if not self._client:
112
140
  self._initialize_client()
113
- if self._client:
114
- response = self._client.get_object(Bucket=self.bucket_name, Key=key)
115
- return response["Body"].read()
116
- return None
141
+ response = self._client.get_object( # type: ignore
142
+ Bucket=self._bucket(container_name), Key=key
143
+ )
144
+ return response["Body"].read()
117
145
  except Exception as e:
146
+ if self._is_not_found(e):
147
+ return None
118
148
  raise StorageError(f"S3-compatible get failed: {str(e)}")
119
149
 
120
150
  @retry(max_attempts=3, delay=1.0, exceptions=(StorageError,))
121
- def delete(self, key: str) -> bool:
122
- """Delete object"""
151
+ def delete(self, key: str, container_name: Optional[str] = None) -> bool:
123
152
  try:
124
153
  if not self._client:
125
154
  self._initialize_client()
126
- if self._client:
127
- self._client.delete_object(Bucket=self.bucket_name, Key=key)
128
- return True
129
- return False
155
+ self._client.delete_object(Bucket=self._bucket(container_name), Key=key) # type: ignore
156
+ return True
130
157
  except Exception as e:
158
+ if self._is_not_found(e):
159
+ return False
131
160
  raise StorageError(f"S3-compatible delete failed: {str(e)}")
132
161
 
133
162
  @retry(max_attempts=3, delay=1.0, exceptions=(StorageError,))
134
- def list(self, prefix: str = "") -> List[str]:
135
- """List objects with prefix"""
163
+ def list(self, prefix: str = "", container_name: Optional[str] = None) -> List[str]:
136
164
  try:
137
165
  if not self._client:
138
166
  self._initialize_client()
139
- if self._client:
140
- response = self._client.list_objects_v2(Bucket=self.bucket_name, Prefix=prefix)
141
- return [obj["Key"] for obj in response.get("Contents", [])]
142
- return []
167
+ paginator = self._client.get_paginator("list_objects_v2") # type: ignore
168
+ results: List[str] = []
169
+ for page in paginator.paginate(Bucket=self._bucket(container_name), Prefix=prefix):
170
+ results.extend(obj["Key"] for obj in page.get("Contents", []))
171
+ self.logger.debug(f"S3 listed {len(results)} objects prefix={prefix}")
172
+ return results
143
173
  except Exception as e:
144
174
  raise StorageError(f"S3-compatible list failed: {str(e)}")
@@ -5,8 +5,6 @@ import os
5
5
  import threading
6
6
  from typing import Any, Dict, List
7
7
 
8
- import boto3
9
- from botocore.exceptions import ClientError
10
8
 
11
9
  from ..base.QueueAdapter import QueueAdapter
12
10
  from ..errors import ConnectionError, QueueError
@@ -39,6 +37,8 @@ class SQSAdapter(QueueAdapter):
39
37
  # ---------------------------------------------------------
40
38
 
41
39
  def _initialize_client(self):
40
+ import boto3
41
+
42
42
  try:
43
43
  with self._lock:
44
44
  if self._client:
@@ -67,6 +67,9 @@ class SQSAdapter(QueueAdapter):
67
67
 
68
68
  def _ensure_queue_exists(self, queue_name: str) -> str:
69
69
  """Create queue if it does not exist"""
70
+
71
+ from botocore.exceptions import ClientError
72
+
70
73
  if not self._client:
71
74
  raise ConnectionError("SQS client not initialized")
72
75
 
@@ -0,0 +1,29 @@
1
+ # src/polydb/adapters/VercelFileAdapter.py
2
+
3
+ from typing import List, Optional
4
+
5
+ from ..base.SharedFilesAdapter import SharedFilesAdapter
6
+
7
+
8
+ class VercelFileAdapter(SharedFilesAdapter):
9
+ """
10
+ Vercel has no shared/persistent filesystem (only ephemeral per-invocation /tmp).
11
+ Shared file storage is not supported — use object storage (Vercel Blob) instead.
12
+ """
13
+
14
+ _MSG = (
15
+ "Vercel has no shared file storage (only ephemeral /tmp). "
16
+ "Use object storage (get_object_storage) instead."
17
+ )
18
+
19
+ def write(self, path: str, data: bytes, share_name: Optional[str] = None) -> bool:
20
+ raise NotImplementedError(self._MSG)
21
+
22
+ def read(self, path: str, share_name: Optional[str] = None) -> bytes | None:
23
+ raise NotImplementedError(self._MSG)
24
+
25
+ def delete(self, path: str, share_name: Optional[str] = None) -> bool:
26
+ raise NotImplementedError(self._MSG)
27
+
28
+ def list(self, directory: str = "", share_name: Optional[str] = None) -> List[str]:
29
+ raise NotImplementedError(self._MSG)
polydb/audit/__init__.py CHANGED
@@ -4,4 +4,4 @@ from .context import AuditContext
4
4
  from .manager import AuditManager
5
5
  from .AuditStorage import AuditStorage
6
6
 
7
- __all__ = ['AuditRecord', 'AuditContext', 'AuditManager', 'AuditStorage']
7
+ __all__ = ["AuditRecord", "AuditContext", "AuditManager", "AuditStorage"]
@@ -2,7 +2,7 @@ from polydb.utils import setup_logger
2
2
 
3
3
 
4
4
  from abc import ABC, abstractmethod
5
- from typing import List
5
+ from typing import List, Optional
6
6
 
7
7
 
8
8
  class SharedFilesAdapter(ABC):
@@ -12,21 +12,21 @@ class SharedFilesAdapter(ABC):
12
12
  self.logger = setup_logger(self.__class__.__name__)
13
13
 
14
14
  @abstractmethod
15
- def write(self, path: str, data: bytes) -> bool:
15
+ def write(self, path: str, data: bytes, share_name: Optional[str] = None) -> bool:
16
16
  """Write file"""
17
17
  pass
18
18
 
19
19
  @abstractmethod
20
- def read(self, path: str) -> bytes | None:
20
+ def read(self, path: str, share_name: Optional[str] = None) -> bytes | None:
21
21
  """Read file"""
22
22
  pass
23
23
 
24
24
  @abstractmethod
25
- def delete(self, path: str) -> bool:
25
+ def delete(self, path: str, share_name: Optional[str] = None) -> bool:
26
26
  """Delete file"""
27
27
  pass
28
28
 
29
29
  @abstractmethod
30
- def list(self, directory: str = "/") -> List[str]:
30
+ def list(self, directory: str = "", share_name: Optional[str] = None) -> List[str]:
31
31
  """List files in directory"""
32
32
  pass