|
|
""" |
|
|
Robust startup script for Hugging Face Spaces deployment. |
|
|
Initializes the database and starts the FastAPI server with comprehensive error handling. |
|
|
""" |
|
|
import os |
|
|
import sys |
|
|
import time |
|
|
import logging |
|
|
import asyncio |
|
|
from pathlib import Path |
|
|
import subprocess |
|
|
from datetime import datetime |
|
|
|
|
|
|
|
|
logging.basicConfig( |
|
|
level=logging.INFO, |
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', |
|
|
handlers=[ |
|
|
logging.FileHandler('logs/startup.log'), |
|
|
logging.StreamHandler(sys.stdout) |
|
|
] |
|
|
) |
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
def check_environment(): |
|
|
"""Check if required environment variables are set.""" |
|
|
logger.info("π Checking environment variables...") |
|
|
|
|
|
required_vars = ['OPENAI_API_KEY', 'OPENROUTER_API_KEY'] |
|
|
missing_vars = [] |
|
|
|
|
|
for var in required_vars: |
|
|
if not os.getenv(var): |
|
|
missing_vars.append(var) |
|
|
logger.error(f"β {var} is not set") |
|
|
else: |
|
|
logger.info(f"β
{var} is set") |
|
|
|
|
|
|
|
|
optional_vars = ['DATABASE_URL', 'QDRANT_URL', 'HF_SPACE_ID'] |
|
|
for var in optional_vars: |
|
|
if os.getenv(var): |
|
|
logger.info(f"β
{var} is set") |
|
|
else: |
|
|
logger.info(f"β οΈ {var} is not set (optional)") |
|
|
|
|
|
if missing_vars: |
|
|
logger.error(f"β Missing required environment variables: {missing_vars}") |
|
|
return False |
|
|
|
|
|
return True |
|
|
|
|
|
def initialize_database(): |
|
|
"""Initialize the database with retries.""" |
|
|
logger.info("π¦ Initializing database...") |
|
|
max_retries = 3 |
|
|
|
|
|
for attempt in range(max_retries): |
|
|
try: |
|
|
|
|
|
if not Path("init_database.py").exists(): |
|
|
logger.warning("β οΈ init_database.py not found, skipping database initialization") |
|
|
return True |
|
|
|
|
|
result = subprocess.run( |
|
|
[sys.executable, "init_database.py"], |
|
|
capture_output=True, |
|
|
text=True, |
|
|
timeout=30 |
|
|
) |
|
|
|
|
|
if result.returncode == 0: |
|
|
logger.info("β
Database initialization completed successfully!") |
|
|
if result.stdout: |
|
|
logger.info(f"Database init output: {result.stdout}") |
|
|
return True |
|
|
else: |
|
|
logger.error(f"β οΈ Database initialization attempt {attempt + 1} failed: {result.stderr}") |
|
|
if attempt < max_retries - 1: |
|
|
time.sleep(2) |
|
|
|
|
|
except subprocess.TimeoutExpired: |
|
|
logger.error(f"β οΈ Database initialization timeout on attempt {attempt + 1}") |
|
|
if attempt < max_retries - 1: |
|
|
time.sleep(2) |
|
|
except Exception as e: |
|
|
logger.error(f"β οΈ Database initialization error on attempt {attempt + 1}: {str(e)}") |
|
|
if attempt < max_retries - 1: |
|
|
time.sleep(2) |
|
|
|
|
|
logger.error("β Database initialization failed after all attempts") |
|
|
return False |
|
|
|
|
|
def verify_database_file(): |
|
|
"""Check if database file exists and is accessible.""" |
|
|
db_path = Path("database/auth.db") |
|
|
if db_path.exists(): |
|
|
try: |
|
|
size = db_path.stat().st_size |
|
|
logger.info(f"β
Database file found! Size: {size} bytes") |
|
|
return True |
|
|
except Exception as e: |
|
|
logger.error(f"β Error accessing database file: {str(e)}") |
|
|
return False |
|
|
else: |
|
|
logger.info("β οΈ Database file not found. The server will create it on startup.") |
|
|
return True |
|
|
|
|
|
def create_directories(): |
|
|
"""Create necessary directories.""" |
|
|
directories = ['database', 'logs', '.cache/huggingface', '.cache/transformers'] |
|
|
for directory in directories: |
|
|
Path(directory).mkdir(parents=True, exist_ok=True) |
|
|
logger.info(f"β
Directory {directory} ready") |
|
|
|
|
|
def start_server(): |
|
|
"""Start the FastAPI server with error handling.""" |
|
|
logger.info("π Starting FastAPI server...") |
|
|
|
|
|
|
|
|
port = int(os.getenv('PORT', 7860)) |
|
|
host = os.getenv('HOST', '0.0.0.0') |
|
|
workers = int(os.getenv('WORKERS', 1)) |
|
|
|
|
|
cmd = [ |
|
|
sys.executable, "-m", "uvicorn", |
|
|
"main:app", |
|
|
"--host", host, |
|
|
"--port", str(port), |
|
|
"--workers", str(workers), |
|
|
"--log-level", "info", |
|
|
"--access-log" |
|
|
] |
|
|
|
|
|
logger.info(f"Command: {' '.join(cmd)}") |
|
|
|
|
|
try: |
|
|
|
|
|
os.execvp(sys.executable, cmd) |
|
|
except Exception as e: |
|
|
logger.error(f"β Failed to start server: {str(e)}") |
|
|
sys.exit(1) |
|
|
|
|
|
def main(): |
|
|
"""Main startup function.""" |
|
|
logger.info("π Starting server initialization for Hugging Face Spaces...") |
|
|
logger.info(f"π
Timestamp: {datetime.utcnow().isoformat()}") |
|
|
logger.info(f"π Python version: {sys.version}") |
|
|
logger.info(f"π Working directory: {os.getcwd()}") |
|
|
|
|
|
|
|
|
if os.path.exists("backend") and not os.getcwd().endswith("backend"): |
|
|
os.chdir("backend") |
|
|
logger.info(f"Changed to backend directory: {os.getcwd()}") |
|
|
|
|
|
try: |
|
|
|
|
|
create_directories() |
|
|
|
|
|
|
|
|
if not check_environment(): |
|
|
logger.error("β Environment check failed. Exiting...") |
|
|
sys.exit(1) |
|
|
|
|
|
|
|
|
if not initialize_database(): |
|
|
logger.error("β Database initialization failed. Continuing anyway...") |
|
|
|
|
|
|
|
|
verify_database_file() |
|
|
|
|
|
|
|
|
logger.info("π Initialization completed successfully!") |
|
|
logger.info(f"π Environment: {'HF Spaces' if os.getenv('SPACE_ID') else 'Local'}") |
|
|
logger.info(f"π§ Port: {os.getenv('PORT', 7860)}") |
|
|
|
|
|
|
|
|
start_server() |
|
|
|
|
|
except KeyboardInterrupt: |
|
|
logger.info("βΉοΈ Startup interrupted by user") |
|
|
sys.exit(0) |
|
|
except Exception as e: |
|
|
logger.error(f"π₯ Fatal error during startup: {str(e)}", exc_info=True) |
|
|
sys.exit(1) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
main() |