Spaces:
Running
Running
File size: 5,820 Bytes
1cff830 bfe88a9 24dc858 4809b28 292f6be bfe88a9 4809b28 bfe88a9 4809b28 465ba2d 4809b28 bfe88a9 24dc858 4809b28 292f6be 4809b28 bfe88a9 4809b28 bfe88a9 292f6be 4809b28 1cff830 292f6be bfe88a9 4809b28 465ba2d 4809b28 465ba2d 24dc858 1cff830 292f6be 465ba2d 24dc858 465ba2d 292f6be 1cff830 292f6be 4809b28 292f6be 1cff830 24dc858 bfe88a9 24dc858 bfe88a9 24dc858 1cff830 24dc858 1cff830 24dc858 1cff830 24dc858 1cff830 24dc858 bfe88a9 1cff830 4809b28 1cff830 292f6be bfe88a9 1cff830 |
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 |
# app/database.py
import os
from databases import Database
from dotenv import load_dotenv
# --- ADD THIS IMPORT ---
from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, text, exc as sqlalchemy_exc
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}")
# --- URL Parsing and Async Database setup (remains the same) ---
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}")
database = Database(final_database_url)
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),
)
# --- Synchronous Engine for Initial Table Creation ---
sync_db_url = final_database_url.replace("+aiosqlite", "")
logger.info(f"Using synchronous DB URL for initial check/create: {sync_db_url}")
engine = create_engine(sync_db_url)
# --- Directory and Table Creation Logic ---
db_file_path = ""
if sync_db_url.startswith("sqlite"):
path_part = sync_db_url.split("sqlite:///")[-1].split("?")[0]
db_file_path = path_part # Should be /tmp/app.db
if db_file_path:
db_dir = os.path.dirname(db_file_path) # Should be /tmp
logger.info(f"Checking database directory: {db_dir}")
try:
if not os.path.exists(db_dir):
logger.error(f"CRITICAL: Directory {db_dir} does not exist!")
elif not os.access(db_dir, os.W_OK):
logger.error(f"CRITICAL: Directory {db_dir} is not writable! Cannot create database.")
else:
logger.info(f"Database directory {db_dir} appears writable.")
except OSError as e:
logger.error(f"Error accessing database directory {db_dir}: {e}")
except Exception as e:
logger.error(f"Unexpected error checking directory {db_dir}: {e}")
# --- Refined Synchronous Table Check/Creation ---
try:
logger.info("Attempting sync connection to check/create table...")
with engine.connect() as connection:
logger.info("Sync engine connection successful.")
try:
# Check if table exists
connection.execute(text("SELECT 1 FROM users LIMIT 1"))
logger.info("Users table already exists (checked via sync connection).")
# --- Catch specific SQLAlchemy error ---
except sqlalchemy_exc.OperationalError as table_missing_err:
# Check if the error message specifically indicates "no such table"
if "no such table" in str(table_missing_err).lower():
logger.warning("Users table not found (expected), attempting creation...")
try:
# Begin a transaction explicitly (optional, connect() usually does)
# with connection.begin(): # Alternative way to manage transaction
# Use the connection object for create_all
metadata.create_all(bind=connection) # <-- Bind to connection
# --- Explicitly commit ---
connection.commit()
logger.info("Users table creation attempted and committed via sync connection.")
# --- Verify creation immediately ---
try:
connection.execute(text("SELECT 1 FROM users LIMIT 1"))
logger.info("Users table successfully verified immediately after creation.")
except Exception as verify_err:
logger.error(f"Failed to verify table immediately after creation: {verify_err}")
except Exception as creation_err:
logger.exception(f"Error during table creation or commit: {creation_err}")
# Optionally rollback? connection.rollback()
else:
# Log other OperationalErrors during the check phase
logger.error(f"OperationalError during table check (but not 'no such table'): {table_missing_err}")
raise # Re-raise unexpected errors
except Exception as table_check_exc: # Catch other unexpected errors during check
logger.error(f"Unexpected error during table check: {type(table_check_exc).__name__}: {table_check_exc}")
raise # Re-raise unexpected errors
except Exception as e:
# Errors connecting, or unexpected errors during check/create phase
logger.exception(f"CRITICAL: Failed during sync connection or table setup: {e}")
# --- Async connect/disconnect functions (remain the same) ---
async def connect_db():
try:
await database.connect()
logger.info(f"Database connection established (async): {final_database_url}")
except Exception as e:
logger.exception(f"Failed to establish async database connection: {e}")
raise
async def disconnect_db():
try:
await database.disconnect()
logger.info("Database connection closed (async).")
except Exception as e:
logger.exception(f"Error closing async database connection: {e}") |