From 8af09ca016cc70bf35f7c0f35515f73870fa7eb5 Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Tue, 20 Jan 2026 09:39:49 -0500 Subject: [PATCH 1/2] Add no_browser option to disable automatic browser opening --- lean/commands/cloud/live/deploy.py | 14 +++++++++----- lean/commands/live/deploy.py | 15 ++++++++++----- lean/components/api/auth0_client.py | 7 ++++++- lean/components/util/auth0_helper.py | 5 +++-- lean/components/util/json_modules_handler.py | 13 +++++++------ lean/models/json_module.py | 6 ++++-- 6 files changed, 39 insertions(+), 21 deletions(-) diff --git a/lean/commands/cloud/live/deploy.py b/lean/commands/cloud/live/deploy.py index deff3b6d..eaa96dbf 100644 --- a/lean/commands/cloud/live/deploy.py +++ b/lean/commands/cloud/live/deploy.py @@ -202,6 +202,10 @@ def _configure_auto_restart(logger: Logger) -> bool: default=False, help="Automatically open the live results in the browser once the deployment starts") @option("--show-secrets", is_flag=True, show_default=True, default=False, help="Show secrets as they are input") +@option("--no-browser", + is_flag=True, + default=False, + help="Display OAuth URL without opening browser.") def deploy(project: str, brokerage: str, data_provider_live: Optional[str], @@ -218,6 +222,7 @@ def deploy(project: str, push: bool, open_browser: bool, show_secrets: bool, + no_browser: bool, **kwargs) -> None: """Start live trading for a project in the cloud. @@ -246,7 +251,7 @@ def deploy(project: str, ensure_options(["brokerage", "node", "auto_restart", "notify_order_events", "notify_insights"]) brokerage_instance = non_interactive_config_build_for_name(lean_config, brokerage, cloud_brokerages, - kwargs, logger) + kwargs, logger, no_browser=no_browser) notify_methods = [] if notify_emails is not None: for config in notify_emails.split(","): @@ -288,7 +293,7 @@ def deploy(project: str, else: # let the user choose the brokerage brokerage_instance = interactive_config_build(lean_config, cloud_brokerages, logger, kwargs, show_secrets, - "Select a brokerage", multiple=False) + "Select a brokerage", multiple=False, no_browser=no_browser) notify_order_events, notify_insights, notify_methods = _configure_notifications(logger) auto_restart = _configure_auto_restart(logger) @@ -304,13 +309,13 @@ def deploy(project: str, # the user sent the live data provider to use for data_provider in data_provider_live: data_provider_instance = non_interactive_config_build_for_name(lean_config, data_provider, - cloud_data_queue_handlers, kwargs, logger) + cloud_data_queue_handlers, kwargs, logger, no_browser=no_browser) live_data_provider_settings.update({data_provider_instance.get_id(): data_provider_instance.get_settings()}) else: # let's ask the user which live data providers to use data_feed_instances = interactive_config_build(lean_config, cloud_data_queue_handlers, logger, kwargs, - show_secrets, "Select a live data feed", multiple=True) + show_secrets, "Select a live data feed", multiple=True, no_browser=no_browser) for data_feed in data_feed_instances: settings = data_feed.get_settings() @@ -354,7 +359,6 @@ def deploy(project: str, live_holdings) logger.info(f"Live url: {live_algorithm.get_url()}") - if open_browser: from webbrowser import open open(live_algorithm.get_url()) diff --git a/lean/commands/live/deploy.py b/lean/commands/live/deploy.py index de7576ed..1fa26761 100644 --- a/lean/commands/live/deploy.py +++ b/lean/commands/live/deploy.py @@ -114,6 +114,10 @@ def _get_history_provider_name(data_provider_live_names: [str]) -> [str]: is_flag=True, default=False, help="Use the local LEAN engine image instead of pulling the latest version") +@option("--no-browser", + is_flag=True, + default=False, + help="Display OAuth URL without opening browser.") def deploy(project: Path, environment: Optional[str], output: Optional[Path], @@ -132,6 +136,7 @@ def deploy(project: Path, extra_config: Optional[Tuple[str, str]], extra_docker_config: Optional[str], no_update: bool, + no_browser: bool, **kwargs) -> None: """Start live trading a project locally using Docker. @@ -206,22 +211,22 @@ def deploy(project: Path, if brokerage: # user provided brokerage, check all arguments were provided brokerage_instance = non_interactive_config_build_for_name(lean_config, brokerage, cli_brokerages, kwargs, - logger, environment_name) + logger, environment_name, no_browser=no_browser) else: # let the user choose the brokerage brokerage_instance = interactive_config_build(lean_config, cli_brokerages, logger, kwargs, show_secrets, "Select a brokerage", multiple=False, - environment_name=environment_name) + environment_name=environment_name, no_browser=no_browser) if data_provider_live and len(data_provider_live) > 0: for data_feed_name in data_provider_live: data_feed = non_interactive_config_build_for_name(lean_config, data_feed_name, cli_data_queue_handlers, - kwargs, logger, environment_name) + kwargs, logger, environment_name, no_browser=no_browser) data_provider_live_instances.append(data_feed) else: data_provider_live_instances = interactive_config_build(lean_config, cli_data_queue_handlers, logger, kwargs, show_secrets, "Select a live data feed", multiple=True, - environment_name=environment_name) + environment_name=environment_name, no_browser=no_browser) # based on the live data providers we set up the history providers data_provider_live = [provider.get_name() for provider in data_provider_live_instances] @@ -229,7 +234,7 @@ def deploy(project: Path, data_provider_historical = "Local" data_downloader_instances = non_interactive_config_build_for_name(lean_config, data_provider_historical, cli_data_downloaders, kwargs, logger, - environment_name) + environment_name, no_browser=no_browser) if history_providers is None or len(history_providers) == 0: history_providers = _get_history_provider_name(data_provider_live) for history_provider in history_providers: diff --git a/lean/components/api/auth0_client.py b/lean/components/api/auth0_client.py index 1cc0b996..f6b42cbf 100644 --- a/lean/components/api/auth0_client.py +++ b/lean/components/api/auth0_client.py @@ -52,12 +52,13 @@ def read(self, brokerage_id: str) -> QCAuth0Authorization: return QCAuth0Authorization(authorization=None) @staticmethod - def authorize(brokerage_id: str, logger: Logger, project_id: int) -> None: + def authorize(brokerage_id: str, logger: Logger, project_id: int, no_browser: bool = False) -> None: """Starts the authorization process for a brokerage. :param brokerage_id: the id of the brokerage to start the authorization process for :param logger: the logger instance to use :param project_id: The local or cloud project_id + :param no_browser: whether to disable opening the browser """ from webbrowser import open @@ -65,6 +66,10 @@ def authorize(brokerage_id: str, logger: Logger, project_id: int) -> None: logger.info(f"Please open the following URL in your browser to authorize the LEAN CLI.") logger.info(full_url) + + if no_browser: + return + open(full_url) diff --git a/lean/components/util/auth0_helper.py b/lean/components/util/auth0_helper.py index d9fad98e..85906330 100644 --- a/lean/components/util/auth0_helper.py +++ b/lean/components/util/auth0_helper.py @@ -16,13 +16,14 @@ from lean.components.util.logger import Logger -def get_authorization(auth0_client: Auth0Client, brokerage_id: str, logger: Logger, project_id: int) -> QCAuth0Authorization: +def get_authorization(auth0_client: Auth0Client, brokerage_id: str, logger: Logger, project_id: int, no_browser: bool = False) -> QCAuth0Authorization: """Gets the authorization data for a brokerage, authorizing if necessary. :param auth0_client: An instance of Auth0Client, containing methods to interact with live/auth0/* API endpoints. :param brokerage_id: The ID of the brokerage to get the authorization data for. :param logger: An instance of Logger, handling all output printing. :param project_id: The local or cloud project_id. + :param no_browser: whether to disable opening the browser :return: The authorization data for the specified brokerage. """ from time import time, sleep @@ -32,7 +33,7 @@ def get_authorization(auth0_client: Auth0Client, brokerage_id: str, logger: Logg return data start_time = time() - auth0_client.authorize(brokerage_id, logger, project_id) + auth0_client.authorize(brokerage_id, logger, project_id, no_browser) # keep checking for new data every 5 seconds for 7 minutes while time() - start_time < 420: diff --git a/lean/components/util/json_modules_handler.py b/lean/components/util/json_modules_handler.py index 5b59ba47..84ce02a8 100644 --- a/lean/components/util/json_modules_handler.py +++ b/lean/components/util/json_modules_handler.py @@ -41,9 +41,9 @@ def build_and_configure_modules(target_modules: List[str], module_list: List[Jso def non_interactive_config_build_for_name(lean_config: Dict[str, Any], target_module_name: str, module_list: List[JsonModule], properties: Dict[str, Any], logger: Logger, - environment_name: str = None) -> JsonModule: + environment_name: str = None, no_browser: bool = False) -> JsonModule: return config_build_for_name(lean_config, target_module_name, module_list, properties, logger, interactive=False, - environment_name=environment_name) + environment_name=environment_name, no_browser=no_browser) def find_module(target_module_name: str, module_list: List[JsonModule], logger: Logger) -> JsonModule: @@ -79,17 +79,17 @@ def find_module(target_module_name: str, module_list: List[JsonModule], logger: def config_build_for_name(lean_config: Dict[str, Any], target_module_name: str, module_list: List[JsonModule], properties: Dict[str, Any], logger: Logger, interactive: bool, - environment_name: str = None) -> JsonModule: + environment_name: str = None, no_browser: bool = False) -> JsonModule: target_module = find_module(target_module_name, module_list, logger) target_module.config_build(lean_config, logger, interactive=interactive, properties=properties, - environment_name=environment_name) + environment_name=environment_name, no_browser=no_browser) _update_settings(logger, environment_name, target_module, lean_config) return target_module def interactive_config_build(lean_config: Dict[str, Any], models: [JsonModule], logger: Logger, user_provided_options: Dict[str, Any], show_secrets: bool, select_message: str, - multiple: bool, environment_name: str = None) -> [JsonModule]: + multiple: bool, environment_name: str = None, no_browser: bool = False) -> [JsonModule]: """Interactively configures the brokerage to use. :param lean_config: the LEAN configuration that should be used @@ -100,6 +100,7 @@ def interactive_config_build(lean_config: Dict[str, Any], models: [JsonModule], :param select_message: the user facing selection message :param multiple: true if multiple selections are allowed :param environment_name: the target environment name + :param no_browser: whether to disable opening the browser :return: the brokerage the user configured """ options = [Option(id=b, label=b.get_name()) for b in models] @@ -113,7 +114,7 @@ def interactive_config_build(lean_config: Dict[str, Any], models: [JsonModule], for module in modules: module.config_build(lean_config, logger, interactive=True, properties=user_provided_options, - hide_input=not show_secrets, environment_name=environment_name) + hide_input=not show_secrets, environment_name=environment_name, no_browser=no_browser) _update_settings(logger, environment_name, module, lean_config) if multiple: return modules diff --git a/lean/models/json_module.py b/lean/models/json_module.py index e998c2f1..7c5b2e55 100644 --- a/lean/models/json_module.py +++ b/lean/models/json_module.py @@ -195,7 +195,8 @@ def config_build(self, interactive: bool, properties: Dict[str, Any] = {}, hide_input: bool = False, - environment_name: str = None) -> 'JsonModule': + environment_name: str = None, + no_browser: bool = False) -> 'JsonModule': """Builds a new instance of this class, prompting the user for input when necessary. :param lean_config: the Lean configuration dict to read defaults from @@ -204,6 +205,7 @@ def config_build(self, :param properties: the properties that passed as options :param hide_input: whether to hide secrets inputs :param environment_name: the target environment name + :param no_browser: whether to disable opening the browser :return: self """ logger.debug(f'Configuring {self._display_name}') @@ -237,7 +239,7 @@ def config_build(self, configuration.require_project_id) logger.debug(f'project_id: {lean_config["project-id"]}') auth_authorizations = get_authorization(container.api_client.auth0, self._display_name.lower(), - logger, lean_config["project-id"]) + logger, lean_config["project-id"], no_browser=no_browser) logger.debug(f'auth: {auth_authorizations}') configuration._value = auth_authorizations.get_authorization_config_without_account() for inner_config in self._lean_configs: From 1c48781bab326d954d40fb602364cec42abefacc Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Tue, 20 Jan 2026 09:44:20 -0500 Subject: [PATCH 2/2] Improve help text --- lean/commands/cloud/live/deploy.py | 2 +- lean/commands/live/deploy.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lean/commands/cloud/live/deploy.py b/lean/commands/cloud/live/deploy.py index eaa96dbf..5e851b30 100644 --- a/lean/commands/cloud/live/deploy.py +++ b/lean/commands/cloud/live/deploy.py @@ -205,7 +205,7 @@ def _configure_auto_restart(logger: Logger) -> bool: @option("--no-browser", is_flag=True, default=False, - help="Display OAuth URL without opening browser.") + help="Display OAuth URL without opening the browser.") def deploy(project: str, brokerage: str, data_provider_live: Optional[str], diff --git a/lean/commands/live/deploy.py b/lean/commands/live/deploy.py index 1fa26761..bc4e753f 100644 --- a/lean/commands/live/deploy.py +++ b/lean/commands/live/deploy.py @@ -117,7 +117,7 @@ def _get_history_provider_name(data_provider_live_names: [str]) -> [str]: @option("--no-browser", is_flag=True, default=False, - help="Display OAuth URL without opening browser.") + help="Display OAuth URL without opening the browser.") def deploy(project: Path, environment: Optional[str], output: Optional[Path],