fras-backend / database.py
pranaykumar2005's picture
fix: pass options endpoint parameter for direct IPv4 Neon connections
02b3655
Raw
History Blame Contribute Delete
2.67 kB
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker, declarative_base
import os
import socket
import urllib.parse
import ssl
from dotenv import load_dotenv
load_dotenv()
# Changed to PostgreSQL connection for pgvector
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql+asyncpg://fras_user:fras_password@127.0.0.1:5433/fras_db")
connect_args = {}
# Increase connection establishment timeout to 30 seconds to handle cold starts
connect_args["timeout"] = 30
if DATABASE_URL:
# 1. Clean query parameters (strip sslmode=require)
if "?" in DATABASE_URL:
base_url, query = DATABASE_URL.split("?", 1)
params = [p for p in query.split("&") if not p.startswith("sslmode=")]
if params:
DATABASE_URL = base_url + "?" + "&".join(params)
else:
DATABASE_URL = base_url
# 2. Resolve hostname to IPv4 to bypass IPv6 routing issues on local networks
parsed = urllib.parse.urlparse(DATABASE_URL)
hostname = parsed.hostname
if hostname and not hostname.replace('.', '').isdigit():
try:
ip_addresses = socket.getaddrinfo(hostname, parsed.port or 5432, family=socket.AF_INET)
if ip_addresses:
ipv4 = ip_addresses[0][4][0]
# Replace hostname in netloc (handles username:password@host:port)
netloc = parsed.netloc.replace(hostname, ipv4)
DATABASE_URL = parsed._replace(netloc=netloc).geturl()
# Pass Neon endpoint ID via server settings options to route direct IP connections
if "neon.tech" in hostname or "neon.tech" in DATABASE_URL:
endpoint_id = hostname.split('.')[0]
connect_args["server_settings"] = {
"options": f"endpoint={endpoint_id}"
}
except Exception:
pass
# 3. Enable SSL and bypass hostname validation if using Neon
if "neon.tech" in os.getenv("DATABASE_URL", "") or "neon.tech" in DATABASE_URL or (hostname and "neon.tech" in hostname):
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
connect_args["ssl"] = ssl_context
engine = create_async_engine(
DATABASE_URL,
echo=False,
connect_args=connect_args,
pool_pre_ping=True,
pool_recycle=300
)
AsyncSessionLocal = sessionmaker(
engine, class_=AsyncSession, expire_on_commit=False
)
Base = declarative_base()
async def get_db():
async with AsyncSessionLocal() as session:
yield session