-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #14 from hv0905/feat/storage
Basic implementation of storage service
- Loading branch information
Showing
18 changed files
with
666 additions
and
123 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
from app.Services.storage.local_storage import LocalStorage | ||
from app.Services.storage.s3_compatible_storage import S3Storage | ||
from app.config import config, StorageMode | ||
|
||
|
||
class StorageService: | ||
def __init__(self): | ||
self.local_storage = LocalStorage() | ||
self.active_storage = None | ||
match config.storage.method: | ||
case StorageMode.LOCAL: | ||
self.active_storage = self.local_storage | ||
case StorageMode.S3: | ||
self.active_storage = S3Storage() | ||
case StorageMode.DISABLED: | ||
return | ||
case _: | ||
raise NotImplementedError(f"Storage method {config.storage.method} not implemented. " | ||
f"Available methods: local, s3") | ||
self.active_storage.pre_check() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
import abc | ||
import os | ||
from typing import TypeVar, Generic, TypeAlias, Optional, AsyncGenerator | ||
|
||
from app.Models.img_data import ImageData | ||
|
||
FileMetaDataT = TypeVar('FileMetaDataT') | ||
|
||
PathLikeType: TypeAlias = str | os.PathLike | ||
LocalFilePathType: TypeAlias = PathLikeType | bytes | ||
RemoteFilePathType: TypeAlias = PathLikeType | ||
LocalFileMetaDataType: TypeAlias = FileMetaDataT | ||
RemoteFileMetaDataType: TypeAlias = FileMetaDataT | ||
|
||
|
||
class BaseStorage(abc.ABC, Generic[FileMetaDataT]): | ||
def __init__(self): | ||
self.static_dir: os.PathLike | ||
self.thumbnails_dir: os.PathLike | ||
self.deleted_dir: os.PathLike | ||
self.file_metadata: FileMetaDataT | ||
|
||
@abc.abstractmethod | ||
def pre_check(self): | ||
raise NotImplementedError | ||
|
||
@abc.abstractmethod | ||
async def is_exist(self, | ||
remote_file: RemoteFilePathType) -> bool: | ||
""" | ||
Check if a remote_file exists. | ||
:param remote_file: The file path relative to static_dir | ||
:return: True if the file exists, False otherwise | ||
""" | ||
raise NotImplementedError | ||
|
||
@abc.abstractmethod | ||
async def size(self, | ||
remote_file: RemoteFilePathType) -> int: | ||
""" | ||
Get the size of a file in static_dir | ||
:param remote_file: The file path relative to static_dir | ||
:return: file's size | ||
""" | ||
raise NotImplementedError | ||
|
||
@abc.abstractmethod | ||
async def url(self, | ||
remote_file: RemoteFilePathType) -> str: | ||
""" | ||
Get the original URL of a file in static_dir. | ||
This url will be placed in the payload field of the qdrant. | ||
:param remote_file: The file path relative to static_dir | ||
:return: file's "original URL" | ||
""" | ||
raise NotImplementedError | ||
|
||
@abc.abstractmethod | ||
async def presign_url(self, | ||
remote_file: RemoteFilePathType, | ||
expire_second: int = 3600) -> str: | ||
""" | ||
Get the presign URL of a file in static_dir. | ||
:param remote_file: The file path relative to static_dir | ||
:param expire_second: Valid time for presign url | ||
:return: file's "presign URL" | ||
""" | ||
raise NotImplementedError | ||
|
||
@abc.abstractmethod | ||
async def fetch(self, | ||
remote_file: RemoteFilePathType) -> bytes: | ||
""" | ||
Fetch a file from static_dir | ||
:param remote_file: The file path relative to static_dir | ||
:return: file's content | ||
""" | ||
raise NotImplementedError | ||
|
||
@abc.abstractmethod | ||
async def upload(self, | ||
local_file: "LocalFilePathType", | ||
remote_file: RemoteFilePathType) -> None: | ||
""" | ||
Move a local picture file to the static_dir. | ||
:param local_file: The absolute path to the local file or bytes. | ||
:param remote_file: The file path relative to static_dir | ||
""" | ||
raise NotImplementedError | ||
|
||
@abc.abstractmethod | ||
async def copy(self, | ||
old_remote_file: RemoteFilePathType, | ||
new_remote_file: RemoteFilePathType) -> None: | ||
""" | ||
Copy a file in static_dir. | ||
:param old_remote_file: The file path relative to static_dir | ||
:param new_remote_file: The file path relative to static_dir | ||
""" | ||
raise NotImplementedError | ||
|
||
@abc.abstractmethod | ||
async def move(self, | ||
old_remote_file: RemoteFilePathType, | ||
new_remote_file: RemoteFilePathType) -> None: | ||
""" | ||
Move a file in static_dir. | ||
:param old_remote_file: The file path relative to static_dir | ||
:param new_remote_file: The file path relative to static_dir | ||
""" | ||
raise NotImplementedError | ||
|
||
@abc.abstractmethod | ||
async def delete(self, | ||
remote_file: RemoteFilePathType) -> None: | ||
""" | ||
Move a file in static_dir. | ||
:param remote_file: The file path relative to static_dir | ||
""" | ||
raise NotImplementedError | ||
|
||
@abc.abstractmethod | ||
async def list_files(self, | ||
path: RemoteFilePathType, | ||
pattern: Optional[str] = "*", | ||
batch_max_files: Optional[int] = None, | ||
valid_extensions: Optional[set[str]] = None) \ | ||
-> AsyncGenerator[list[RemoteFilePathType], None]: | ||
""" | ||
Asynchronously generates a list of files from a given base directory path that match a specified pattern and set | ||
of file extensions. | ||
:param path: The relative base directory path from which relative to static_dir to start listing files. | ||
:param pattern: A glob pattern to filter files based on their names. Defaults to '*' which selects all files. | ||
:param batch_max_files: The maximum number of files to return. If None, all matching files are returned. | ||
:param valid_extensions: An extra set of file extensions to include (e.g., {".jpg", ".png"}). | ||
If None, files are not filtered by extension. | ||
:return: An asynchronous generator yielding lists of RemoteFilePathType objects representing the matching files. | ||
Usage example: | ||
async for batch in list_files(base_path=".", pattern="*", max_files=100, valid_extensions={".jpg", ".png"}): | ||
print(f"Batch: {batch}") | ||
""" | ||
raise NotImplementedError | ||
|
||
@abc.abstractmethod | ||
async def update_metadata(self, | ||
local_file_metadata: LocalFileMetaDataType, | ||
remote_file_metadata: RemoteFileMetaDataType) -> None: | ||
raise NotImplementedError | ||
|
||
async def get_image_url(self, img: ImageData) -> str: | ||
return img.url |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
class StorageExtension(Exception): | ||
pass | ||
|
||
|
||
class LocalFileNotFoundError(StorageExtension): | ||
pass | ||
|
||
|
||
class LocalFileExistsError(StorageExtension): | ||
pass | ||
|
||
|
||
class LocalFilePermissionError(StorageExtension): | ||
pass | ||
|
||
|
||
class RemoteFileNotFoundError(StorageExtension): | ||
pass | ||
|
||
|
||
class RemoteFileExistsError(StorageExtension): | ||
pass | ||
|
||
|
||
class RemoteFilePermissionError(StorageExtension): | ||
pass | ||
|
||
|
||
class RemoteConnectError(StorageExtension): | ||
pass |
Oops, something went wrong.