From ca659d2e339a1a4f11d99852debcd336a58fdfa2 Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Mon, 22 Sep 2025 17:32:15 -0500 Subject: [PATCH 1/3] Initial solution --- lean/commands/cloud/push.py | 11 +++++++---- lean/components/api/project_client.py | 9 +++++++-- lean/components/cloud/push_manager.py | 18 +++++++++-------- tests/commands/cloud/test_push.py | 28 ++++++++++++++++++++++++++- 4 files changed, 51 insertions(+), 15 deletions(-) diff --git a/lean/commands/cloud/push.py b/lean/commands/cloud/push.py index da115818..b097098c 100644 --- a/lean/commands/cloud/push.py +++ b/lean/commands/cloud/push.py @@ -34,7 +34,10 @@ @option("--key", type=PathParameter(exists=True, file_okay=True, dir_okay=False), help="Path to the encryption key to use") -def push(project: Optional[Path], encrypt: Optional[bool], decrypt: Optional[bool], key: Optional[Path]) -> None: +@option("--force", + is_flag=True, default=False, + help="Force push even if there's a lock conflict") +def push(project: Optional[Path], encrypt: Optional[bool], decrypt: Optional[bool], key: Optional[Path], force: Optional[bool]) -> None: """Push local projects to QuantConnect. This command overrides the content of cloud files with the content of their respective local counterparts. @@ -61,11 +64,11 @@ def push(project: Optional[Path], encrypt: Optional[bool], decrypt: Optional[boo if encrypt and key is not None: from lean.components.util.encryption_helper import validate_encryption_key_registered_with_cloud - validate_encryption_key_registered_with_cloud(key, container.organization_manager, container.api_client) + validate_encryption_key_registered_with_cloud(key, container.organization_manager, container.api_client) - push_manager.push_project(project, encryption_action, key) + push_manager.push_project(project, encryption_action, key, force) else: if key is not None: raise RuntimeError(f"Encryption key can only be specified when pushing a single project.") projects_to_push = [p.parent for p in Path.cwd().rglob(PROJECT_CONFIG_FILE_NAME)] - push_manager.push_projects(projects_to_push, [], encryption_action, key) + push_manager.push_projects(projects_to_push, [], encryption_action, key, force) diff --git a/lean/components/api/project_client.py b/lean/components/api/project_client.py index b4cbbf70..fa359e44 100644 --- a/lean/components/api/project_client.py +++ b/lean/components/api/project_client.py @@ -83,7 +83,8 @@ def update(self, python_venv: Optional[int] = None, files: Optional[List[Dict[str, str]]] = None, libraries: Optional[List[int]] = None, - encryption_key: Optional[str] = None) -> None: + encryption_key: Optional[str] = None, + code_source_id: Optional[str] = "cli") -> None: """Updates an existing project. :param project_id: the id of the project to update @@ -94,6 +95,7 @@ def update(self, :param python_venv: the python venv id for the project, or None if the python venv shouldn't be changed :param files: the list of files for the project :param libraries: the list of libraries referenced by the project + :param code_source_id: the source of the code changes (e.g., "cli") """ request_parameters = { "projectId": project_id @@ -136,7 +138,10 @@ def update(self, if encryption_key is not None: request_parameters["encryptionKey"] = encryption_key - + + if code_source_id is not None: + request_parameters["codeSourceId"] = code_source_id + self._api.post("projects/update", request_parameters, data_as_json=False) def delete(self, project_id: int) -> None: diff --git a/lean/components/cloud/push_manager.py b/lean/components/cloud/push_manager.py index 3f171de6..8d4ff277 100644 --- a/lean/components/cloud/push_manager.py +++ b/lean/components/cloud/push_manager.py @@ -46,7 +46,7 @@ def __init__(self, self._organization_manager = organization_manager self._cloud_projects = [] - def push_project(self, project: Path, encryption_action: Optional[ActionType]=None, encryption_key: Optional[Path]=None) -> None: + def push_project(self, project: Path, encryption_action: Optional[ActionType]=None, encryption_key: Optional[Path]=None, force: Optional[bool]=False) -> None: """Pushes the given project from the local drive to the cloud. It will also push every library referenced by the project and add or remove references. @@ -54,9 +54,9 @@ def push_project(self, project: Path, encryption_action: Optional[ActionType]=No :param project: path to the directory containing the local project that needs to be pushed """ libraries = self._project_manager.get_project_libraries(project) - self.push_projects([project], libraries, encryption_action, encryption_key) + self.push_projects([project], libraries, encryption_action, encryption_key, force) - def push_projects(self, projects_to_push: List[Path], associated_libraries_to_push: Optional[List[Path]]=[], encryption_action: Optional[ActionType]=None, encryption_key: Optional[Path]=None) -> None: + def push_projects(self, projects_to_push: List[Path], associated_libraries_to_push: Optional[List[Path]]=[], encryption_action: Optional[ActionType]=None, encryption_key: Optional[Path]=None, force: Optional[bool]=False) -> None: """Pushes the given projects from the local drive to the cloud. It will also push every library referenced by each project and add or remove references. @@ -78,7 +78,7 @@ def push_projects(self, projects_to_push: List[Path], associated_libraries_to_pu relative_path = path.relative_to(Path.cwd()) try: self._logger.info(f"[{index}/{len(all_projects_to_push)}] Pushing '{relative_path}'") - self._push_project(path, organization_id, encryption_action_value, encryption_key_value) + self._push_project(path, organization_id, encryption_action_value, encryption_key_value, force=force) except Exception as ex: from traceback import format_exc self._logger.debug(format_exc().strip()) @@ -95,7 +95,7 @@ def _get_local_libraries_cloud_ids(self, project_dir: Path) -> List[int]: return local_libraries_cloud_ids - def _push_project(self, project_path: Path, organization_id: str, encryption_action: Optional[ActionType], encryption_key: Optional[Path], suggested_rename_path: Path = None) -> None: + def _push_project(self, project_path: Path, organization_id: str, encryption_action: Optional[ActionType], encryption_key: Optional[Path], force: Optional[bool], suggested_rename_path: Path = None) -> None: """Pushes a single local project to the cloud. Raises an error with a descriptive message if the project cannot be pushed. @@ -111,7 +111,6 @@ def _push_project(self, project_path: Path, organization_id: str, encryption_act if suggested_rename_path and suggested_rename_path != project_path: potential_new_name = suggested_rename_path.relative_to(Path.cwd()).as_posix() - project_config = self._project_config_manager.get_project_config(project_path) cloud_id = project_config.get("cloud-id") local_encryption_state = project_config.get("encrypted", False) @@ -163,7 +162,7 @@ def _push_project(self, project_path: Path, organization_id: str, encryption_act encryption_key = local_encryption_key encryption_action = ActionType.ENCRYPT if local_encryption_state else ActionType.DECRYPT # Finalize pushing by updating locally modified metadata, files and libraries - self._push_metadata(project_path, cloud_project, encryption_action, encryption_key) + self._push_metadata(project_path, cloud_project, encryption_action, encryption_key, force) def _get_files(self, project: Path, encryption_action: Optional[ActionType], encryption_key: Optional[Path]) -> List[Dict[str, str]]: """Pushes the files of a local project to the cloud. @@ -193,7 +192,7 @@ def _get_files(self, project: Path, encryption_action: Optional[ActionType], enc return files - def _push_metadata(self, project: Path, cloud_project: QCProject, encryption_action: Optional[ActionType], encryption_key: Optional[Path]) -> None: + def _push_metadata(self, project: Path, cloud_project: QCProject, encryption_action: Optional[ActionType], encryption_key: Optional[Path], force: Optional[bool]) -> None: """Pushes local project description and parameters to the cloud. Does nothing if the cloud is already up-to-date. @@ -260,6 +259,8 @@ def _push_metadata(self, project: Path, cloud_project: QCProject, encryption_act if "encryption_key" in update_args: del update_args["encryption_key"] + if not force: + update_args["code_source_id"] = "cli" updated_keys = list(update_args) if len(updated_keys) == 1: updated_keys_str = updated_keys[0] @@ -267,6 +268,7 @@ def _push_metadata(self, project: Path, cloud_project: QCProject, encryption_act updated_keys_str = " and ".join(updated_keys) else: updated_keys_str = ", ".join(updated_keys[:-1]) + f", and {updated_keys[-1]}" + self._logger.info(f"Successfully updated {updated_keys_str} for '{cloud_project.name}'") def _get_cloud_project(self, project_id: int, organization_id: str) -> QCProject: diff --git a/tests/commands/cloud/test_push.py b/tests/commands/cloud/test_push.py index 9b8d48cb..36f4ab3d 100644 --- a/tests/commands/cloud/test_push.py +++ b/tests/commands/cloud/test_push.py @@ -82,7 +82,7 @@ def test_cloud_push_pushes_single_project_when_project_option_given() -> None: assert result.exit_code == 0 - push_manager.push_project.assert_called_once_with(Path.cwd() / "Python Project", None, None) + push_manager.push_project.assert_called_once_with(Path.cwd() / "Python Project", None, None, False) def test_cloud_push_aborts_when_given_directory_is_not_lean_project() -> None: @@ -469,3 +469,29 @@ def _get_expected_encrypted_files_content() -> dict: UGw0ehtO8qY5FmPGcUlkBGuqmd7r6aLE4mosoZrc/UyZb+clWNYJITRLFJbQpWm3EU/Xrt5UM8uWwEdV bFWAAkX56MyDHwJefC1nkA==""" } + +def test_cloud_push_sets_code_source_id_to_cli() -> None: + create_fake_lean_cli_directory() + project_path = Path.cwd() / "Python Project" + + api_client = mock.Mock() + cloud_project = create_api_project(1, "Python Project") + api_client.projects.create = mock.MagicMock(return_value=cloud_project) + api_client.files.get_all = mock.MagicMock(return_value=[ + QCFullFile(name="file.py", content="print(123)", modified=datetime.now(), isLibrary=False) + ]) + + init_container(api_client_to_use=api_client) + + result = CliRunner().invoke(lean, ["cloud", "push", "--project", project_path]) + + assert result.exit_code == 0 + expected_arguments = { + "name": "Python Project", + "description": "", + "files": mock.ANY, + "libraries": [], + "encryption_key": "", + "codeSourceId": "cli" + } + api_client.projects.update.assert_called_once_with(1, **expected_arguments) From 98f512eca8589b2b5fd4ea4d125d20603e34fa78 Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Tue, 23 Sep 2025 10:41:30 -0500 Subject: [PATCH 2/3] Add unit tests --- lean/components/cloud/push_manager.py | 6 +++-- tests/commands/cloud/test_push.py | 35 ++++++++++++++++++++------- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/lean/components/cloud/push_manager.py b/lean/components/cloud/push_manager.py index 8d4ff277..5ad30a46 100644 --- a/lean/components/cloud/push_manager.py +++ b/lean/components/cloud/push_manager.py @@ -254,13 +254,15 @@ def _push_metadata(self, project: Path, cloud_project: QCProject, encryption_act encryption_key_id = get_project_key_hash(encryption_key) update_args["encryption_key"] = encryption_key_id + if not force: + update_args["code_source_id"] = "cli" + if update_args != {}: self._api_client.projects.update(cloud_project.projectId, **update_args) if "encryption_key" in update_args: del update_args["encryption_key"] - if not force: - update_args["code_source_id"] = "cli" + updated_keys = list(update_args) if len(updated_keys) == 1: updated_keys_str = updated_keys[0] diff --git a/tests/commands/cloud/test_push.py b/tests/commands/cloud/test_push.py index 36f4ab3d..e8765f50 100644 --- a/tests/commands/cloud/test_push.py +++ b/tests/commands/cloud/test_push.py @@ -15,6 +15,7 @@ from unittest import mock from datetime import datetime +import pytest from click.testing import CliRunner from lean.commands import lean @@ -203,7 +204,8 @@ def test_cloud_push_sends_encrypted_files_and_turns_on_encryption_with_encrypted "description": "", "files":[{'name': name, 'content': content} for name, content in expected_encrypted_files.items()], "libraries": [], - "encryption_key": key_hash + "encryption_key": key_hash, + "code_source_id": 'cli' } api_client.projects.update.assert_called_once_with(1, **expected_arguments) @@ -272,7 +274,8 @@ def test_cloud_push_sends_decrypted_files_and_turns_off_encryption_with_decrypte "description": "", "files": [{'name': 'main.py', 'content': '# region imports\nfrom AlgorithmImports import *\n# endregion\n\nclass PythonProject(QCAlgorithm):\n\n def initialize(self):\n # Locally Lean installs free sample data, to download more data please visit https://www.quantconnect.com/docs/v2/lean-cli/datasets/downloading-data\n self.set_start_date(2013, 10, 7) # Set Start Date\n self.set_end_date(2013, 10, 11) # Set End Date\n self.set_cash(100000) # Set Strategy Cash\n self.add_equity("SPY", Resolution.MINUTE)\n\n def on_data(self, data: Slice):\n """on_data event is the primary entry point for your algorithm. Each new data point will be pumped in here.\n Arguments:\n data: Slice object keyed by symbol containing the stock data\n """\n if not self.portfolio.invested:\n self.set_holdings("SPY", 1)\n self.debug("Purchased Stock")\n'}, {'name': 'research.ipynb', 'content': '{\n "cells": [\n {\n "cell_type": "markdown",\n "metadata": {},\n "source": [\n "![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\\n",\n "
"\n ]\n },\n {\n "cell_type": "code",\n "execution_count": null,\n "metadata": {},\n "outputs": [],\n "source": [\n "# QuantBook Analysis Tool \\n",\n "# For more information see [https://www.quantconnect.com/docs/v2/our-platform/research/getting-started]\\n",\n "qb = QuantBook()\\n",\n "spy = qb.add_equity(\\"SPY\\")\\n",\n "# Locally Lean installs free sample data, to download more data please visit https://www.quantconnect.com/docs/v2/lean-cli/datasets/downloading-data \\n",\n "qb.set_start_date(2013, 10, 11)\\n",\n "history = qb.history(qb.securities.keys(), 360, Resolution.DAILY)\\n",\n "\\n",\n "# Indicator Analysis\\n",\n "bbdf = qb.indicator(BollingerBands(30, 2), spy.symbol, 360, Resolution.DAILY)\\n",\n "bbdf.drop(\'standarddeviation\', axis=1).plot()"\n ]\n }\n ],\n "metadata": {\n "kernelspec": {\n "display_name": "Python 3",\n "language": "python",\n "name": "python3"\n }\n },\n "nbformat": 4,\n "nbformat_minor": 2\n}\n'}], "libraries": [], - "encryption_key": '' + "encryption_key": '', + "code_source_id": 'cli' } api_client.projects.update.assert_called_once_with(1, **expected_arguments) @@ -314,7 +317,8 @@ def test_cloud_push_sends_decrypted_files_when_project_in_encrypted_state_with_d "description": "", "files": [{'name': 'main.py', 'content': '# region imports\nfrom AlgorithmImports import *\n# endregion\n\nclass PythonProject(QCAlgorithm):\n\n def initialize(self):\n # Locally Lean installs free sample data, to download more data please visit https://www.quantconnect.com/docs/v2/lean-cli/datasets/downloading-data\n self.set_start_date(2013, 10, 7) # Set Start Date\n self.set_end_date(2013, 10, 11) # Set End Date\n self.set_cash(100000) # Set Strategy Cash\n self.add_equity("SPY", Resolution.MINUTE)\n\n def on_data(self, data: Slice):\n """on_data event is the primary entry point for your algorithm. Each new data point will be pumped in here.\n Arguments:\n data: Slice object keyed by symbol containing the stock data\n """\n if not self.portfolio.invested:\n self.set_holdings("SPY", 1)\n self.debug("Purchased Stock")\n'}, {'name': 'research.ipynb', 'content': '{\n "cells": [\n {\n "cell_type": "markdown",\n "metadata": {},\n "source": [\n "![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\\n",\n "
"\n ]\n },\n {\n "cell_type": "code",\n "execution_count": null,\n "metadata": {},\n "outputs": [],\n "source": [\n "# QuantBook Analysis Tool \\n",\n "# For more information see [https://www.quantconnect.com/docs/v2/our-platform/research/getting-started]\\n",\n "qb = QuantBook()\\n",\n "spy = qb.add_equity(\\"SPY\\")\\n",\n "# Locally Lean installs free sample data, to download more data please visit https://www.quantconnect.com/docs/v2/lean-cli/datasets/downloading-data \\n",\n "qb.set_start_date(2013, 10, 11)\\n",\n "history = qb.history(qb.securities.keys(), 360, Resolution.DAILY)\\n",\n "\\n",\n "# Indicator Analysis\\n",\n "bbdf = qb.indicator(BollingerBands(30, 2), spy.symbol, 360, Resolution.DAILY)\\n",\n "bbdf.drop(\'standarddeviation\', axis=1).plot()"\n ]\n }\n ],\n "metadata": {\n "kernelspec": {\n "display_name": "Python 3",\n "language": "python",\n "name": "python3"\n }\n },\n "nbformat": 4,\n "nbformat_minor": 2\n}\n'}], "libraries": [], - "encryption_key": '' + "encryption_key": '', + "code_source_id": 'cli' } api_client.projects.update.assert_called_once_with(1, **expected_arguments) @@ -359,7 +363,8 @@ def test_cloud_push_encrypts_when_local_files_in_encrypted_state_and_cloud_proje "description": "", "files":[{'name': name, 'content': content} for name, content in expected_encrypted_files.items()], "libraries": [], - "encryption_key": key_hash + "encryption_key": key_hash, + "code_source_id": 'cli' } api_client.projects.update.assert_called_once_with(1, **expected_arguments) @@ -388,7 +393,8 @@ def test_cloud_push_decrypted_when_local_files_in_decrypted_state_and_cloud_proj "description": "", "files": [{'name': 'main.py', 'content': '# region imports\nfrom AlgorithmImports import *\n# endregion\n\nclass PythonProject(QCAlgorithm):\n\n def initialize(self):\n # Locally Lean installs free sample data, to download more data please visit https://www.quantconnect.com/docs/v2/lean-cli/datasets/downloading-data\n self.set_start_date(2013, 10, 7) # Set Start Date\n self.set_end_date(2013, 10, 11) # Set End Date\n self.set_cash(100000) # Set Strategy Cash\n self.add_equity("SPY", Resolution.MINUTE)\n\n def on_data(self, data: Slice):\n """on_data event is the primary entry point for your algorithm. Each new data point will be pumped in here.\n Arguments:\n data: Slice object keyed by symbol containing the stock data\n """\n if not self.portfolio.invested:\n self.set_holdings("SPY", 1)\n self.debug("Purchased Stock")\n'}, {'name': 'research.ipynb', 'content': '{\n "cells": [\n {\n "cell_type": "markdown",\n "metadata": {},\n "source": [\n "![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\\n",\n "
"\n ]\n },\n {\n "cell_type": "code",\n "execution_count": null,\n "metadata": {},\n "outputs": [],\n "source": [\n "# QuantBook Analysis Tool \\n",\n "# For more information see [https://www.quantconnect.com/docs/v2/our-platform/research/getting-started]\\n",\n "qb = QuantBook()\\n",\n "spy = qb.add_equity(\\"SPY\\")\\n",\n "# Locally Lean installs free sample data, to download more data please visit https://www.quantconnect.com/docs/v2/lean-cli/datasets/downloading-data \\n",\n "qb.set_start_date(2013, 10, 11)\\n",\n "history = qb.history(qb.securities.keys(), 360, Resolution.DAILY)\\n",\n "\\n",\n "# Indicator Analysis\\n",\n "bbdf = qb.indicator(BollingerBands(30, 2), spy.symbol, 360, Resolution.DAILY)\\n",\n "bbdf.drop(\'standarddeviation\', axis=1).plot()"\n ]\n }\n ],\n "metadata": {\n "kernelspec": {\n "display_name": "Python 3",\n "language": "python",\n "name": "python3"\n }\n },\n "nbformat": 4,\n "nbformat_minor": 2\n}\n'}], "libraries": [], - "encryption_key": '' + "encryption_key": '', + "code_source_id": 'cli' } api_client.projects.update.assert_called_once_with(1, **expected_arguments) @@ -470,7 +476,11 @@ def _get_expected_encrypted_files_content() -> dict: bFWAAkX56MyDHwJefC1nkA==""" } -def test_cloud_push_sets_code_source_id_to_cli() -> None: +@pytest.mark.parametrize("force_flag, expected_code_source_id", [ + (False, "cli"), + (True, None), +]) +def test_cloud_push_code_source_id_behavior(force_flag: bool, expected_code_source_id: str) -> None: create_fake_lean_cli_directory() project_path = Path.cwd() / "Python Project" @@ -483,15 +493,22 @@ def test_cloud_push_sets_code_source_id_to_cli() -> None: init_container(api_client_to_use=api_client) - result = CliRunner().invoke(lean, ["cloud", "push", "--project", project_path]) + command = ["cloud", "push", "--project", project_path] + if force_flag: + command.append("--force") + result = CliRunner().invoke(lean, command) assert result.exit_code == 0 + expected_arguments = { "name": "Python Project", "description": "", - "files": mock.ANY, + "files": [{'name': 'main.py', 'content': '# region imports\nfrom AlgorithmImports import *\n# endregion\n\nclass PythonProject(QCAlgorithm):\n\n def initialize(self):\n # Locally Lean installs free sample data, to download more data please visit https://www.quantconnect.com/docs/v2/lean-cli/datasets/downloading-data\n self.set_start_date(2013, 10, 7) # Set Start Date\n self.set_end_date(2013, 10, 11) # Set End Date\n self.set_cash(100000) # Set Strategy Cash\n self.add_equity("SPY", Resolution.MINUTE)\n\n def on_data(self, data: Slice):\n """on_data event is the primary entry point for your algorithm. Each new data point will be pumped in here.\n Arguments:\n data: Slice object keyed by symbol containing the stock data\n """\n if not self.portfolio.invested:\n self.set_holdings("SPY", 1)\n self.debug("Purchased Stock")\n'}, {'name': 'research.ipynb', 'content': '{\n "cells": [\n {\n "cell_type": "markdown",\n "metadata": {},\n "source": [\n "![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\\n",\n "
"\n ]\n },\n {\n "cell_type": "code",\n "execution_count": null,\n "metadata": {},\n "outputs": [],\n "source": [\n "# QuantBook Analysis Tool \\n",\n "# For more information see [https://www.quantconnect.com/docs/v2/our-platform/research/getting-started]\\n",\n "qb = QuantBook()\\n",\n "spy = qb.add_equity(\\"SPY\\")\\n",\n "# Locally Lean installs free sample data, to download more data please visit https://www.quantconnect.com/docs/v2/lean-cli/datasets/downloading-data \\n",\n "qb.set_start_date(2013, 10, 11)\\n",\n "history = qb.history(qb.securities.keys(), 360, Resolution.DAILY)\\n",\n "\\n",\n "# Indicator Analysis\\n",\n "bbdf = qb.indicator(BollingerBands(30, 2), spy.symbol, 360, Resolution.DAILY)\\n",\n "bbdf.drop(\'standarddeviation\', axis=1).plot()"\n ]\n }\n ],\n "metadata": {\n "kernelspec": {\n "display_name": "Python 3",\n "language": "python",\n "name": "python3"\n }\n },\n "nbformat": 4,\n "nbformat_minor": 2\n}\n'}], "libraries": [], "encryption_key": "", - "codeSourceId": "cli" } + + if expected_code_source_id is not None: + expected_arguments["code_source_id"] = expected_code_source_id + api_client.projects.update.assert_called_once_with(1, **expected_arguments) From 8dc45c9babb968550694cc7754a060856600f2fd Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Tue, 23 Sep 2025 16:24:08 -0500 Subject: [PATCH 3/3] Add missing force parameter in recursive _push_project call --- lean/components/api/project_client.py | 2 +- lean/components/cloud/push_manager.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lean/components/api/project_client.py b/lean/components/api/project_client.py index fa359e44..e9a9adba 100644 --- a/lean/components/api/project_client.py +++ b/lean/components/api/project_client.py @@ -84,7 +84,7 @@ def update(self, files: Optional[List[Dict[str, str]]] = None, libraries: Optional[List[int]] = None, encryption_key: Optional[str] = None, - code_source_id: Optional[str] = "cli") -> None: + code_source_id: Optional[str] = None) -> None: """Updates an existing project. :param project_id: the id of the project to update diff --git a/lean/components/cloud/push_manager.py b/lean/components/cloud/push_manager.py index 5ad30a46..55d7b112 100644 --- a/lean/components/cloud/push_manager.py +++ b/lean/components/cloud/push_manager.py @@ -148,7 +148,7 @@ def _push_project(self, project_path: Path, organization_id: str, encryption_act if cloud_project.name != project_name: # cloud project name was changed. Repeat steps to validate the new name locally. self._logger.info(f"Received new name '{cloud_project.name}' for project '{project_name}' from QuantConnect.com") - self._push_project(project_path, organization_id, encryption_action, encryption_key, Path.cwd() / cloud_project.name) + self._push_project(project_path, organization_id, encryption_action, encryption_key, force, Path.cwd() / cloud_project.name) return self._cloud_projects.append(cloud_project) @@ -256,7 +256,6 @@ def _push_metadata(self, project: Path, cloud_project: QCProject, encryption_act if not force: update_args["code_source_id"] = "cli" - if update_args != {}: self._api_client.projects.update(cloud_project.projectId, **update_args)