myrmidon / python /src /server /repositories /base_repository.py
tek Atrust
chore(deploy): build monolithic server for Hugging Face
d5ef46f
Raw
History Blame Contribute Delete
2.89 kB
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
# 物理加固:更新 Supabase Client 的 Authorization 標頭
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}"}
# Include count metadata if available in the response
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
# Only retry on connection terminated or similar network/server errors
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)}"}