File size: 2,426 Bytes
ef9fa15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
"""
Database Client Setup
=====================

What:
    Creates the shared Supabase client and exposes a small accessor function
    for the rest of the backend.

How:
    Environment variables are loaded once at import time, then used to build
    a single Supabase client. Other modules call `get_db()` instead of
    importing the client directly so access stays consistent.

What it does:
    - Loads `SUPABASE_URL` and `SUPABASE_KEY` from the environment
    - Creates the shared Supabase client
    - Exposes `get_db()` as the standard database client accessor

What it does NOT do:
    - Does not define repositories or services
    - Does not contain business logic
    - Does not execute domain queries itself
"""
import os
import threading
from dotenv import load_dotenv
from httpx import Client as HttpxClient
from supabase import create_client, Client
from supabase.lib.client_options import SyncClientOptions

load_dotenv()

url: str = (os.environ.get("SUPABASE_URL") or "").strip()
key: str = (
    os.environ.get("SUPABASE_KEY")
    or os.environ.get("SUPABASE_SERVICE_ROLE_KEY")
    or ""
).strip()


def create_db_client() -> Client:
    """Create a fresh Supabase client using the backend service key."""
    if not url:
        raise RuntimeError("SUPABASE_URL is required.")
    if not key:
        raise RuntimeError("SUPABASE_KEY or SUPABASE_SERVICE_ROLE_KEY is required.")

    # Ignore machine-level proxy env vars for Supabase calls.
    # This project has been run in environments where HTTP(S)_PROXY points to
    # a dead local proxy, which breaks every database request with WinError 10061.
    http_client = HttpxClient(trust_env=False)
    options = SyncClientOptions(httpx_client=http_client)
    return create_client(url, key, options=options)


# Expose a default instance for tests or single-threaded use cases
supabase: Client = create_db_client()

# Thread-local storage to prevent httpx.Client from being shared across FastAPI worker threads.
# Sharing a single httpx.Client concurrently causes "Server disconnected" and RemoteProtocolError.
_local = threading.local()


def get_db() -> Client:
    """
    Factory function that returns a thread-local Supabase client.
    Use this instead of importing `supabase` directly to avoid
    concurrent connection pool errors.
    """
    if not hasattr(_local, "client"):
        _local.client = create_db_client()
    return _local.client