Skip to content

Commit 714e118

Browse files
committed
feat: Add database connection and session management utilities
1 parent f75bea8 commit 714e118

File tree

2 files changed

+138
-0
lines changed

2 files changed

+138
-0
lines changed

app/db/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""Database connection and session management."""
2+
3+
from app.db.session import get_db, init_db, close_db
4+
5+
__all__ = ["get_db", "init_db", "close_db"]

app/db/session.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
"""Database session management."""
2+
3+
from typing import AsyncGenerator
4+
5+
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
6+
from sqlalchemy.pool import NullPool, QueuePool
7+
8+
from app.core.config import get_settings
9+
from app.core.logging import get_logger
10+
from app.models.base import Base
11+
12+
logger = get_logger(__name__)
13+
14+
# Global engine and session factory
15+
engine = None
16+
async_session_factory = None
17+
18+
19+
def init_db() -> None:
20+
"""
21+
Initialize database engine and session factory.
22+
23+
Creates async engine with connection pooling and configures session factory.
24+
"""
25+
global engine, async_session_factory
26+
27+
settings = get_settings()
28+
29+
# Configure connection pool based on environment
30+
if settings.is_production:
31+
# Production: Use connection pooling
32+
engine = create_async_engine(
33+
settings.database_url,
34+
echo=settings.debug,
35+
poolclass=QueuePool,
36+
pool_size=settings.database_pool_size,
37+
max_overflow=settings.database_max_overflow,
38+
pool_timeout=settings.database_pool_timeout,
39+
pool_recycle=settings.database_pool_recycle,
40+
pool_pre_ping=True, # Enable connection health checks
41+
connect_args={
42+
"server_settings": {"application_name": settings.app_name},
43+
"timeout": 30,
44+
},
45+
)
46+
logger.info(
47+
"database_initialized",
48+
pool_size=settings.database_pool_size,
49+
max_overflow=settings.database_max_overflow,
50+
environment=settings.environment,
51+
)
52+
else:
53+
# Development: No pooling for easier debugging
54+
engine = create_async_engine(
55+
settings.database_url,
56+
echo=settings.debug,
57+
poolclass=NullPool, # NullPool doesn't accept pool_size, max_overflow, etc.
58+
pool_pre_ping=True,
59+
connect_args={
60+
"server_settings": {"application_name": settings.app_name},
61+
"timeout": 30,
62+
},
63+
)
64+
logger.info(
65+
"database_initialized",
66+
pool_class="NullPool",
67+
environment=settings.environment,
68+
)
69+
70+
71+
# Create session factory
72+
async_session_factory = async_sessionmaker(
73+
engine,
74+
class_=AsyncSession,
75+
expire_on_commit=False,
76+
autocommit=False,
77+
autoflush=False,
78+
)
79+
80+
81+
async def get_db() -> AsyncGenerator[AsyncSession, None]:
82+
"""
83+
Dependency to get database session.
84+
85+
Yields:
86+
AsyncSession instance
87+
88+
Usage:
89+
@app.get("/endpoint")
90+
async def endpoint(db: AsyncSession = Depends(get_db)):
91+
...
92+
"""
93+
if async_session_factory is None:
94+
raise RuntimeError("Database not initialized. Call init_db() first.")
95+
96+
async with async_session_factory() as session:
97+
try:
98+
yield session
99+
await session.commit()
100+
except Exception:
101+
await session.rollback()
102+
raise
103+
finally:
104+
await session.close()
105+
106+
107+
async def close_db() -> None:
108+
"""Close database connections and dispose of engine."""
109+
global engine
110+
111+
if engine is not None:
112+
await engine.dispose()
113+
logger.info("database_connections_closed")
114+
115+
116+
async def create_tables() -> None:
117+
"""Create all database tables. Use only in development or for testing."""
118+
if engine is None:
119+
raise RuntimeError("Database not initialized. Call init_db() first.")
120+
121+
async with engine.begin() as conn:
122+
await conn.run_sync(Base.metadata.create_all)
123+
logger.info("database_tables_created")
124+
125+
126+
async def drop_tables() -> None:
127+
"""Drop all database tables. Use only in development or for testing."""
128+
if engine is None:
129+
raise RuntimeError("Database not initialized. Call init_db() first.")
130+
131+
async with engine.begin() as conn:
132+
await conn.run_sync(Base.metadata.drop_all)
133+
logger.warning("database_tables_dropped")

0 commit comments

Comments
 (0)