import json import logging import os from typing import Optional # Module-level state for runtime path overrides _config_path_override: Optional[str] = None _cookies_path_override: Optional[str] = None def set_config_path(path: Optional[str]) -> None: """ Set a custom path for config.json at runtime. This override takes precedence over environment variables and defaults. Note: This uses module-level state and is not thread-safe. Suitable for single-threaded CLI usage or single async operations. Args: path: Absolute or relative path to config file, or None to reset """ global _config_path_override _config_path_override = path def set_cookies_path(path: Optional[str]) -> None: """ Set a custom path for cookies.json at runtime. This override takes precedence over environment variables and defaults. Note: This uses module-level state and is not thread-safe. Suitable for single-threaded CLI usage or single async operations. Args: path: Absolute or relative path to cookies file, or None to reset """ global _cookies_path_override _cookies_path_override = path def get_config_path() -> str: """ Resolve the configuration file path using priority order: 1. Runtime override (set_config_path) 2. Environment variable SCHWAB_CONFIG_PATH 3. Default locations (../config.json relative to module, then ./config.json) Returns: str: Path to configuration file """ # Priority 1: Runtime override if _config_path_override: return _config_path_override # Priority 2: Environment variable env_path = os.environ.get('SCHWAB_CONFIG_PATH') if env_path: return env_path # Priority 3: Default locations # Try package root first (for development/installed package) default_path = os.path.join(os.path.dirname(__file__), '..', 'config.json') if os.path.exists(default_path): return default_path # Fall back to current working directory return 'config.json' def get_cookies_path() -> str: """ Resolve the cookies file path using priority order: 1. Runtime override (set_cookies_path) 2. Environment variable SCHWAB_COOKIES_PATH 3. Default location (./cookies.json in CWD) Returns: str: Path to cookies file """ # Priority 1: Runtime override if _cookies_path_override: return _cookies_path_override # Priority 2: Environment variable env_path = os.environ.get('SCHWAB_COOKIES_PATH') if env_path: return env_path # Priority 3: Default location return 'cookies.json' def load_config(): """Load configuration from config.json (or custom path if configured)""" logger = logging.getLogger(__name__) config_path = get_config_path() try: with open(config_path, 'r') as f: return json.load(f) except FileNotFoundError: logger.error(f"config.json not found at {config_path}. Please create one based on config.json.sample") return None except json.JSONDecodeError: logger.error(f"Invalid JSON in config file at {config_path}") return None def get_playwright_url(config=None): """Get the Playwright browserless URL from config""" import os env_url = os.environ.get('SCHWAB_PLAYWRIGHT_URL') if env_url: return env_url if config is None: config = load_config() if config and 'playwright' in config and 'url' in config['playwright']: return config['playwright']['url'] else: # Default fallback URL return "ws://browser.local.ben.io:3000/playwright/chromium" def get_schwab_credentials(config=None): """Get Schwab credentials from config""" if config is None: config = load_config() if config and 'schwab' in config: return config['schwab'].get('username'), config['schwab'].get('password') else: return None, None