Coverage for src / local_deep_research / web / app.py: 97%
59 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:55 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:55 +0000
1import atexit
2from loguru import logger
4from ..__version__ import __version__
5from ..utilities.log_utils import config_logger, flush_log_queue
6from .app_factory import create_app
7from .server_config import load_server_config
10@logger.catch
11def main():
12 """
13 Entry point for the web application when run as a command.
14 This function is needed for the package's entry point to work properly.
15 """
16 # Configure logging with milestone level
17 config = load_server_config()
18 config_logger("ldr_web", debug=config["debug"])
19 logger.info(f"Starting Local Deep Research v{__version__}")
21 # Register atexit handler to flush logs on exit
22 def flush_logs_on_exit():
23 """Flush any pending logs when the program exits."""
24 try:
25 # Create a minimal Flask app context to allow database access
26 from flask import Flask
28 app = Flask(__name__)
29 with app.app_context():
30 flush_log_queue()
31 except Exception:
32 logger.exception("Failed to flush logs on exit")
34 atexit.register(flush_logs_on_exit)
35 logger.debug("Registered atexit handler for log flushing")
37 # Create the Flask app and SocketIO instance
38 app, socket_service = create_app()
40 # Get web server settings from environment variables (LDR_WEB_HOST, etc.)
41 # These require a server restart to take effect
42 host = config["host"]
43 port = config["port"]
44 debug = config["debug"]
45 use_https = config["use_https"]
47 if use_https:
48 # For development, use self-signed certificate
49 logger.info("Starting server with HTTPS (self-signed certificate)")
50 # Note: SocketIOService doesn't support SSL context directly
51 # For production, use a reverse proxy like nginx for HTTPS
52 logger.warning(
53 "HTTPS requested but not supported directly. Use a reverse proxy for HTTPS."
54 )
56 # Start periodic cleanup of idle database connections
57 # Guard against Flask debug reloader spawning duplicate schedulers
58 import os
60 if not debug or os.environ.get("WERKZEUG_RUN_MAIN") == "true":
61 from .auth.connection_cleanup import start_connection_cleanup_scheduler
62 from .auth.session_manager import session_manager
63 from ..database.encrypted_db import db_manager
65 try:
66 cleanup_scheduler = start_connection_cleanup_scheduler(
67 session_manager, db_manager
68 )
69 atexit.register(lambda: cleanup_scheduler.shutdown(wait=False))
70 except Exception:
71 logger.warning(
72 "Failed to start cleanup scheduler; idle connections will not be auto-closed",
73 )
74 cleanup_scheduler = None
76 # Register shutdown handler for scheduler
77 def shutdown_scheduler():
78 if hasattr(app, "news_scheduler") and app.news_scheduler:
79 try:
80 app.news_scheduler.stop()
81 logger.info("News subscription scheduler stopped gracefully")
82 except Exception:
83 logger.exception("Error stopping scheduler")
85 atexit.register(shutdown_scheduler)
87 # Register shutdown handler for database connections
88 def shutdown_databases():
89 try:
90 from ..database.encrypted_db import db_manager
92 db_manager.close_all_databases()
93 logger.info("Database connections closed gracefully")
94 except Exception:
95 logger.exception("Error closing database connections")
97 atexit.register(shutdown_databases)
99 # Use the SocketIOService's run method which properly runs the socketio server
100 socket_service.run(host=host, port=port, debug=debug)
103if __name__ == "__main__": 103 ↛ 104line 103 didn't jump to line 104 because the condition on line 103 was never true
104 main()