feat: Add SQLite fallback for database - enables deployment without PostgreSQL

#1
Files changed (1) hide show
  1. 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
- # Create async engine with production-ready pool settings
56
- engine = create_async_engine(
57
- settings.database_url,
58
- echo=settings.debug,
59
- future=True,
60
- pool_size=20,
61
- max_overflow=10,
62
- pool_recycle=3600,
63
- pool_pre_ping=True,
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 (for development only)."""
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)