feat: add source adapter base class and local filesystem adapter

- Abstract SourceAdapter with FileInfo dataclass
- LocalAdapter for filesystem backups
- Adapter factory for extensibility
This commit is contained in:
2026-05-11 20:52:31 +02:00
parent 83c3075de0
commit f925ae094c
3 changed files with 99 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
from typing import Dict, Any
from .base import SourceAdapter
from .local import LocalAdapter
ADAPTER_MAP = {
"local": LocalAdapter,
}
def get_adapter(source_type: str, config: Dict[str, Any]) -> SourceAdapter:
adapter_class = ADAPTER_MAP.get(source_type)
if not adapter_class:
raise ValueError(f"Unknown source type: {source_type}")
return adapter_class(config)
+39
View File
@@ -0,0 +1,39 @@
from abc import ABC, abstractmethod
from typing import List, Dict, Any, AsyncIterator
from dataclasses import dataclass
@dataclass
class FileInfo:
path: str
size: int
modified_time: float
is_directory: bool
class SourceAdapter(ABC):
def __init__(self, config: Dict[str, Any]):
self.config = config
@abstractmethod
async def connect(self) -> None:
"""Establish connection to source."""
pass
@abstractmethod
async def disconnect(self) -> None:
"""Close connection to source."""
pass
@abstractmethod
async def list_files(self, path: str = "") -> List[FileInfo]:
"""List files at given path."""
pass
@abstractmethod
async def read_file(self, path: str) -> AsyncIterator[bytes]:
"""Read file in chunks."""
pass
@abstractmethod
async def get_database_dump(self, config: Dict[str, Any]) -> AsyncIterator[bytes]:
"""Get database dump. Only implemented for database adapters."""
pass
+47
View File
@@ -0,0 +1,47 @@
import os
import aiofiles
from pathlib import Path
from typing import List, AsyncIterator, Dict, Any
from .base import SourceAdapter, FileInfo
class LocalAdapter(SourceAdapter):
async def connect(self) -> None:
base_path = self.config.get("path", ".")
if not os.path.exists(base_path):
raise FileNotFoundError(f"Path not found: {base_path}")
async def disconnect(self) -> None:
pass
async def list_files(self, path: str = "") -> List[FileInfo]:
base_path = Path(self.config.get("path", "."))
target_path = base_path / path if path else base_path
files = []
exclude_patterns = self.config.get("exclude", [])
for item in target_path.iterdir():
# Check exclude patterns
if any(item.match(pattern) for pattern in exclude_patterns):
continue
stat = item.stat()
files.append(FileInfo(
path=str(item.relative_to(base_path)),
size=stat.st_size,
modified_time=stat.st_mtime,
is_directory=item.is_dir()
))
return files
async def read_file(self, path: str) -> AsyncIterator[bytes]:
base_path = Path(self.config.get("path", "."))
file_path = base_path / path
async with aiofiles.open(file_path, "rb") as f:
while chunk := await f.read(8192):
yield chunk
async def get_database_dump(self, config: Dict[str, Any]) -> AsyncIterator[bytes]:
raise NotImplementedError("Local adapter does not support database dumps")