arcshukla's picture
real db
7a1f075
Raw
History Blame Contribute Delete
7.08 kB
"""
Standalone DB connectivity test — run with:
uv run python test_db.py
Tests each layer independently so the exact failure point is visible.
Loads DATABASE_URL from .env automatically.
"""
import asyncio
import os
import socket
import sys
from urllib.parse import urlparse
from dotenv import load_dotenv
load_dotenv(override=True)
# asyncpg requires SelectorEventLoop on Windows — ProactorEventLoop (default in
# Python 3.8+ on Windows) causes asyncpg to hang silently on connect.
if sys.platform == "win32":
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
RAW_URL = os.environ.get("DATABASE_URL", "")
# ── 0. Show what URL we are working with ──────────────────────────────────────
print("\n── Step 0: URL check ──────────────────────────────────────────────────")
if not RAW_URL:
print("FAIL DATABASE_URL is not set in .env")
sys.exit(1)
parsed = urlparse(RAW_URL)
masked = RAW_URL.replace(parsed.password or "", "***") if parsed.password else RAW_URL
print(f" Raw scheme : {parsed.scheme}")
print(f" Host : {parsed.hostname}")
print(f" Port : {parsed.port or 5432}")
print(f" Database : {parsed.path.lstrip('/')}")
print(f" Full URL : {masked}")
# asyncpg needs postgresql+asyncpg:// — detect and auto-fix for this test
ASYNCPG_URL = RAW_URL
if parsed.scheme == "postgresql" or parsed.scheme == "postgres":
ASYNCPG_URL = RAW_URL.replace(parsed.scheme + "://", "postgresql+asyncpg://", 1)
print(f"\n NOTE: scheme was '{parsed.scheme}://' — using 'postgresql+asyncpg://' for asyncpg test")
print(f" → Fix your .env: change DATABASE_URL to start with 'postgresql+asyncpg://'")
# ── 1. DNS resolution ─────────────────────────────────────────────────────────
print("\n── Step 1: DNS resolution ─────────────────────────────────────────────")
host = parsed.hostname
try:
ip = socket.gethostbyname(host)
print(f" OK {host}{ip}")
except socket.gaierror as e:
print(f" FAIL Cannot resolve {host}: {e}")
sys.exit(1)
# ── 2. TCP reachability ───────────────────────────────────────────────────────
print("\n── Step 2: TCP connect (port 5432) ────────────────────────────────────")
port = parsed.port or 5432
try:
sock = socket.create_connection((host, port), timeout=10)
sock.close()
print(f" OK TCP {host}:{port} reachable")
except (socket.timeout, ConnectionRefusedError, OSError) as e:
print(f" FAIL Cannot TCP-connect to {host}:{port}: {e}")
print(" Common cause: corporate firewall blocks port 5432.")
print(" Try Neon's pooler port 6543 or use a VPN/hotspot.")
sys.exit(1)
# ── 3a. TLS handshake (pure Python ssl — no asyncpg, no asyncio) ──────────────
print("\n── Step 3a: TLS handshake (no asyncpg) ────────────────────────────────")
import ssl as ssl_mod
import threading
def _tls_test(result):
try:
ctx = ssl_mod.SSLContext(ssl_mod.PROTOCOL_TLS_CLIENT)
ctx.check_hostname = False
ctx.verify_mode = ssl_mod.CERT_NONE
raw = socket.create_connection((parsed.hostname, parsed.port or 5432), timeout=10)
tls = ctx.wrap_socket(raw, server_hostname=parsed.hostname)
result["ok"] = True
result["ver"] = tls.version()
tls.close()
except Exception as e:
result["err"] = f"{type(e).__name__}: {e}"
_r = {}
_t = threading.Thread(target=_tls_test, args=(_r,), daemon=True)
_t.start()
_t.join(timeout=12)
if _t.is_alive():
print(" FAIL TLS timed out — SSL port blocked or Neon not responding to TLS")
sys.exit(1)
elif "err" in _r:
print(f" FAIL TLS error: {_r['err']}")
sys.exit(1)
else:
print(f" OK TLS handshake succeeded, protocol={_r['ver']}")
# ── 3b. psycopg (psycopg3) sync connection — no asyncio, no Windows issues ────
print("\n── Step 3b: psycopg connection ────────────────────────────────────────")
try:
import psycopg
except ImportError:
print(" FAIL psycopg not installed — run: uv add 'psycopg[binary]'")
sys.exit(1)
_host = parsed.hostname
_port = parsed.port or 5432
_user = parsed.username
_passw = parsed.password
_dbname = parsed.path.lstrip("/")
def _psycopg_thread(result):
try:
conn = psycopg.connect(
host=_host, port=_port, user=_user,
password=_passw, dbname=_dbname,
sslmode="require", connect_timeout=30,
)
row = conn.execute("SELECT version()").fetchone()
conn.close()
result["version"] = row[0]
except Exception as e:
result["err"] = f"{type(e).__name__}: {e}"
import time as _time
_r2 = {}
_t2 = threading.Thread(target=_psycopg_thread, args=(_r2,), daemon=True)
_t2.start()
print(" ... waiting", end="", flush=True)
for _i in range(35):
if not _t2.is_alive():
break
_time.sleep(1)
print(f" {_i+1}s", end="", flush=True)
print()
if _t2.is_alive():
print(" FAIL timed out after 35s — check Neon compute status")
sys.exit(1)
elif "err" in _r2:
print(f" FAIL {_r2['err']}")
sys.exit(1)
else:
print(f" OK {_r2['version'][:80]}")
# ── 4. SQLAlchemy async engine (psycopg, main-thread asyncio) ─────────────────
print("\n── Step 4: SQLAlchemy async engine ────────────────────────────────────")
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy import text as sa_text
sa_url = (f"postgresql+psycopg://{parsed.username}:{parsed.password}"
f"@{parsed.hostname}:{parsed.port or 5432}{parsed.path}")
async def _sqlalchemy_async():
eng = create_async_engine(
sa_url,
connect_args={"sslmode": "require", "connect_timeout": 30},
pool_pre_ping=True,
)
async with eng.connect() as conn:
row = (await conn.execute(sa_text("SELECT version()"))).fetchone()
await eng.dispose()
return row[0]
print(" ... connecting (connect_timeout=30s)", flush=True)
try:
val = asyncio.run(_sqlalchemy_async())
print(f" OK {val[:80]}")
except Exception as e:
print(f" FAIL {type(e).__name__}: {e}")
sys.exit(1)
print("\n══ All checks passed — Neon database is reachable and ready ══════════\n")