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}")
|