from flask import Flask import os import logging from datetime import datetime from sqlalchemy.exc import SQLAlchemyError, OperationalError from sqlalchemy import inspect from time import sleep from .config import get_config from .logging_config import configure_logging from dotenv import load_dotenv from .extensions import db, login_manager, migrate, csrf, limiter, cache, mail from flask_sqlalchemy import SQLAlchemy from flask_login import LoginManager from flask_migrate import Migrate from flask_limiter import Limiter from flask_limiter.util import get_remote_address from flask_caching import Cache # Load environment variables first load_dotenv() db = SQLAlchemy() migrate = Migrate() limiter = Limiter(key_func=get_remote_address) cache = Cache() def create_app(config_class=None): app = Flask(__name__, static_folder='static') @app.context_processor def inject_now(): return {'now': datetime.utcnow()} # Load configuration if config_class is None: config_class = get_config() app.config.from_object(config_class) # Initialize configuration config_class.init_app(app) # Ensure secret key is properly set if not app.config.get('SECRET_KEY'): raise ValueError("SECRET_KEY must be set in the configuration") app.config['SECRET_KEY'] = app.config['SECRET_KEY'].encode('utf-8') # Ensure instance folder exists try: os.makedirs(app.instance_path) except OSError: pass # Configure logging configure_logging(app) # Initialize extensions with retry logic max_retries = 3 retry_delay = 5 # Initialize extensions only if they haven't been initialized if not hasattr(app, 'extensions') or 'sqlalchemy' not in app.extensions: for attempt in range(max_retries): try: # Initialize SQLAlchemy only if not already initialized if 'sqlalchemy' not in app.extensions: db.init_app(app) migrate.init_app(app, db) # Initialize other extensions if 'csrf' not in app.extensions: csrf.init_app(app) if 'limiter' not in app.extensions: limiter.init_app(app) if 'cache' not in app.extensions: cache.init_app(app) if 'mail' not in app.extensions: mail.init_app(app) if 'login_manager' not in app.extensions: login_manager.init_app(app) # Test database connection with app.app_context(): db.engine.connect() break except OperationalError as e: if attempt == max_retries - 1: app.logger.error(f"Failed to connect to database after {max_retries} attempts: {str(e)}") raise app.logger.warning(f"Database connection attempt {attempt + 1} failed: {str(e)}") sleep(retry_delay) # Configure login manager login_manager.login_view = 'auth.login' login_manager.login_message = 'Please log in to access this page.' login_manager.login_message_category = 'warning' @login_manager.user_loader def load_user(user_id): from .models import User return User.query.get(int(user_id)) # Register blueprints from .routes import main, auth, admin app.register_blueprint(main.bp) app.register_blueprint(auth.bp) app.register_blueprint(admin.bp) # Register error handlers from .errors import register_error_handlers register_error_handlers(app) # Debug: log all registered endpoints at startup (warning level and flush) app.logger.warning("Registered endpoints (debug):") for rule in app.url_map.iter_rules(): app.logger.warning(f"{rule.endpoint} {rule.methods} {rule.rule}") for handler in app.logger.handlers: handler.flush() # Debug: write all registered endpoints to a file with open('logs/endpoints.log', 'w') as f: for rule in app.url_map.iter_rules(): f.write(f"{rule.endpoint} {rule.methods} {rule.rule}\n") return app