amaye15 commited on
Commit
4809b28
·
1 Parent(s): 1cff830

Debug - User Add

Browse files
Files changed (1) hide show
  1. app/database.py +78 -29
app/database.py CHANGED
@@ -3,22 +3,42 @@ 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,52 +47,81 @@ users = Table(
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():
 
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
7
+ from urllib.parse import urlparse, urlunparse, parse_qs, urlencode # For URL manipulation
8
 
9
  load_dotenv()
10
+ logger = logging.getLogger(__name__)
11
 
12
+ # --- Database URL Configuration ---
13
+ DEFAULT_DB_PATH = "/data/app.db"
14
+ # Start with the base URL from env or default
15
+ raw_db_url = os.getenv("DATABASE_URL", f"sqlite+aiosqlite:///{DEFAULT_DB_PATH}")
16
 
17
+ # Ensure 'check_same_thread=False' is in the URL for SQLite async connection
18
+ final_database_url = raw_db_url
19
+ if raw_db_url.startswith("sqlite+aiosqlite"):
20
+ # Parse the URL
21
+ parsed_url = urlparse(raw_db_url)
22
+ # Parse existing query parameters into a dictionary
23
+ query_params = parse_qs(parsed_url.query)
24
+ # Add check_same_thread=False ONLY if it's not already there
25
+ # (in case it's set via DATABASE_URL env var)
26
+ if 'check_same_thread' not in query_params:
27
+ query_params['check_same_thread'] = ['False'] # Needs to be a list for urlencode
28
+ # Rebuild the query string
29
+ new_query = urlencode(query_params, doseq=True)
30
+ # Rebuild the URL using _replace method of the named tuple
31
+ final_database_url = urlunparse(parsed_url._replace(query=new_query))
32
+ logger.info(f"Using final async DB URL: {final_database_url}")
33
+ else:
34
+ logger.info(f"Using non-SQLite async DB URL: {final_database_url}")
35
 
 
 
36
 
37
+ # --- Async Database Instance (using 'databases' library) ---
38
+ # Pass the *modified* URL. DO NOT pass connect_args separately here.
39
+ database = Database(final_database_url)
40
+
41
+ metadata = MetaData()
42
  users = Table(
43
  "users",
44
  metadata,
 
47
  Column("hashed_password", String, nullable=False),
48
  )
49
 
50
+ # --- Synchronous Engine for Initial Table Creation (using SQLAlchemy Core) ---
51
+ # Derive the sync URL (remove +aiosqlite). The query param should remain.
52
+ sync_db_url = final_database_url.replace("+aiosqlite", "")
53
+
54
+ # SQLAlchemy's create_engine *can* take connect_args, but for check_same_thread,
55
+ # it also understands it from the URL query string. Let's rely on the URL for simplicity.
56
+ # sync_connect_args = {"check_same_thread": False} if sync_db_url.startswith("sqlite") else {} # Keep for reference if other args are needed
57
+
58
  logger.info(f"Using synchronous DB URL for initial check/create: {sync_db_url}")
59
+ # Create the engine using the URL which now includes ?check_same_thread=False
60
+ engine = create_engine(sync_db_url) # No connect_args needed here if only using check_same_thread
61
 
62
+ # --- Directory and Table Creation Logic ---
63
+ # Extract path correctly, ignoring query parameters for os.path operations
64
+ db_file_path = ""
65
+ if sync_db_url.startswith("sqlite"):
66
+ # Get the path part after 'sqlite:///' and before '?'
67
+ path_part = sync_db_url.split("sqlite:///")[-1].split("?")[0]
68
+ # Ensure it's an absolute path if it starts with /
69
+ if path_part.startswith('/'):
70
+ db_file_path = path_part
71
+ else:
72
+ # Handle relative paths if they were somehow configured (though /data should be absolute)
73
+ # This case is less likely with our default /data/app.db
74
+ db_file_path = os.path.abspath(path_part)
75
+
76
+
77
+ if db_file_path:
78
  db_dir = os.path.dirname(db_file_path)
79
  logger.info(f"Ensuring database directory exists: {db_dir}")
80
  try:
81
  if db_dir and not os.path.exists(db_dir):
82
  os.makedirs(db_dir, exist_ok=True)
83
  logger.info(f"Created database directory: {db_dir}")
84
+ # Add a check for writability after ensuring directory exists
85
+ if db_dir and not os.access(db_dir, os.W_OK):
86
+ logger.error(f"Database directory {db_dir} is not writable!")
87
+ # Also check if the file itself can be created/opened (might fail here if dir is writable but file isn't)
88
+ # This check is implicitly done by engine.connect() below
89
+
90
  except OSError as e:
91
+ logger.error(f"Error creating or accessing database directory {db_dir}: {e}")
92
+ except Exception as e:
93
+ logger.error(f"Unexpected error checking/creating DB directory {db_dir}: {e}")
94
+
95
 
96
+ # Now try connecting and creating the table with the sync engine
97
  try:
98
  logger.info("Attempting to connect with sync engine to check/create table...")
99
  with engine.connect() as connection:
 
100
  try:
101
+ # Use text() for literal SQL
102
  connection.execute(text("SELECT 1 FROM users LIMIT 1"))
103
  logger.info("Users table already exists.")
104
+ except Exception as table_check_exc: # Catch specific DB errors if possible
105
+ logger.warning(f"Users table check failed ({type(table_check_exc).__name__}), attempting creation...")
106
+ # Pass the engine explicitly to create_all
107
+ metadata.create_all(bind=engine)
108
  logger.info("Users table created (or creation attempted).")
109
 
110
  except Exception as e:
111
+ # This OperationalError "unable to open database file" might still indicate
112
+ # a fundamental permission issue with /data/app.db in the HF environment.
113
  logger.exception(f"CRITICAL: Failed to connect/create database tables using sync engine: {e}")
 
 
 
114
 
115
 
116
+ # --- Async connect/disconnect functions ---
117
  async def connect_db():
118
  try:
119
+ # The 'database' instance now uses the URL with the query param
120
  await database.connect()
121
+ logger.info(f"Database connection established (async): {final_database_url}")
122
  except Exception as e:
123
  logger.exception(f"Failed to establish async database connection: {e}")
124
+ # If the sync engine failed earlier due to permissions, this might fail too.
125
  raise # Reraise critical error during startup lifespan
126
 
127
  async def disconnect_db():