| from collections.abc import Callable |
| from typing import Any |
|
|
| from supabase import Client |
|
|
| from ..config.logfire_config import get_logger |
| from ..services.client_manager import get_supabase_client |
|
|
| logger = get_logger(__name__) |
|
|
|
|
| class BaseRepository: |
| def __init__(self, supabase_client: Client | None = None): |
| self.supabase_client = supabase_client or get_supabase_client() |
| self._token: str | None = None |
|
|
| def set_user_context(self, token: str) -> "BaseRepository": |
| """ |
| 注入使用者的 JWT Token,確保後續查詢能觸發資料庫層的 RLS。 |
| """ |
| if token: |
| self._token = token |
| |
| self.supabase_client.postgrest.auth(token) |
| return self |
|
|
| def execute_query( |
| self, query_func: Callable[[], Any], error_context: str = "Query failed", require_data: bool = False, max_retries: int = 1 |
| ) -> tuple[bool, dict[str, Any]]: |
| """ |
| 封裝 Supabase 查詢,提供標準化的錯誤處理與資料驗證機制。包含對 ConnectionTerminated 的自動重試 (自癒)。 |
| |
| Args: |
| query_func: 一個閉包或 lambda,回傳 Supabase 查詢的 response |
| error_context: 錯誤紀錄時的上下文描述 (供前端介面與後端 Log 辨識) |
| require_data: 如果此為 True,必定要求 response.data 不為空,否則視為 False |
| max_retries: 遇到錯誤時的最多重試次數 |
| |
| Returns: |
| Tuple[bool, dict]: (成功與否, {"data": list/dict} 或 {"error": str}) |
| """ |
| import time |
|
|
| last_exception = None |
| for attempt in range(max_retries + 1): |
| try: |
| response = query_func() |
| if require_data and not response.data: |
| return False, {"error": f"{error_context}"} |
|
|
| |
| result = {"data": response.data} |
| if hasattr(response, "count") and response.count is not None: |
| result["count"] = response.count |
|
|
| return True, result |
| except Exception as e: |
| last_exception = e |
| |
| error_str = str(e) |
| if attempt < max_retries and ("ConnectionTerminated" in error_str or "RemoteProtocolError" in error_str or "ReadTimeout" in error_str): |
| logger.warning(f"⚠️ {error_context}: Connection dropped (attempt {attempt + 1}). Retrying...") |
| time.sleep(0.5) |
| continue |
| break |
|
|
| logger.error(f"{error_context}: {str(last_exception)}") |
| return False, {"error": f"{error_context}: {str(last_exception)}"} |
|
|