Skip to content

Commit 57da5f4

Browse files
authored
✨ Abstract Storage SDK #1219
2 parents f0dc54b + 2cf6f04 commit 57da5f4

39 files changed

+4097
-689
lines changed

backend/database/attachment_db.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ def get_file_size_from_minio(object_name: str, bucket: Optional[str] = None) ->
165165
"""
166166
Get file size by object name
167167
"""
168-
bucket = bucket or minio_client.default_bucket
168+
bucket = bucket or minio_client.storage_config.default_bucket
169169
return minio_client.get_file_size(object_name, bucket)
170170

171171

@@ -207,7 +207,7 @@ def delete_file(object_name: str, bucket: Optional[str] = None) -> Dict[str, Any
207207
Dict[str, Any]: Delete result, containing success flag and error message (if any)
208208
"""
209209
if not bucket:
210-
bucket = minio_client.default_bucket
210+
bucket = minio_client.storage_config.default_bucket
211211
success, result = minio_client.delete_file(object_name, bucket)
212212

213213
response = {"success": success, "object_name": object_name}

backend/database/client.py

Lines changed: 37 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
import logging
2-
import os
32
from contextlib import contextmanager
43
from typing import Any, BinaryIO, Dict, List, Optional, Tuple
54

6-
import boto3
75
import psycopg2
8-
from botocore.client import Config
9-
from botocore.exceptions import ClientError
106
from sqlalchemy import create_engine
117
from sqlalchemy.orm import class_mapper, sessionmaker
128

@@ -23,6 +19,8 @@
2319
POSTGRES_USER,
2420
)
2521
from database.db_models import TableBase
22+
from nexent.storage.storage_client_factory import create_storage_client_from_config, MinIOStorageConfig
23+
2624

2725
logger = logging.getLogger("database.client")
2826

@@ -73,6 +71,12 @@ def clean_string_values(data: Dict[str, Any]) -> Dict[str, Any]:
7371

7472

7573
class MinioClient:
74+
"""
75+
MinIO client wrapper using storage SDK
76+
77+
This class maintains backward compatibility with the existing MinioClient interface
78+
while using the new storage SDK under the hood.
79+
"""
7680
_instance: Optional['MinioClient'] = None
7781

7882
def __new__(cls):
@@ -81,39 +85,18 @@ def __new__(cls):
8185
return cls._instance
8286

8387
def __init__(self):
84-
self.endpoint = MINIO_ENDPOINT
85-
self.access_key = MINIO_ACCESS_KEY
86-
self.secret_key = MINIO_SECRET_KEY
87-
self.region = MINIO_REGION
88-
self.default_bucket = MINIO_DEFAULT_BUCKET
89-
90-
# Initialize S3 client with proxy settings
91-
self.client = boto3.client(
92-
's3',
93-
endpoint_url=self.endpoint,
94-
aws_access_key_id=self.access_key,
95-
aws_secret_access_key=self.secret_key,
96-
region_name=self.region,
97-
config=Config(
98-
signature_version='s3v4',
99-
proxies={
100-
'http': None,
101-
'https': None
102-
}
103-
)
88+
# Determine if endpoint uses HTTPS
89+
secure = MINIO_ENDPOINT.startswith('https://') if MINIO_ENDPOINT else True
90+
# Initialize storage client using SDK factory
91+
self.storage_config = MinIOStorageConfig(
92+
endpoint=MINIO_ENDPOINT,
93+
access_key=MINIO_ACCESS_KEY,
94+
secret_key=MINIO_SECRET_KEY,
95+
region=MINIO_REGION,
96+
default_bucket=MINIO_DEFAULT_BUCKET,
97+
secure=secure
10498
)
105-
106-
# Ensure default bucket exists
107-
self._ensure_bucket_exists(self.default_bucket)
108-
109-
def _ensure_bucket_exists(self, bucket_name: str) -> None:
110-
"""Ensure bucket exists, create if it doesn't"""
111-
try:
112-
self.client.head_bucket(Bucket=bucket_name)
113-
except ClientError:
114-
# Bucket doesn't exist, create it
115-
self.client.create_bucket(Bucket=bucket_name)
116-
logger.info(f"Created bucket: {bucket_name}")
99+
self._storage_client = create_storage_client_from_config(self.storage_config)
117100

118101
def upload_file(
119102
self,
@@ -132,16 +115,7 @@ def upload_file(
132115
Returns:
133116
Tuple[bool, str]: (Success status, File URL or error message)
134117
"""
135-
bucket = bucket or self.default_bucket
136-
if object_name is None:
137-
object_name = os.path.basename(file_path)
138-
139-
try:
140-
self.client.upload_file(file_path, bucket, object_name)
141-
file_url = f"/{bucket}/{object_name}"
142-
return True, file_url
143-
except Exception as e:
144-
return False, str(e)
118+
return self._storage_client.upload_file(file_path, object_name, bucket)
145119

146120
def upload_fileobj(self, file_obj: BinaryIO, object_name: str, bucket: Optional[str] = None) -> Tuple[bool, str]:
147121
"""
@@ -155,13 +129,7 @@ def upload_fileobj(self, file_obj: BinaryIO, object_name: str, bucket: Optional[
155129
Returns:
156130
Tuple[bool, str]: (Success status, File URL or error message)
157131
"""
158-
bucket = bucket or self.default_bucket
159-
try:
160-
self.client.upload_fileobj(file_obj, bucket, object_name)
161-
file_url = f"/{bucket}/{object_name}"
162-
return True, file_url
163-
except Exception as e:
164-
return False, str(e)
132+
return self._storage_client.upload_fileobj(file_obj, object_name, bucket)
165133

166134
def download_file(self, object_name: str, file_path: str, bucket: Optional[str] = None) -> Tuple[bool, str]:
167135
"""
@@ -175,12 +143,7 @@ def download_file(self, object_name: str, file_path: str, bucket: Optional[str]
175143
Returns:
176144
Tuple[bool, str]: (Success status, Success message or error message)
177145
"""
178-
bucket = bucket or self.default_bucket
179-
try:
180-
self.client.download_file(bucket, object_name, file_path)
181-
return True, f"File downloaded successfully to {file_path}"
182-
except Exception as e:
183-
return False, str(e)
146+
return self._storage_client.download_file(object_name, file_path, bucket)
184147

185148
def get_file_url(self, object_name: str, bucket: Optional[str] = None, expires: int = 3600) -> Tuple[bool, str]:
186149
"""
@@ -194,23 +157,20 @@ def get_file_url(self, object_name: str, bucket: Optional[str] = None, expires:
194157
Returns:
195158
Tuple[bool, str]: (Success status, Presigned URL or error message)
196159
"""
197-
bucket = bucket or self.default_bucket
198-
try:
199-
url = self.client.generate_presigned_url('get_object', Params={'Bucket': bucket, 'Key': object_name},
200-
ExpiresIn=expires)
201-
return True, url
202-
except Exception as e:
203-
return False, str(e)
160+
return self._storage_client.get_file_url(object_name, bucket, expires)
204161

205162
def get_file_size(self, object_name: str, bucket: Optional[str] = None) -> int:
206-
bucket = bucket or self.default_bucket
207-
try:
208-
response = self.client.head_object(Bucket=bucket, Key=object_name)
209-
return int(response['ContentLength'])
210-
except ClientError as e:
211-
logger.error(
212-
f"Get file size by objectname({object_name}) failed: {e}")
213-
return 0
163+
"""
164+
Get file size in bytes
165+
166+
Args:
167+
object_name: Object name
168+
bucket: Bucket name, if not specified use default bucket
169+
170+
Returns:
171+
int: File size in bytes, 0 if file not found or error
172+
"""
173+
return self._storage_client.get_file_size(object_name, bucket)
214174

215175
def list_files(self, prefix: str = "", bucket: Optional[str] = None) -> List[dict]:
216176
"""
@@ -223,19 +183,7 @@ def list_files(self, prefix: str = "", bucket: Optional[str] = None) -> List[dic
223183
Returns:
224184
List[dict]: List of file information
225185
"""
226-
bucket = bucket or self.default_bucket
227-
try:
228-
response = self.client.list_objects_v2(
229-
Bucket=bucket, Prefix=prefix)
230-
files = []
231-
if 'Contents' in response:
232-
for obj in response['Contents']:
233-
files.append(
234-
{'key': obj['Key'], 'size': obj['Size'], 'last_modified': obj['LastModified']})
235-
return files
236-
except Exception as e:
237-
logger.error(f"Error listing files: {str(e)}")
238-
return []
186+
return self._storage_client.list_files(prefix, bucket)
239187

240188
def delete_file(self, object_name: str, bucket: Optional[str] = None) -> Tuple[bool, str]:
241189
"""
@@ -248,12 +196,7 @@ def delete_file(self, object_name: str, bucket: Optional[str] = None) -> Tuple[b
248196
Returns:
249197
Tuple[bool, str]: (Success status, Success message or error message)
250198
"""
251-
bucket = bucket or self.default_bucket
252-
try:
253-
self.client.delete_object(Bucket=bucket, Key=object_name)
254-
return True, f"File {object_name} deleted successfully"
255-
except Exception as e:
256-
return False, str(e)
199+
return self._storage_client.delete_file(object_name, bucket)
257200

258201
def get_file_stream(self, object_name: str, bucket: Optional[str] = None) -> Tuple[bool, Any]:
259202
"""
@@ -266,12 +209,7 @@ def get_file_stream(self, object_name: str, bucket: Optional[str] = None) -> Tup
266209
Returns:
267210
Tuple[bool, Any]: (Success status, File stream object or error message)
268211
"""
269-
bucket = bucket or self.default_bucket
270-
try:
271-
response = self.client.get_object(Bucket=bucket, Key=object_name)
272-
return True, response['Body']
273-
except Exception as e:
274-
return False, str(e)
212+
return self._storage_client.get_file_stream(object_name, bucket)
275213

276214

277215
# Create global database and MinIO client instances

sdk/nexent/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from .core import *
22
from .data_process import *
33
from .memory import *
4+
from .storage import *
45
from .vector_database import *
56

67

7-
__all__ = ["core", "data_process", "memory", "vector_database"]
8+
__all__ = ["core", "data_process", "memory", "storage", "vector_database"]

sdk/nexent/storage/__init__.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"""
2+
Storage module for Nexent SDK
3+
4+
Provides abstract storage interface and implementations for various storage backends.
5+
"""
6+
7+
from .storage_client_base import StorageClient, StorageConfig
8+
from .storage_client_factory import create_storage_client_from_config
9+
from .minio_config import MinIOStorageConfig
10+
from .minio import MinIOStorageClient
11+
12+
__all__ = [
13+
"StorageClient",
14+
"StorageConfig",
15+
"MinIOStorageConfig",
16+
"create_storage_client_from_config",
17+
"MinIOStorageClient",
18+
]
19+

0 commit comments

Comments
 (0)