Coverage for src / local_deep_research / storage / database.py: 96%
71 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:55 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:55 +0000
1"""Database-based report storage implementation."""
3from typing import Dict, Any, List, Optional
4from loguru import logger
5from sqlalchemy.orm import Session
7from .base import ReportStorage
8from ..database.models import ResearchHistory
11class DatabaseReportStorage(ReportStorage):
12 """Store reports in the database with caching support."""
14 def __init__(self, session: Session):
15 """Initialize database storage.
17 Args:
18 session: SQLAlchemy database session
19 """
20 self.session = session
22 def save_report(
23 self,
24 research_id: str,
25 content: str,
26 metadata: Optional[Dict[str, Any]] = None,
27 username: Optional[str] = None,
28 ) -> bool:
29 """Save report to database."""
30 try:
31 research = (
32 self.session.query(ResearchHistory)
33 .filter_by(id=research_id)
34 .first()
35 )
37 if not research:
38 logger.error(f"Research {research_id} not found")
39 return False
41 research.report_content = content # type: ignore[assignment]
43 if metadata:
44 if research.research_meta:
45 research.research_meta.update(metadata) # type: ignore[union-attr]
46 else:
47 research.research_meta = metadata # type: ignore[assignment]
49 self.session.commit()
50 logger.info(f"Saved report for research {research_id} to database")
51 return True
53 except Exception:
54 logger.exception("Error saving report to database")
55 self.session.rollback()
56 return False
58 def get_report(
59 self, research_id: str, username: Optional[str] = None
60 ) -> Optional[str]:
61 """Get report from database."""
62 try:
63 research = (
64 self.session.query(ResearchHistory)
65 .filter_by(id=research_id)
66 .first()
67 )
69 if not research or not research.report_content:
70 return None
72 return research.report_content # type: ignore[return-value]
74 except Exception:
75 logger.exception("Error getting report from database")
76 return None
78 def get_report_with_metadata(
79 self, research_id: str, username: Optional[str] = None
80 ) -> Optional[Dict[str, Any]]:
81 """Get report with metadata from database."""
82 try:
83 research = (
84 self.session.query(ResearchHistory)
85 .filter_by(id=research_id)
86 .first()
87 )
89 if not research or not research.report_content:
90 return None
92 return {
93 "content": research.report_content,
94 "metadata": research.research_meta or {},
95 "query": research.query,
96 "mode": research.mode,
97 "created_at": research.created_at,
98 "completed_at": research.completed_at,
99 "duration_seconds": research.duration_seconds,
100 }
102 except Exception:
103 logger.exception("Error getting report with metadata")
104 return None
106 def list_reports(
107 self, username: Optional[str] = None
108 ) -> List[Dict[str, Any]]:
109 """List reports from database."""
110 try:
111 query = self.session.query(ResearchHistory).filter(
112 ResearchHistory.report_content.isnot(None)
113 )
114 results = query.all()
115 return [
116 {
117 "id": r.id,
118 "query": r.query,
119 "mode": r.mode,
120 "created_at": r.created_at,
121 "completed_at": r.completed_at,
122 }
123 for r in results
124 ]
125 except Exception:
126 logger.exception("Error listing reports from database")
127 return []
129 def delete_report(
130 self, research_id: str, username: Optional[str] = None
131 ) -> bool:
132 """Delete report from database."""
133 try:
134 research = (
135 self.session.query(ResearchHistory)
136 .filter_by(id=research_id)
137 .first()
138 )
140 if not research:
141 return False
143 research.report_content = None # type: ignore[assignment]
144 self.session.commit()
146 return True
148 except Exception:
149 logger.exception("Error deleting report")
150 self.session.rollback()
151 return False
153 def report_exists(
154 self, research_id: str, username: Optional[str] = None
155 ) -> bool:
156 """Check if report exists in database."""
157 try:
158 research = (
159 self.session.query(ResearchHistory)
160 .filter_by(id=research_id)
161 .first()
162 )
164 return research is not None and research.report_content is not None
166 except Exception:
167 logger.exception("Error checking if report exists")
168 return False