amaye15 commited on
Commit
0f4be73
·
1 Parent(s): 24dc858
Files changed (2) hide show
  1. app/database.py +36 -80
  2. app/main.py +54 -8
app/database.py CHANGED
@@ -2,8 +2,8 @@
2
  import os
3
  from databases import Database
4
  from dotenv import load_dotenv
5
- # --- ADD THIS IMPORT ---
6
- from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, text, exc as sqlalchemy_exc
7
  import logging
8
  from urllib.parse import urlparse, urlunparse, parse_qs, urlencode
9
 
@@ -14,7 +14,6 @@ logger = logging.getLogger(__name__)
14
  DEFAULT_DB_PATH = "/tmp/app.db" # Store DB in the temporary directory
15
  raw_db_url = os.getenv("DATABASE_URL", f"sqlite+aiosqlite:///{DEFAULT_DB_PATH}")
16
 
17
- # --- URL Parsing and Async Database setup (remains the same) ---
18
  final_database_url = raw_db_url
19
  if raw_db_url.startswith("sqlite+aiosqlite"):
20
  parsed_url = urlparse(raw_db_url)
@@ -27,7 +26,10 @@ if raw_db_url.startswith("sqlite+aiosqlite"):
27
  else:
28
  logger.info(f"Using non-SQLite async DB URL: {final_database_url}")
29
 
 
30
  database = Database(final_database_url)
 
 
31
  metadata = MetaData()
