feat: Add SQLite fallback for database - enables deployment without PostgreSQL
#1
by MouleeswaranM - opened
- brain/app/database.py +35 -14
brain/app/database.py
CHANGED
|
@@ -1,12 +1,13 @@
|
|
| 1 |
"""
|
| 2 |
Database connection and session management.
|
| 3 |
-
Uses async SQLAlchemy with asyncpg driver
|
|
|
|
| 4 |
"""
|
| 5 |
|
| 6 |
import uuid
|
|
|
|
| 7 |
|
| 8 |
from sqlalchemy import CHAR, text
|
| 9 |
-
from sqlalchemy.dialects.postgresql import UUID as PG_UUID
|
| 10 |
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
|
| 11 |
from sqlalchemy.orm import DeclarativeBase
|
| 12 |
from sqlalchemy.types import TypeDecorator
|
|
@@ -18,13 +19,13 @@ class GUID(TypeDecorator):
|
|
| 18 |
"""
|
| 19 |
Platform-independent GUID type.
|
| 20 |
Uses PostgreSQL's UUID type when available, otherwise uses CHAR(32) for SQLite.
|
| 21 |
-
Stores as stringified hex values in SQLite.
|
| 22 |
"""
|
| 23 |
impl = CHAR
|
| 24 |
cache_ok = True
|
| 25 |
|
| 26 |
def load_dialect_impl(self, dialect):
|
| 27 |
if dialect.name == 'postgresql':
|
|
|
|
| 28 |
return dialect.type_descriptor(PG_UUID(as_uuid=True))
|
| 29 |
else:
|
| 30 |
return dialect.type_descriptor(CHAR(32))
|
|
@@ -52,16 +53,36 @@ class GUID(TypeDecorator):
|
|
| 52 |
|
| 53 |
settings = get_settings()
|
| 54 |
|
| 55 |
-
#
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
# Session factory
|
| 67 |
async_session_maker = async_sessionmaker(
|
|
@@ -103,6 +124,6 @@ async def check_db_health() -> bool:
|
|
| 103 |
|
| 104 |
|
| 105 |
async def init_db() -> None:
|
| 106 |
-
"""Initialize database tables
|
| 107 |
async with engine.begin() as conn:
|
| 108 |
await conn.run_sync(Base.metadata.create_all)
|
|
|
|
| 1 |
"""
|
| 2 |
Database connection and session management.
|
| 3 |
+
Uses async SQLAlchemy with asyncpg driver for PostgreSQL,
|
| 4 |
+
or aiosqlite for local/free-tier deployment.
|
| 5 |
"""
|
| 6 |
|
| 7 |
import uuid
|
| 8 |
+
import os
|
| 9 |
|
| 10 |
from sqlalchemy import CHAR, text
|
|
|
|
| 11 |
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
|
| 12 |
from sqlalchemy.orm import DeclarativeBase
|
| 13 |
from sqlalchemy.types import TypeDecorator
|
|
|
|
| 19 |
"""
|
| 20 |
Platform-independent GUID type.
|
| 21 |
Uses PostgreSQL's UUID type when available, otherwise uses CHAR(32) for SQLite.
|
|
|
|
| 22 |
"""
|
| 23 |
impl = CHAR
|
| 24 |
cache_ok = True
|
| 25 |
|
| 26 |
def load_dialect_impl(self, dialect):
|
| 27 |
if dialect.name == 'postgresql':
|
| 28 |
+
from sqlalchemy.dialects.postgresql import UUID as PG_UUID
|
| 29 |
return dialect.type_descriptor(PG_UUID(as_uuid=True))
|
| 30 |
else:
|
| 31 |
return dialect.type_descriptor(CHAR(32))
|
|
|
|
| 53 |
|
| 54 |
settings = get_settings()
|
| 55 |
|
| 56 |
+
# Determine database URL - fallback to SQLite if no PostgreSQL configured
|
| 57 |
+
_db_url = settings.database_url
|
| 58 |
+
|
| 59 |
+
if not _db_url or _db_url == "":
|
| 60 |
+
# Use SQLite as fallback (works without external DB)
|
| 61 |
+
_db_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data")
|
| 62 |
+
os.makedirs(_db_dir, exist_ok=True)
|
| 63 |
+
_db_url = f"sqlite+aiosqlite:///{_db_dir}/fairrelay.db"
|
| 64 |
+
_is_sqlite = True
|
| 65 |
+
else:
|
| 66 |
+
_is_sqlite = False
|
| 67 |
+
|
| 68 |
+
# Create async engine with appropriate settings
|
| 69 |
+
if _is_sqlite:
|
| 70 |
+
engine = create_async_engine(
|
| 71 |
+
_db_url,
|
| 72 |
+
echo=settings.debug,
|
| 73 |
+
future=True,
|
| 74 |
+
connect_args={"check_same_thread": False},
|
| 75 |
+
)
|
| 76 |
+
else:
|
| 77 |
+
engine = create_async_engine(
|
| 78 |
+
_db_url,
|
| 79 |
+
echo=settings.debug,
|
| 80 |
+
future=True,
|
| 81 |
+
pool_size=20,
|
| 82 |
+
max_overflow=10,
|
| 83 |
+
pool_recycle=3600,
|
| 84 |
+
pool_pre_ping=True,
|
| 85 |
+
)
|
| 86 |
|
| 87 |
# Session factory
|
| 88 |
async_session_maker = async_sessionmaker(
|
|
|
|
| 124 |
|
| 125 |
|
| 126 |
async def init_db() -> None:
|
| 127 |
+
"""Initialize database tables."""
|
| 128 |
async with engine.begin() as conn:
|
| 129 |
await conn.run_sync(Base.metadata.create_all)
|