Spaces:
Running
Running
File size: 8,163 Bytes
3222a21 9e56290 1cff830 bfe88a9 9e56290 bfe88a9 9e56290 4809b28 9e56290 bfe88a9 9e56290 bfe88a9 4809b28 bfe88a9 9e56290 3222a21 9e56290 4809b28 9e56290 bfe88a9 9e56290 1cff830 9e56290 bfe88a9 9e56290 1cff830 9e56290 |
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 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# # app/database.py
# import os
# from databases import Database
# from dotenv import load_dotenv
# # --- Keep only these SQLAlchemy imports ---
# from sqlalchemy import MetaData, Table, Column, Integer, String
# import logging
# from urllib.parse import urlparse, urlunparse, parse_qs, urlencode
# load_dotenv()
# logger = logging.getLogger(__name__)
# # --- Database URL Configuration ---
# DEFAULT_DB_PATH = "/tmp/app.db" # Store DB in the temporary directory
# raw_db_url = os.getenv("DATABASE_URL", f"sqlite+aiosqlite:///{DEFAULT_DB_PATH}")
# final_database_url = raw_db_url
# if raw_db_url.startswith("sqlite+aiosqlite"):
# parsed_url = urlparse(raw_db_url)
# query_params = parse_qs(parsed_url.query)
# if 'check_same_thread' not in query_params:
# query_params['check_same_thread'] = ['False']
# new_query = urlencode(query_params, doseq=True)
# final_database_url = urlunparse(parsed_url._replace(query=new_query))
# logger.info(f"Using final async DB URL: {final_database_url}")
# else:
# logger.info(f"Using non-SQLite async DB URL: {final_database_url}")
# # --- Async Database Instance ---
# database = Database(final_database_url)
# # --- Metadata and Table Definition (Still needed for DDL generation) ---
# metadata = MetaData()
# users = Table(
# "users",
# metadata,
# Column("id", Integer, primary_key=True),
# Column("email", String, unique=True, index=True, nullable=False),
# Column("hashed_password", String, nullable=False),
# )
# # --- REMOVE ALL SYNCHRONOUS ENGINE AND TABLE CREATION LOGIC ---
# # --- Keep and refine Async connect/disconnect functions ---
# async def connect_db():
# """Connects to the database, ensuring the parent directory exists."""
# try:
# # Ensure the directory exists just before connecting
# db_file_path = final_database_url.split("sqlite:///")[-1].split("?")[0]
# db_dir = os.path.dirname(db_file_path)
# if db_dir: # Only proceed if a directory path was found
# if not os.path.exists(db_dir):
# logger.info(f"Database directory {db_dir} does not exist. Attempting creation...")
# try:
# os.makedirs(db_dir, exist_ok=True)
# logger.info(f"Created database directory {db_dir}.")
# except Exception as mkdir_err:
# # Log error but proceed, connection might still work if path is valid but dir creation failed weirdly
# logger.error(f"Failed to create directory {db_dir}: {mkdir_err}")
# # Check writability after ensuring existence attempt
# if os.path.exists(db_dir) and not os.access(db_dir, os.W_OK):
# logger.error(f"CRITICAL: Directory {db_dir} exists but is not writable!")
# elif not os.path.exists(db_dir):
# logger.error(f"CRITICAL: Directory {db_dir} does not exist and could not be created!")
# # Now attempt connection
# await database.connect()
# logger.info(f"Database connection established (async): {final_database_url}")
# # Table creation will happen in main.py lifespan event using this connection
# except Exception as e:
# logger.exception(f"Failed to establish async database connection: {e}")
# raise # Reraise critical error during startup
# async def disconnect_db():
# """Disconnects from the database if connected."""
# try:
# if database.is_connected:
# await database.disconnect()
# logger.info("Database connection closed (async).")
# else:
# logger.info("Database already disconnected (async).")
# except Exception as e:
# logger.exception(f"Error closing async database connection: {e}")
# app/database.py
import os
from databases import Database
from dotenv import load_dotenv
# --- Keep only these SQLAlchemy imports ---
# MetaData and Table are needed for defining the table structure
# which is used by crud.py and for DDL generation in main.py
from sqlalchemy import MetaData, Table, Column, Integer, String
import logging
from urllib.parse import urlparse, urlunparse, parse_qs, urlencode
# Load environment variables from .env file (if it exists)
load_dotenv()
logger = logging.getLogger(__name__)
# --- Database URL Configuration ---
# Use /tmp directory for the SQLite file as it's generally writable in containers
DEFAULT_DB_PATH = "/tmp/app.db"
# Get the URL from environment or use the default /tmp path
raw_db_url = os.getenv("DATABASE_URL", f"sqlite+aiosqlite:///{DEFAULT_DB_PATH}")
final_database_url = raw_db_url
# Ensure 'check_same_thread=False' is in the URL query string for SQLite async connection
if raw_db_url.startswith("sqlite+aiosqlite"):
parsed_url = urlparse(raw_db_url)
query_params = parse_qs(parsed_url.query)
if 'check_same_thread' not in query_params:
query_params['check_same_thread'] = ['False'] # Value needs to be a list for urlencode
new_query = urlencode(query_params, doseq=True)
# Rebuild the URL using _replace method of the named tuple
final_database_url = urlunparse(parsed_url._replace(query=new_query))
logger.info(f"Using final async DB URL: {final_database_url}")
else:
logger.info(f"Using non-SQLite async DB URL: {final_database_url}")
# --- Async Database Instance ---
# This 'database' object will be used by crud.py and main.py lifespan
database = Database(final_database_url)
# --- Metadata and Table Definition ---
# These definitions are needed by:
# 1. crud.py to construct queries (e.g., users.select())
# 2. main.py (lifespan) to generate the CREATE TABLE statement
metadata = MetaData()
users = Table(
"users",
metadata,
Column("id", Integer, primary_key=True),
Column("email", String, unique=True, index=True, nullable=False),
Column("hashed_password", String, nullable=False),
)
# --- Async connect/disconnect functions ---
# Called by the FastAPI lifespan event handler in main.py
async def connect_db():
"""Connects to the database defined by 'final_database_url'."""
try:
# Optional: Check/create directory if using file-based DB like SQLite
if final_database_url.startswith("sqlite"):
db_file_path = final_database_url.split("sqlite:///")[-1].split("?")[0]
db_dir = os.path.dirname(db_file_path)
if db_dir:
if not os.path.exists(db_dir):
logger.info(f"DB directory '{db_dir}' missing, creating.")
try:
os.makedirs(db_dir, exist_ok=True)
except Exception as mkdir_err:
logger.error(f"Failed to create DB directory '{db_dir}': {mkdir_err}")
# Check writability (best effort)
if os.path.exists(db_dir) and not os.access(db_dir, os.W_OK):
logger.error(f"CRITICAL: DB directory '{db_dir}' exists but is not writable!")
elif not os.path.exists(db_dir):
logger.error(f"CRITICAL: DB directory '{db_dir}' does not exist and could not be created!")
# Connect using the 'databases' library instance
if not database.is_connected:
await database.connect()
logger.info(f"Database connection established: {final_database_url}")
else:
logger.info("Database connection already established.")
# Note: Table creation happens in main.py lifespan after connection
except Exception as e:
logger.exception(f"FATAL: Failed to establish async database connection: {e}")
raise # Stop application startup if DB connection fails
async def disconnect_db():
"""Disconnects from the database if connected."""
try:
if database.is_connected:
await database.disconnect()
logger.info("Database connection closed.")
else:
logger.info("Database already disconnected.")
except Exception as e:
logger.exception(f"Error closing database connection: {e}") |