32
  users = Table(
33
  "users",
@@ -37,92 +39,46 @@ users = Table(
37
  Column("hashed_password", String, nullable=False),
38
  )
39
 
40
- # --- Synchronous Engine for Initial Table Creation ---
41
- sync_db_url = final_database_url.replace("+aiosqlite", "")
42
- logger.info(f"Using synchronous DB URL for initial check/create: {sync_db_url}")
43
- engine = create_engine(sync_db_url)
44
-
45
- # --- Directory and Table Creation Logic ---
46
- db_file_path = ""
47
- if sync_db_url.startswith("sqlite"):
48
- path_part = sync_db_url.split("sqlite:///")[-1].split("?")[0]
49
- db_file_path = path_part # Should be /tmp/app.db
50
-
51
- if db_file_path:
52
- db_dir = os.path.dirname(db_file_path) # Should be /tmp
53
- logger.info(f"Checking database directory: {db_dir}")
54
- try:
55
- if not os.path.exists(db_dir):
56
- logger.error(f"CRITICAL: Directory {db_dir} does not exist!")
57
- elif not os.access(db_dir, os.W_OK):
58
- logger.error(f"CRITICAL: Directory {db_dir} is not writable! Cannot create database.")
59
- else:
60
- logger.info(f"Database directory {db_dir} appears writable.")
61
- except OSError as e:
62
- logger.error(f"Error accessing database directory {db_dir}: {e}")
63
- except Exception as e:
64
- logger.error(f"Unexpected error checking directory {db_dir}: {e}")
65
-
66
-
67
- # --- Refined Synchronous Table Check/Creation ---
68
- try:
69
- logger.info("Attempting sync connection to check/create table...")
70
- with engine.connect() as connection:
71
- logger.info("Sync engine connection successful.")
72
- try:
73
- # Check if table exists
74
- connection.execute(text("SELECT 1 FROM users LIMIT 1"))
75
- logger.info("Users table already exists (checked via sync connection).")
76
- # --- Catch specific SQLAlchemy error ---
77
- except sqlalchemy_exc.OperationalError as table_missing_err:
78
- # Check if the error message specifically indicates "no such table"
79
- if "no such table" in str(table_missing_err).lower():
80
- logger.warning("Users table not found (expected), attempting creation...")
81
- try:
82
- # Begin a transaction explicitly (optional, connect() usually does)
83
- # with connection.begin(): # Alternative way to manage transaction
84
- # Use the connection object for create_all
85
- metadata.create_all(bind=connection) # <-- Bind to connection
86
- # --- Explicitly commit ---
87
- connection.commit()
88
- logger.info("Users table creation attempted and committed via sync connection.")
89
- # --- Verify creation immediately ---
90
- try:
91
- connection.execute(text("SELECT 1 FROM users LIMIT 1"))
92
- logger.info("Users table successfully verified immediately after creation.")
93
- except Exception as verify_err:
94
- logger.error(f"Failed to verify table immediately after creation: {verify_err}")
95
-
96
- except Exception as creation_err:
97
- logger.exception(f"Error during table creation or commit: {creation_err}")
98
- # Optionally rollback? connection.rollback()
99
-
100
- else:
101
- # Log other OperationalErrors during the check phase
102
- logger.error(f"OperationalError during table check (but not 'no such table'): {table_missing_err}")
103
- raise # Re-raise unexpected errors
104
-
105
- except Exception as table_check_exc: # Catch other unexpected errors during check
106
- logger.error(f"Unexpected error during table check: {type(table_check_exc).__name__}: {table_check_exc}")
107
- raise # Re-raise unexpected errors
108
 
109
- except Exception as e:
110
- # Errors connecting, or unexpected errors during check/create phase
111
- logger.exception(f"CRITICAL: Failed during sync connection or table setup: {e}")
112
-
113
-
114
- # --- Async connect/disconnect functions (remain the same) ---
115
  async def connect_db():
 
116
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  await database.connect()
118
  logger.info(f"Database connection established (async): {final_database_url}")
 
119
  except Exception as e:
120
  logger.exception(f"Failed to establish async database connection: {e}")
121
- raise
122
 
123
  async def disconnect_db():
 
124
  try:
125
- await database.disconnect()
126
- logger.info("Database connection closed (async).")
 
 
 
127
  except Exception as e:
128
  logger.exception(f"Error closing async database connection: {e}")
 
2
  import os
3
  from databases import Database
4
  from dotenv import load_dotenv
5
+ # --- Keep only these SQLAlchemy imports ---
6
+ from sqlalchemy import MetaData, Table, Column, Integer, String
7
  import logging
8
  from urllib.parse import urlparse, urlunparse, parse_qs, urlencode
9
 
 
14
  DEFAULT_DB_PATH = "/tmp/app.db" # Store DB in the temporary directory
15
  raw_db_url = os.getenv("DATABASE_URL", f"sqlite+aiosqlite:///{DEFAULT_DB_PATH}")
16
 
 
17
  final_database_url = raw_db_url
18
  if raw_db_url.startswith("sqlite+aiosqlite"):
19
  parsed_url = urlparse(raw_db_url)
 
26
  else:
27
  logger.info(f"Using non-SQLite async DB URL: {final_database_url}")
28
 
29
+ # --- Async Database Instance ---
30
  database = Database(final_database_url)
31
+
32
+ # --- Metadata and Table Definition (Still needed for DDL generation) ---
33
  metadata = MetaData()
34
  users = Table(
35
  "users",
 
39
  Column("hashed_password", String, nullable=False),
40
  )
41
 
42
+ # --- REMOVE ALL SYNCHRONOUS ENGINE AND TABLE CREATION LOGIC ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
+ # --- Keep and refine Async connect/disconnect functions ---
 
 
 
 
 
45
  async def connect_db():
46
+ """Connects to the database, ensuring the parent directory exists."""
47
  try:
48
+ # Ensure the directory exists just before connecting
49
+ db_file_path = final_database_url.split("sqlite:///")[-1].split("?")[0]
50
+ db_dir = os.path.dirname(db_file_path)
51
+ if db_dir: # Only proceed if a directory path was found
52
+ if not os.path.exists(db_dir):
53
+ logger.info(f"Database directory {db_dir} does not exist. Attempting creation...")
54
+ try:
55
+ os.makedirs(db_dir, exist_ok=True)
56
+ logger.info(f"Created database directory {db_dir}.")
57
+ except Exception as mkdir_err:
58
+ # Log error but proceed, connection might still work if path is valid but dir creation failed weirdly
59
+ logger.error(f"Failed to create directory {db_dir}: {mkdir_err}")
60
+ # Check writability after ensuring existence attempt
61
+ if os.path.exists(db_dir) and not os.access(db_dir, os.W_OK):
62
+ logger.error(f"CRITICAL: Directory {db_dir} exists but is not writable!")
63
+ elif not os.path.exists(db_dir):
64
+ logger.error(f"CRITICAL: Directory {db_dir} does not exist and could not be created!")
65
+
66
+
67
+ # Now attempt connection
68
  await database.connect()
69
  logger.info(f"Database connection established (async): {final_database_url}")
70
+ # Table creation will happen in main.py lifespan event using this connection
71
  except Exception as e:
72
  logger.exception(f"Failed to establish async database connection: {e}")
73
+ raise # Reraise critical error during startup
74
 
75
  async def disconnect_db():
76
+ """Disconnects from the database if connected."""
77
  try:
78
+ if database.is_connected:
79
+ await database.disconnect()
80
+ logger.info("Database connection closed (async).")
81
+ else:
82
+ logger.info("Database already disconnected (async).")
83
  except Exception as e:
84
  logger.exception(f"Error closing async database connection: {e}")
app/main.py CHANGED
@@ -1,6 +1,7 @@
 
1
  import gradio as gr
2
- import httpx # Use httpx for async requests from Gradio backend to API
3
- import websockets # Use websockets library to connect from Gradio backend
4
  import asyncio
5
  import json
6
  import os
@@ -8,27 +9,72 @@ import logging
8
  from contextlib import asynccontextmanager
9
 
10
  from fastapi import FastAPI, Depends # Import FastAPI itself
11
- from .api import router as api_router # Import the API router
12
- from .database import connect_db, disconnect_db
13
- from . import schemas, auth, dependencies # Import necessary modules
 
 
 
 
 
14
 
15
  # Configure logging
16
  logging.basicConfig(level=logging.INFO)
17
  logger = logging.getLogger(__name__)
18
 
19
- # Base URL for the API (since Gradio runs its own server, even if mounted)
20
- # Assuming Gradio runs on 7860, FastAPI routes mounted under it
21
  API_BASE_URL = "http://127.0.0.1:7860/api" # Adjust if needed
22
 
23
  # --- FastAPI Lifespan Event ---
24
  @asynccontextmanager
25
  async def lifespan(app: FastAPI):
26
  # Startup
 
27
  await connect_db()
28
- # Start background task for websocket listening if needed (or handle within component interaction)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  yield
30
  # Shutdown
 
31
  await disconnect_db()
 
32
 
33
  # Create the main FastAPI app instance that Gradio will use
34
  # We attach our API routes to this instance.
 
1
+ # app/main.py
2
  import gradio as gr
3
+ import httpx
4
+ import websockets
5
  import asyncio
6
  import json
7
  import os
 
9
  from contextlib import asynccontextmanager
10
 
11
  from fastapi import FastAPI, Depends # Import FastAPI itself
12
+ # --- Import necessary items from database.py ---
13
+ from .database import connect_db, disconnect_db, database, metadata, users
14
+ from .api import router as api_router
15
+ from . import schemas, auth, dependencies
16
+
17
+ # --- Import SQLAlchemy helpers for DDL generation ---
18
+ from sqlalchemy.schema import CreateTable
19
+ from sqlalchemy.dialects import sqlite # Assuming SQLite, adjust if needed
20
 
21
  # Configure logging
22
  logging.basicConfig(level=logging.INFO)
23
  logger = logging.getLogger(__name__)
24
 
25
+ # Base URL for the API
 
26
  API_BASE_URL = "http://127.0.0.1:7860/api" # Adjust if needed
27
 
28
  # --- FastAPI Lifespan Event ---
29
  @asynccontextmanager
30
  async def lifespan(app: FastAPI):
31
  # Startup
32
+ logger.info("Application startup: Connecting DB...")
33
  await connect_db()
34
+ logger.info("Application startup: DB Connected. Checking/Creating tables...")
35
+ if database.is_connected: # Proceed only if connection succeeded
36
+ try:
37
+ # 1. Check if table exists using the async connection
38
+ # For SQLite, check the sqlite_master table
39
+ check_query = "SELECT name FROM sqlite_master WHERE type='table' AND name=:table_name;"
40
+ table_exists = await database.fetch_one(query=check_query, values={"table_name": users.name})
41
+
42
+ if not table_exists:
43
+ logger.info(f"Table '{users.name}' not found, attempting creation using async connection...")
44
+
45
+ # 2. Generate the CREATE TABLE statement using SQLAlchemy DDL compiler
46
+ # We need a dialect object for the compiler
47
+ # Infer dialect from the database URL if possible, default to SQLite for this example
48
+ dialect = sqlite.dialect() # Or determine dynamically based on database.url
49
+ create_table_stmt = str(CreateTable(users).compile(dialect=dialect))
50
+ logger.debug(f"Generated CREATE TABLE statement: {create_table_stmt}")
51
+
52
+ # 3. Execute the CREATE TABLE statement via the async database connection
53
+ await database.execute(query=create_table_stmt)
54
+ logger.info(f"Table '{users.name}' created successfully via async connection.")
55
+
56
+ # 4. Optional: Verify again immediately
57
+ table_exists_after = await database.fetch_one(query=check_query, values={"table_name": users.name})
58
+ if table_exists_after:
59
+ logger.info(f"Table '{users.name}' verified after creation.")
60
+ else:
61
+ logger.error(f"Table '{users.name}' verification FAILED after creation attempt!")
62
+
63
+ else:
64
+ logger.info(f"Table '{users.name}' already exists (checked via async connection).")
65
+
66
+ except Exception as db_setup_err:
67
+ logger.exception(f"CRITICAL error during async DB table setup: {db_setup_err}")
68
+ # Consider whether to halt startup here depending on severity
69
+ else:
70
+ logger.error("CRITICAL: Database connection failed, skipping table setup.")
71
+
72
+ logger.info("Application startup: DB setup phase complete.")
73
  yield
74
  # Shutdown
75
+ logger.info("Application shutdown: Disconnecting DB...")
76
  await disconnect_db()
77
+ logger.info("Application shutdown: DB Disconnected.")
78
 
79
  # Create the main FastAPI app instance that Gradio will use
80
  # We attach our API routes to this instance.