Spaces:
Running
Running
| from __future__ import annotations | |
| import asyncio | |
| import logging | |
| from typing import Any, Dict, List, Optional | |
| from supabase import create_client, Client | |
| from app.config import get_settings | |
| logger = logging.getLogger(__name__) | |
| class SupabaseClient: | |
| def __init__(self, url: str, service_key: str): | |
| self._url = url | |
| self._service_key = service_key | |
| self._client: Optional[Client] = None | |
| async def initialize(self): | |
| self._client = await asyncio.to_thread(create_client, self._url, self._service_key) | |
| logger.info("Supabase client initialized") | |
| def client(self) -> Client: | |
| if self._client is None: | |
| raise RuntimeError("Supabase client not initialized. Call initialize() first.") | |
| return self._client | |
| async def close(self): | |
| self._client = None | |
| set_supabase_client(None) | |
| logger.info("Supabase client closed") | |
| # --- Table operations --- | |
| async def select( | |
| self, table: str, columns: str = "*", | |
| eq: Optional[tuple[str, Any]] = None, | |
| order: Optional[tuple[str, bool]] = None, | |
| limit: Optional[int] = None, | |
| offset: Optional[int] = None, | |
| ) -> List[Dict[str, Any]]: | |
| query = self.client.table(table).select(columns) | |
| if eq: | |
| query = query.eq(eq[0], eq[1]) | |
| if order: | |
| query = query.order(order[0], desc=order[1]) | |
| if limit: | |
| query = query.limit(limit) | |
| if offset: | |
| query = query.offset(offset) | |
| result = await asyncio.to_thread(query.execute) | |
| return result.data if result else [] | |
| async def select_in( | |
| self, table: str, column: str, values: List[Any], | |
| columns: str = "*", | |
| ) -> List[Dict[str, Any]]: | |
| query = self.client.table(table).select(columns).in_(column, values) | |
| result = await asyncio.to_thread(query.execute) | |
| return result.data if result else [] | |
| async def insert( | |
| self, table: str, data: Dict[str, Any], | |
| returning: str = "representation", | |
| ) -> Optional[Dict[str, Any]]: | |
| query = self.client.table(table).insert(data, returning=returning) | |
| result = await asyncio.to_thread(query.execute) | |
| if result and result.data: | |
| return result.data[0] | |
| return None | |
| async def update( | |
| self, table: str, column: str, value: Any, | |
| data: Dict[str, Any], | |
| ) -> List[Dict[str, Any]]: | |
| query = self.client.table(table).update(data).eq(column, value) | |
| result = await asyncio.to_thread(query.execute) | |
| return result.data if result else [] | |
| async def delete( | |
| self, table: str, column: str, value: Any, | |
| ) -> List[Dict[str, Any]]: | |
| query = self.client.table(table).delete().eq(column, value) | |
| result = await asyncio.to_thread(query.execute) | |
| return result.data if result else [] | |
| async def upsert( | |
| self, table: str, data: Dict[str, Any], | |
| on_conflict: str = "id", | |
| ) -> Optional[Dict[str, Any]]: | |
| query = self.client.table(table).upsert(data, on_conflict=on_conflict) | |
| result = await asyncio.to_thread(query.execute) | |
| if result and result.data: | |
| return result.data[0] | |
| return None | |
| async def find_one( | |
| self, table: str, column: str, value: Any, | |
| columns: str = "*", | |
| ) -> Optional[Dict[str, Any]]: | |
| rows = await self.select(table, columns=columns, eq=(column, value), limit=1) | |
| return rows[0] if rows else None | |
| async def find_all( | |
| self, table: str, column: str, value: Any, | |
| columns: str = "*", | |
| ) -> List[Dict[str, Any]]: | |
| return await self.select(table, columns=columns, eq=(column, value)) | |
| async def count( | |
| self, table: str, column: str, value: Any, | |
| ) -> int: | |
| query = self.client.table(table).select("*", count="exact").eq(column, value) | |
| result = await asyncio.to_thread(query.execute) | |
| return result.count if hasattr(result, "count") and result.count else 0 | |
| _client_instance: Optional[SupabaseClient] = None | |
| def get_supabase_client() -> Optional[SupabaseClient]: | |
| global _client_instance | |
| return _client_instance | |
| def set_supabase_client(client: SupabaseClient): | |
| global _client_instance | |
| _client_instance = client | |
| async def create_supabase_client() -> SupabaseClient: | |
| settings = get_settings() | |
| url = settings.supabase_url | |
| key = settings.supabase_service_role_key | |
| if not url or not key: | |
| logger.warning("Supabase not configured (SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY missing)") | |
| return None | |
| client = SupabaseClient(url, key) | |
| await client.initialize() | |
| set_supabase_client(client) | |
| return client | |