#!/usr/bin/env python3 """ NSE Stock Dashboard (NSEDashboard) Application Entry Point. This module serves as the primary executable entry point for the NSE Stock Dashboard Flask application. It parses command-line arguments to configure the server settings (base directory, host interface, port, and debug mode), initializes the Flask application with all required services, and starts the Flask WSGI server with multithreading enabled. Features: - Configurable base data directory via command line or environment variables. - Integrated background task processing framework. - Multi-threaded Flask server configuration to maintain frontend responsiveness. Usage: python nsedash_app.py [--base-dir BASE_DIR] [--host HOST] [--port PORT] [--debug] Options: --base-dir DIR Specify the directory containing the processed stock data files. If not provided, the current working directory or the value of the $NSEDASH_BASE_DIR environment variable is used. --host HOST Host interface to bind the web server to (default: 127.0.0.1). --port PORT Port to listen on (default: 5000). --debug Run the Flask application in debug mode. """ import argparse import os import signal import sys import time # --------------------------------------------------------------------------- # CLI SWITCHES (mlbench pattern — parsed before argparse for env var side-effects) # --------------------------------------------------------------------------- #: -hf Force HuggingFace mode: bind 0.0.0.0:7860, activate HF dataset sync ENABLE_HF = '-hf' in sys.argv or '--hf' in sys.argv #: -noadmin Disable admin-only tools (script runner, data utilities) ENABLE_NOADMIN = '-noadmin' in sys.argv or '--noadmin' in sys.argv if ENABLE_HF: os.environ["HF_MODE"] = "1" print("[*] HF mode (-hf): Enabled — binding 0.0.0.0:7860, HF dataset sync active.") if ENABLE_NOADMIN: os.environ["ADMIN_MODE"] = "0" print("[*] No-admin (-noadmin): Enabled — admin tools disabled.") # Disable CUDA inside Hugging Face Spaces mode to save resources if "SPACE_ID" in os.environ or ENABLE_HF: os.environ["CUDA_VISIBLE_DEVICES"] = "-1" os.environ["HF_MODE"] = "1" # Also set from SPACE_ID detection from nsedash.web import create_app def get_gpu_info() -> str: gpu_info = "CPU Only (No GPU detected)" try: import torch if torch.cuda.is_available(): gpu_info = f"CUDA Active - {torch.cuda.get_device_name(0)}" except ImportError: pass try: if gpu_info == "CPU Only (No GPU detected)": import tensorflow as tf gpus = tf.config.list_physical_devices('GPU') if gpus: gpu_info = f"TensorFlow Active - {len(gpus)} GPU(s)" except ImportError: pass return gpu_info def print_console_splash(host, port): display_host = "localhost" if host in ("127.0.0.1", "0.0.0.0") else host gpu_info = get_gpu_info() is_hf = os.environ.get("HF_MODE") == "1" or "SPACE_ID" in os.environ admin_enabled = os.environ.get("ADMIN_MODE", "1") == "1" banner = r""" ====================================================================== _ _ ____ _____ ____ _ ____ _ _ ____ ___ _ ____ ____ | \ | / ___|| ____| _ \ / \ / ___|| | | | _ \ / _ \ / \ | _ \| _ \ | \| \___ \| _| | | | / _ \\___ \| |_| | |_) | | | / _ \| |_) | | | | | |\ |___) | |___| |_| / ___ \___) | _ | _ <| |_| / ___ \ _ <| |_| | |_| \_|____/|_____|____/_/ \_\____/|_| |_|_| \_\\___/_/ \_\_| \_\____/ ====================================================================== """ print(banner) print(f" * NSE Stock Dashboard (Flask v5.0)") print(f" * Running on: http://{display_host}:{port}/") print(f" * GPU in use: {gpu_info}") print(f" * HF Mode: {'Enabled (HF dataset sync active)' if is_hf else 'Disabled (local mode)'}") print(f" * Admin Mode: {'Enabled' if admin_enabled else 'Disabled (-noadmin)'}") print(f" * Auth: userid + password + TOTP (mlbench pattern)") print(f" * Press Ctrl+C in console to exit gracefully.") print("======================================================================\n") _shutdown_in_progress = False def register_shutdown_handler(app): def handle_sigint(signum, frame): global _shutdown_in_progress if _shutdown_in_progress: return _shutdown_in_progress = True print("\n[Shutdown] Gracefully shutting down nsedash backend...") # 1. Mark as exiting so UI status calls see it try: service = app.extensions["nsedash"]["service"] service.is_exiting = True except Exception: pass # 2. Wait a short moment for UI to poll/receive the exiting state time.sleep(1.5) # 3. Clean up backend components try: scheduler = app.extensions["nsedash"]["scheduler"] scheduler.stop() except Exception: pass try: scripts = app.extensions["nsedash"]["scripts"] for run in list(scripts._runs.values()): if run.process and run.process.poll() is None: run.process.terminate() try: run.process.wait(timeout=1.0) except Exception: run.process.kill() except Exception: pass try: runtime = app.extensions["nsedash"]["runtime"] runtime.shutdown() except Exception: pass print("[Shutdown] Backend shutdown complete. Exiting process.") sys.exit(0) signal.signal(signal.SIGINT, handle_sigint) def main(): parser = argparse.ArgumentParser( description="NSE Stock Dashboard (Flask)", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" CLI switches (mlbench pattern, checked before argparse): -hf Force HuggingFace mode: 0.0.0.0:7860, HF dataset sync -noadmin Disable admin tools (script runner, data utilities) """, ) parser.add_argument("--base-dir", default=None, help="Base data directory (default: CWD or $NSEDASH_BASE_DIR)") is_hf = ENABLE_HF or ("SPACE_ID" in os.environ) default_host = "0.0.0.0" if is_hf else "127.0.0.1" default_port = 7860 if is_hf else 5000 parser.add_argument("--host", default=default_host) parser.add_argument("--port", type=int, default=default_port) parser.add_argument("--debug", action="store_true") parser.add_argument("--hf", action="store_true", help="Force HF mode (same as -hf switch)") parser.add_argument("--noadmin", action="store_true", help="Disable admin tools (same as -noadmin switch)") args = parser.parse_args() # argparse flags also set env vars (in case user passed --hf instead of -hf) if args.hf: os.environ["HF_MODE"] = "1" if args.noadmin: os.environ["ADMIN_MODE"] = "0" app = create_app(args.base_dir) # Register shutdown handler & show splash screen register_shutdown_handler(app) print_console_splash(args.host, args.port) # threaded=True: request handlers stay responsive while background # crews run on the dedicated asyncio thread. app.run(host=args.host, port=args.port, debug=args.debug, threaded=True) if __name__ == "__main__": main()