Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 23 additions & 7 deletions lean/components/config/project_config_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ def get_project_id_from_project_config(self, project_directory: Path) -> int:
it indicates the directory is unavailable.

Returns:
int: Returns the 'cloud-id' if available.
If 'cloud-id' is missing, returns the negative of 'local-id'.
int: Returns the 'cloud-id' if available and the project it refers to still exists in the cloud.
Otherwise returns the negative of 'local-id'.
If neither is found nor if project_directory is None, returns -1.
"""
if project_directory is None:
Expand All @@ -87,14 +87,30 @@ def get_project_id_from_project_config(self, project_directory: Path) -> int:
project_config = self.get_project_config(project_directory)

cloud_id = project_config.get("cloud-id")
if cloud_id is not None:
if cloud_id is not None and self._cloud_project_exists(cloud_id):
return cloud_id

local_id = project_config.get("local-id")
if local_id is not None:
return -local_id # Local ID must be negative.
return -abs(int(project_config.get("local-id", 1)))

return -1 # Return -1 if no valid IDs are found
def _cloud_project_exists(self, cloud_id: int) -> bool:
"""Returns whether a project still exists in the cloud.

A 'cloud-id' left behind by a project that no longer exists in the cloud identifies nothing,
and neither does one this user cannot reach, so it cannot be used as the id of the project that runs.

:param cloud_id: the cloud id to look up
:return: True if the cloud project exists, False if it does not or if it could not be looked up
"""
from lean.container import container

try:
organization_id = container.organization_manager.try_get_working_organization_id()
container.api_client.projects.get(cloud_id, organization_id)
return True
except Exception as e:
container.logger.debug(f"ProjectConfigManager._cloud_project_exists(): cloud project {cloud_id} "
f"is unavailable, falling back to the local id: {e}")
return False

def get_latest_live_directory(self, project_directory: Path) -> Path:
"""Returns the path of the latest live directory.
Expand Down
16 changes: 16 additions & 0 deletions tests/components/config/test_lean_config_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from lean.components.util.xml_manager import XMLManager
from lean.container import container
from lean.models.utils import DebuggingMethod
from tests.conftest import initialize_container
from tests.test_helpers import create_fake_lean_cli_directory


Expand Down Expand Up @@ -388,6 +389,21 @@ def test_get_complete_lean_config_returns_dict_with_all_keys_removed_in_clean_le
assert key in config


def test_get_complete_lean_config_sets_the_project_id_of_the_project_being_run() -> None:
create_fake_lean_cli_directory()
# the id of the project being run is only used when the Lean config does not set one
(Path.cwd() / "lean.json").write_text('{"data-folder": "data", "organization-id": "abc"}', encoding="utf-8")
initialize_container()

project_directory = Path.cwd() / "Python Project"
ProjectConfigManager(XMLManager()).get_project_config(project_directory).set("cloud-id", 1234)

manager = _create_lean_config_manager()
config = manager.get_complete_lean_config("backtesting", project_directory / "main.py", None)

assert config["project-id"] == 1234


def test_get_complete_lean_config_sets_environment() -> None:
create_fake_lean_cli_directory()

Expand Down
46 changes: 46 additions & 0 deletions tests/components/config/test_project_config_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
# limitations under the License.

from pathlib import Path
from unittest import mock

from lean.components.config.project_config_manager import ProjectConfigManager
from lean.models.errors import RequestFailedError
from tests.conftest import initialize_container
from lean.components.util.xml_manager import XMLManager
from lean.models.utils import CSharpLibrary
from tests.test_helpers import create_fake_lean_cli_directory
Expand Down Expand Up @@ -118,3 +121,46 @@ def test_get_csharp_libraries_skips_invalid_package_reference_tags() -> None:

assert len(libraries) == 1
assert CSharpLibrary(name="QuantConnect.Lean", version="2.5.11586") in libraries


def test_get_project_id_from_project_config_returns_negative_local_id() -> None:
create_fake_lean_cli_directory()

project_config_manager = ProjectConfigManager(XMLManager())
project_directory = Path.cwd() / "Python Project"
local_id = project_config_manager.get_local_id(project_directory)

assert project_config_manager.get_project_id_from_project_config(project_directory) == -local_id


def test_get_project_id_from_project_config_returns_cloud_id_when_the_cloud_project_exists() -> None:
create_fake_lean_cli_directory()
initialize_container()

project_config_manager = ProjectConfigManager(XMLManager())
project_directory = Path.cwd() / "Python Project"
project_config_manager.get_project_config(project_directory).set("cloud-id", 1234)

assert project_config_manager.get_project_id_from_project_config(project_directory) == 1234


def test_get_project_id_from_project_config_falls_back_to_local_id_when_the_cloud_project_is_gone() -> None:
"""A cloud-id left behind by a project deleted in the cloud identifies nothing."""
create_fake_lean_cli_directory()
container = initialize_container()
container.api_client.projects.get.side_effect = RequestFailedError(mock.MagicMock())

project_config_manager = ProjectConfigManager(XMLManager())
project_directory = Path.cwd() / "Python Project"
project_config_manager.get_project_config(project_directory).set("cloud-id", 1234)
local_id = project_config_manager.get_local_id(project_directory)

assert project_config_manager.get_project_id_from_project_config(project_directory) == -local_id


def test_get_project_id_from_project_config_returns_minus_one_without_a_project_directory() -> None:
create_fake_lean_cli_directory()

project_config_manager = ProjectConfigManager(XMLManager())

assert project_config_manager.get_project_id_from_project_config(None) == -1
Loading