ifieryarrows commited on
Commit
86c0ef4
·
verified ·
1 Parent(s): f2045e2

Update app/db.py

Browse files
Files changed (1) hide show
  1. app/db.py +156 -151
app/db.py CHANGED
@@ -1,151 +1,156 @@
1
- """
2
- Database connection and session management.
3
- SQLite with WAL mode for concurrent read/write support.
4
- """
5
-
6
- import logging
7
- from contextlib import contextmanager
8
- from typing import Generator
9
-
10
- from sqlalchemy import create_engine, event, text
11
- from sqlalchemy.orm import sessionmaker, Session, declarative_base
12
-
13
- from app.settings import get_settings
14
-
15
- logger = logging.getLogger(__name__)
16
-
17
- # SQLAlchemy declarative base
18
- Base = declarative_base()
19
-
20
- # Global engine and session factory (lazy initialized)
21
- _engine = None
22
- _SessionLocal = None
23
-
24
-
25
- def get_engine():
26
- """Get or create the database engine."""
27
- global _engine
28
-
29
- if _engine is None:
30
- settings = get_settings()
31
- database_url = settings.database_url
32
-
33
- # Determine if SQLite
34
- is_sqlite = database_url.startswith("sqlite")
35
-
36
- # Engine configuration
37
- engine_kwargs = {
38
- "echo": settings.log_level == "DEBUG",
39
- "pool_pre_ping": True,
40
- }
41
-
42
- if is_sqlite:
43
- # SQLite-specific settings
44
- engine_kwargs["connect_args"] = {
45
- "check_same_thread": False,
46
- "timeout": 30,
47
- }
48
-
49
- _engine = create_engine(database_url, **engine_kwargs)
50
-
51
- # SQLite WAL mode and pragmas
52
- if is_sqlite:
53
- @event.listens_for(_engine, "connect")
54
- def set_sqlite_pragma(dbapi_connection, connection_record):
55
- cursor = dbapi_connection.cursor()
56
- # WAL mode for concurrent reads
57
- cursor.execute("PRAGMA journal_mode=WAL")
58
- # Busy timeout (ms) - wait for locks instead of immediate failure
59
- cursor.execute("PRAGMA busy_timeout=5000")
60
- # Synchronous mode - balance between safety and speed
61
- cursor.execute("PRAGMA synchronous=NORMAL")
62
- # Foreign keys enforcement
63
- cursor.execute("PRAGMA foreign_keys=ON")
64
- # Memory-mapped I/O (faster reads)
65
- cursor.execute("PRAGMA mmap_size=268435456") # 256MB
66
- cursor.close()
67
-
68
- logger.info("SQLite configured with WAL mode and performance pragmas")
69
-
70
- logger.info(f"Database engine created: {database_url.split('?')[0]}")
71
-
72
- return _engine
73
-
74
-
75
- def get_session_factory() -> sessionmaker:
76
- """Get or create the session factory."""
77
- global _SessionLocal
78
-
79
- if _SessionLocal is None:
80
- engine = get_engine()
81
- _SessionLocal = sessionmaker(
82
- autocommit=False,
83
- autoflush=False,
84
- bind=engine
85
- )
86
-
87
- return _SessionLocal
88
-
89
-
90
- def SessionLocal() -> Session:
91
- """Create a new database session."""
92
- factory = get_session_factory()
93
- return factory()
94
-
95
-
96
- @contextmanager
97
- def get_db() -> Generator[Session, None, None]:
98
- """
99
- Context manager for database sessions.
100
- Automatically commits on success, rolls back on exception.
101
- """
102
- session = SessionLocal()
103
- try:
104
- yield session
105
- session.commit()
106
- except Exception:
107
- session.rollback()
108
- raise
109
- finally:
110
- session.close()
111
-
112
-
113
- def init_db():
114
- """
115
- Initialize the database - create all tables.
116
- Safe to call multiple times (uses CREATE IF NOT EXISTS).
117
- """
118
- # Import models to register them with Base
119
- from app import models # noqa: F401
120
-
121
- engine = get_engine()
122
- Base.metadata.create_all(bind=engine)
123
- logger.info("Database tables created/verified")
124
-
125
-
126
- def get_db_type() -> str:
127
- """Return the database type (sqlite, postgresql, etc.)."""
128
- settings = get_settings()
129
- url = settings.database_url
130
-
131
- if url.startswith("sqlite"):
132
- return "sqlite"
133
- elif url.startswith("postgresql"):
134
- return "postgresql"
135
- elif url.startswith("mysql"):
136
- return "mysql"
137
- else:
138
- return "unknown"
139
-
140
-
141
- def check_db_connection() -> bool:
142
- """Test database connectivity."""
143
- try:
144
- engine = get_engine()
145
- with engine.connect() as conn:
146
- conn.execute(text("SELECT 1"))
147
- return True
148
- except Exception as e:
149
- logger.error(f"Database connection check failed: {e}")
150
- return False
151
-
 
 
 
 
 
 
1
+ """
2
+ Database connection and session management.
3
+ SQLite with WAL mode for concurrent read/write support.
4
+ """
5
+
6
+ import logging
7
+ from contextlib import contextmanager
8
+ from typing import Generator
9
+
10
+ from sqlalchemy import create_engine, event, text
11
+ from sqlalchemy.orm import sessionmaker, Session, declarative_base
12
+
13
+ from app.settings import get_settings
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ # SQLAlchemy declarative base
18
+ Base = declarative_base()
19
+
20
+ # Global engine and session factory (lazy initialized)
21
+ _engine = None
22
+ _SessionLocal = None
23
+
24
+
25
+ def get_engine():
26
+ """Get or create the database engine."""
27
+ global _engine
28
+
29
+ if _engine is None:
30
+ settings = get_settings()
31
+ database_url = settings.database_url
32
+
33
+ # Determine if SQLite
34
+ is_sqlite = database_url.startswith("sqlite")
35
+
36
+ # Engine configuration
37
+ engine_kwargs = {
38
+ "echo": settings.log_level == "DEBUG",
39
+ "pool_pre_ping": True,
40
+ }
41
+
42
+ if is_sqlite:
43
+ # SQLite-specific settings
44
+ engine_kwargs["connect_args"] = {
45
+ "check_same_thread": False,
46
+ "timeout": 30,
47
+ }
48
+ else:
49
+ # PostgreSQL (Supabase) - connection pooling
50
+ engine_kwargs["pool_size"] = 5
51
+ engine_kwargs["max_overflow"] = 10
52
+ engine_kwargs["pool_timeout"] = 30
53
+
54
+ _engine = create_engine(database_url, **engine_kwargs)
55
+
56
+ # SQLite WAL mode and pragmas
57
+ if is_sqlite:
58
+ @event.listens_for(_engine, "connect")
59
+ def set_sqlite_pragma(dbapi_connection, connection_record):
60
+ cursor = dbapi_connection.cursor()
61
+ # WAL mode for concurrent reads
62
+ cursor.execute("PRAGMA journal_mode=WAL")
63
+ # Busy timeout (ms) - wait for locks instead of immediate failure
64
+ cursor.execute("PRAGMA busy_timeout=5000")
65
+ # Synchronous mode - balance between safety and speed
66
+ cursor.execute("PRAGMA synchronous=NORMAL")
67
+ # Foreign keys enforcement
68
+ cursor.execute("PRAGMA foreign_keys=ON")
69
+ # Memory-mapped I/O (faster reads)
70
+ cursor.execute("PRAGMA mmap_size=268435456") # 256MB
71
+ cursor.close()
72
+
73
+ logger.info("SQLite configured with WAL mode and performance pragmas")
74
+
75
+ logger.info(f"Database engine created: {database_url.split('?')[0]}")
76
+
77
+ return _engine
78
+
79
+
80
+ def get_session_factory() -> sessionmaker:
81
+ """Get or create the session factory."""
82
+ global _SessionLocal
83
+
84
+ if _SessionLocal is None:
85
+ engine = get_engine()
86
+ _SessionLocal = sessionmaker(
87
+ autocommit=False,
88
+ autoflush=False,
89
+ bind=engine
90
+ )
91
+
92
+ return _SessionLocal
93
+
94
+
95
+ def SessionLocal() -> Session:
96
+ """Create a new database session."""
97
+ factory = get_session_factory()
98
+ return factory()
99
+
100
+
101
+ @contextmanager
102
+ def get_db() -> Generator[Session, None, None]:
103
+ """
104
+ Context manager for database sessions.
105
+ Automatically commits on success, rolls back on exception.
106
+ """
107
+ session = SessionLocal()
108
+ try:
109
+ yield session
110
+ session.commit()
111
+ except Exception:
112
+ session.rollback()
113
+ raise
114
+ finally:
115
+ session.close()
116
+
117
+
118
+ def init_db():
119
+ """
120
+ Initialize the database - create all tables.
121
+ Safe to call multiple times (uses CREATE IF NOT EXISTS).
122
+ """
123
+ # Import models to register them with Base
124
+ from app import models # noqa: F401
125
+
126
+ engine = get_engine()
127
+ Base.metadata.create_all(bind=engine)
128
+ logger.info("Database tables created/verified")
129
+
130
+
131
+ def get_db_type() -> str:
132
+ """Return the database type (sqlite, postgresql, etc.)."""
133
+ settings = get_settings()
134
+ url = settings.database_url
135
+
136
+ if url.startswith("sqlite"):
137
+ return "sqlite"
138
+ elif url.startswith("postgresql"):
139
+ return "postgresql"
140
+ elif url.startswith("mysql"):
141
+ return "mysql"
142
+ else:
143
+ return "unknown"
144
+
145
+
146
+ def check_db_connection() -> bool:
147
+ """Test database connectivity."""
148
+ try:
149
+ engine = get_engine()
150
+ with engine.connect() as conn:
151
+ conn.execute(text("SELECT 1"))
152
+ return True
153
+ except Exception as e:
154
+ logger.error(f"Database connection check failed: {e}")
155
+ return False
156
+