Skip to content

Commit cdafeac

Browse files
committed
Add push/pull methods to ImpulseDB for S3 sync
ImpulseDB now accepts an optional s3_manager parameter, enabling push() to back up the local database to S3 with a timestamp, and pull() to restore the latest backup from S3. S3Manager gains a restore_database() method (the counterpart to the existing backup_database()), and S3Backend exposes the same as a convenience wrapper.
1 parent 294e381 commit cdafeac

File tree

3 files changed

+85
-2
lines changed

3 files changed

+85
-2
lines changed

impulse/collection/database.py

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,19 @@
77
import sqlite3
88
from datetime import datetime, timezone
99
from pathlib import Path
10-
from typing import Dict, List, Optional
10+
from typing import TYPE_CHECKING, Dict, List, Optional
1111
from contextlib import contextmanager
1212

13+
if TYPE_CHECKING:
14+
from impulse.collection.s3_manager import S3Manager
15+
1316

1417
class ImpulseDB:
1518
"""Manages SQLite database for replay tracking (raw and parsed)."""
1619

17-
def __init__(self, db_path: str = "./impulse.db"):
20+
def __init__(self, db_path: str = "./impulse.db", s3_manager: "Optional[S3Manager]" = None):
1821
self.db_path = Path(db_path)
22+
self.s3_manager = s3_manager
1923
self.db_path.parent.mkdir(parents=True, exist_ok=True)
2024
self.init_database()
2125
print(f"Database initialized: {self.db_path}")
@@ -613,3 +617,42 @@ def get_full_stats(self) -> Dict:
613617
'raw': self.get_stats(),
614618
'parsed': self.get_parse_stats()
615619
}
620+
621+
# =========================================================================
622+
# S3 Sync
623+
# =========================================================================
624+
625+
def push(self, s3_prefix: str = "database-backups") -> Dict:
626+
"""
627+
Back up the local database to S3.
628+
629+
Creates a timestamped copy under s3_prefix so previous backups are
630+
preserved. Requires s3_manager to be set on this instance.
631+
632+
Args:
633+
s3_prefix: S3 prefix for backups (default: "database-backups")
634+
635+
Returns:
636+
Dict with backup info (s3_key, size_bytes, success, ...)
637+
"""
638+
if self.s3_manager is None:
639+
raise RuntimeError("push() requires an s3_manager. Pass one to ImpulseDB().")
640+
return self.s3_manager.backup_database(str(self.db_path), s3_prefix)
641+
642+
def pull(self, s3_prefix: str = "database-backups") -> bool:
643+
"""
644+
Replace the local database with the latest backup from S3.
645+
646+
Downloads the most recent timestamped backup and overwrites the local
647+
file at self.db_path. Subsequent queries will use the restored data
648+
automatically. Requires s3_manager to be set on this instance.
649+
650+
Args:
651+
s3_prefix: S3 prefix where backups are stored (default: "database-backups")
652+
653+
Returns:
654+
True if successful, False otherwise
655+
"""
656+
if self.s3_manager is None:
657+
raise RuntimeError("pull() requires an s3_manager. Pass one to ImpulseDB().")
658+
return self.s3_manager.restore_database(str(self.db_path), s3_prefix)

impulse/collection/s3_manager.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,33 @@ def backup_database(self, db_path: str, s3_prefix: str = "database-backups") ->
301301

302302
return result
303303

304+
def restore_database(self, local_path: str, s3_prefix: str = "database-backups") -> bool:
305+
"""
306+
Restore the latest database backup from S3.
307+
308+
Backups are stored with timestamps in their key names, so lexicographic
309+
sort gives chronological order and the last entry is the most recent.
310+
311+
Args:
312+
local_path: Where to write the restored database file
313+
s3_prefix: S3 prefix where backups are stored
314+
315+
Returns:
316+
True if successful, False otherwise
317+
"""
318+
keys = self.list_objects(prefix=s3_prefix)
319+
if not keys:
320+
print(f"✗ No database backups found at s3://{self.s3_bucket_name}/{s3_prefix}/")
321+
return False
322+
323+
latest_key = sorted(keys)[-1]
324+
success = self.download_file(latest_key, local_path)
325+
if success:
326+
print(f"✓ Database restored from s3://{self.s3_bucket_name}/{latest_key}")
327+
else:
328+
print(f"✗ Database restore failed")
329+
return success
330+
304331
def get_storage_stats(self, prefix: str = "") -> Dict:
305332
"""
306333
Get storage statistics for objects with given prefix.

impulse/collection/storage.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,3 +302,16 @@ def backup_database(self, db_path: str, backup_prefix: str = "database-backups")
302302
Dict with backup info
303303
"""
304304
return self.s3_manager.backup_database(db_path, backup_prefix)
305+
306+
def restore_database(self, local_path: str, backup_prefix: str = "database-backups") -> bool:
307+
"""
308+
Convenience method to restore the latest database backup from S3.
309+
310+
Args:
311+
local_path: Where to write the restored database file
312+
backup_prefix: S3 prefix where backups are stored
313+
314+
Returns:
315+
True if successful, False otherwise
316+
"""
317+
return self.s3_manager.restore_database(local_path, backup_prefix)

0 commit comments

Comments
 (0)