-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdatabase.py
More file actions
130 lines (108 loc) · 3.79 KB
/
database.py
File metadata and controls
130 lines (108 loc) · 3.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
"""
Database configuration and models for the ELI5 application.
Handles user authentication and history management.
"""
import os
from datetime import datetime
from logging import Logger
from typing import Any, Generator, List
from dotenv import load_dotenv
from sqlalchemy import (
Column,
DateTime,
Engine,
ForeignKey,
Integer,
String,
Text,
create_engine,
)
from sqlalchemy.orm import Mapped, declarative_base, relationship, sessionmaker
from sqlalchemy.orm.session import Session
from sqlalchemy.sql import func
load_dotenv()
# Database URL configuration
# For development: Use SQLite (file-based, no server needed)
# For production: Set DATABASE_URL environment variable to PostgreSQL URL
DATABASE_URL: str = os.getenv("DATABASE_URL", "sqlite:///./eli5_dev.db")
# Create engine with appropriate settings for SQLite
if DATABASE_URL.startswith("sqlite"):
# SQLite-specific configuration
engine: Engine = create_engine(
DATABASE_URL,
connect_args={"check_same_thread": False}, # Needed for SQLite with FastAPI
)
else:
# PostgreSQL or other database configuration
engine = create_engine(DATABASE_URL)
# Create SessionLocal class
SessionLocal: sessionmaker[Session] = sessionmaker(
autocommit=False, autoflush=False, bind=engine
)
# Create Base class
Base: Any = declarative_base()
class User(Base):
"""
User model for authentication and profile management.
Stores user credentials and basic information.
"""
__tablename__: str = "users"
id: Column[int] = Column(Integer, primary_key=True, index=True)
email: Column[str] = Column(String, unique=True, index=True, nullable=False)
username: Column[str] = Column(String, unique=True, index=True, nullable=False)
hashed_password: Column[str] = Column(String, nullable=False)
created_at: Column[datetime] = Column(
DateTime(timezone=True), server_default=func.now()
)
updated_at: Column[datetime] = Column(DateTime(timezone=True), onupdate=func.now())
# Relationship to history entries
history_entries: Mapped[List["HistoryEntry"]] = relationship(
"HistoryEntry",
back_populates="user",
cascade="all, delete-orphan",
passive_deletes=True,
)
class HistoryEntry(Base):
"""
History model to store user's past concept explanations.
Links to the User model to provide personalized history.
"""
__tablename__: str = "history_entries"
id: Column[int] = Column(Integer, primary_key=True, index=True)
user_id: Column[int] = Column(
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False
)
concept: Column[str] = Column(String, nullable=False)
explanation: Column[str] = Column(Text, nullable=False)
created_at: Column[datetime] = Column(
DateTime(timezone=True), server_default=func.now()
)
# Relationship to user
user: Mapped["User"] = relationship("User", back_populates="history_entries")
def get_db() -> Generator[Session, Any, None]:
"""
Dependency to get database session.
Ensures proper session management and cleanup.
"""
db: Session = SessionLocal()
try:
yield db
finally:
db.close()
def create_tables() -> None:
"""
Create all database tables.
Should be called when initializing the application.
"""
import logging
logger: Logger = logging.getLogger(__name__)
try:
Base.metadata.create_all(bind=engine)
logger.info("Database tables created successfully.")
except Exception as e:
logger.error(f"Failed to create database tables: {str(e)}")
logger.warning(
"Database may not be available. Please check your DATABASE_URL configuration."
)
# Don't re-raise the exception to allow the app to start without DB
# raise