Spaces:
Running
Running
Debug - Change DB
Browse files- Dockerfile +3 -2
- app/database.py +56 -20
Dockerfile
CHANGED
|
@@ -18,8 +18,9 @@ COPY ./app /code/app
|
|
| 18 |
# Make port 7860 available to the world outside this container (Gradio default)
|
| 19 |
EXPOSE 7860
|
| 20 |
|
| 21 |
-
#
|
| 22 |
-
#
|
|
|
|
| 23 |
|
| 24 |
# Command to run the application using uvicorn
|
| 25 |
# It will run the FastAPI app instance created in app/main.py
|
|
|
|
| 18 |
# Make port 7860 available to the world outside this container (Gradio default)
|
| 19 |
EXPOSE 7860
|
| 20 |
|
| 21 |
+
# Explicitly create the /data directory where the SQLite DB will live
|
| 22 |
+
# Running as root by default, so permissions should be okay initially
|
| 23 |
+
RUN mkdir -p /data
|
| 24 |
|
| 25 |
# Command to run the application using uvicorn
|
| 26 |
# It will run the FastAPI app instance created in app/main.py
|
app/database.py
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
|
|
| 1 |
import os
|
| 2 |
from databases import Database
|
| 3 |
from dotenv import load_dotenv
|
| 4 |
from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, text
|
|
|
|
| 5 |
|
| 6 |
load_dotenv()
|
|
|
|
| 7 |
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
-
# Use 'check_same_thread': False only for SQLite
|
| 11 |
connect_args = {"check_same_thread": False} if DATABASE_URL.startswith("sqlite") else {}
|
| 12 |
|
| 13 |
database = Database(DATABASE_URL, connect_args=connect_args)
|
| 14 |
metadata = MetaData()
|
| 15 |
|
| 16 |
-
# Define Users table using SQLAlchemy Core (needed for initial setup)
|
| 17 |
users = Table(
|
| 18 |
"users",
|
| 19 |
metadata,
|
|
@@ -22,26 +27,57 @@ users = Table(
|
|
| 22 |
Column("hashed_password", String, nullable=False),
|
| 23 |
)
|
| 24 |
|
| 25 |
-
# Create the database and table if they don't exist
|
| 26 |
-
#
|
| 27 |
-
|
|
|
|
|
|
|
| 28 |
|
| 29 |
-
#
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
try:
|
|
|
|
| 32 |
with engine.connect() as connection:
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
async def connect_db():
|
| 42 |
-
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
async def disconnect_db():
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# app/database.py
|
| 2 |
import os
|
| 3 |
from databases import Database
|
| 4 |
from dotenv import load_dotenv
|
| 5 |
from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, text
|
| 6 |
+
import logging # Add logging
|
| 7 |
|
| 8 |
load_dotenv()
|
| 9 |
+
logger = logging.getLogger(__name__) # Add logger
|
| 10 |
|
| 11 |
+
# --- CHANGE THIS LINE ---
|
| 12 |
+
# Use an absolute path in a known writable directory like /data
|
| 13 |
+
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite+aiosqlite:////data/app.db")
|
| 14 |
+
# Note the four slashes for an absolute path: sqlite+aiosqlite:////path/to/db
|
| 15 |
|
| 16 |
+
# Use 'check_same_thread': False only for SQLite
|
| 17 |
connect_args = {"check_same_thread": False} if DATABASE_URL.startswith("sqlite") else {}
|
| 18 |
|
| 19 |
database = Database(DATABASE_URL, connect_args=connect_args)
|
| 20 |
metadata = MetaData()
|
| 21 |
|
|
|
|
| 22 |
users = Table(
|
| 23 |
"users",
|
| 24 |
metadata,
|
|
|
|
| 27 |
Column("hashed_password", String, nullable=False),
|
| 28 |
)
|
| 29 |
|
| 30 |
+
# Create the database and table if they don't exist (synchronous part)
|
| 31 |
+
# Derive the synchronous URL correctly from the potentially absolute DATABASE_URL
|
| 32 |
+
sync_db_url = DATABASE_URL.replace("+aiosqlite", "")
|
| 33 |
+
logger.info(f"Using synchronous DB URL for initial check/create: {sync_db_url}")
|
| 34 |
+
engine = create_engine(sync_db_url, connect_args=connect_args)
|
| 35 |
|
| 36 |
+
# Extract the directory path to ensure it exists
|
| 37 |
+
db_file_path = sync_db_url.split("sqlite:///")[-1] # Gets /data/app.db
|
| 38 |
+
if db_file_path: # Ensure we got a path
|
| 39 |
+
db_dir = os.path.dirname(db_file_path)
|
| 40 |
+
logger.info(f"Ensuring database directory exists: {db_dir}")
|
| 41 |
+
try:
|
| 42 |
+
if db_dir and not os.path.exists(db_dir):
|
| 43 |
+
os.makedirs(db_dir, exist_ok=True)
|
| 44 |
+
logger.info(f"Created database directory: {db_dir}")
|
| 45 |
+
except OSError as e:
|
| 46 |
+
logger.error(f"Error creating database directory {db_dir}: {e}")
|
| 47 |
+
# Proceed anyway, maybe permissions allow file creation but not dir listing/creation
|
| 48 |
+
|
| 49 |
+
# Now try connecting and creating the table
|
| 50 |
try:
|
| 51 |
+
logger.info("Attempting to connect with sync engine to check/create table...")
|
| 52 |
with engine.connect() as connection:
|
| 53 |
+
# Try a simple query to see if the table exists
|
| 54 |
+
try:
|
| 55 |
+
connection.execute(text("SELECT 1 FROM users LIMIT 1"))
|
| 56 |
+
logger.info("Users table already exists.")
|
| 57 |
+
except Exception: # Catch specific DB exceptions if possible, e.g., sqlalchemy.exc.ProgrammingError
|
| 58 |
+
logger.info("Users table not found or error checking, attempting creation...")
|
| 59 |
+
metadata.create_all(engine) # Create tables if check fails
|
| 60 |
+
logger.info("Users table created (or creation attempted).")
|
| 61 |
+
|
| 62 |
+
except Exception as e:
|
| 63 |
+
logger.exception(f"CRITICAL: Failed to connect/create database tables using sync engine: {e}")
|
| 64 |
+
# Application might fail to start properly here. Depending on requirements,
|
| 65 |
+
# you might raise the exception or just log it and hope the async part works.
|
| 66 |
+
# For now, just log it, as the async connection might still succeed later.
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
# Async connect/disconnect functions
|
| 70 |
async def connect_db():
|
| 71 |
+
try:
|
| 72 |
+
await database.connect()
|
| 73 |
+
logger.info(f"Database connection established (async): {DATABASE_URL}")
|
| 74 |
+
except Exception as e:
|
| 75 |
+
logger.exception(f"Failed to establish async database connection: {e}")
|
| 76 |
+
raise # Reraise critical error during startup lifespan
|
| 77 |
|
| 78 |
async def disconnect_db():
|
| 79 |
+
try:
|
| 80 |
+
await database.disconnect()
|
| 81 |
+
logger.info("Database connection closed (async).")
|
| 82 |
+
except Exception as e:
|
| 83 |
+
logger.exception(f"Error closing async database connection: {e}")
|