Skip to content

Commit

Permalink
Merge pull request #222 from rjra2611/feature-optimize-cloud-pull-whe…
Browse files Browse the repository at this point in the history
…n-project-rename

Feature optimize cloud pull when project rename
  • Loading branch information
Martin-Molinero authored Nov 9, 2022
2 parents d58b906 + 07bf88e commit f247f08
Show file tree
Hide file tree
Showing 8 changed files with 29 additions and 22 deletions.
5 changes: 3 additions & 2 deletions lean/commands/create_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from lean.models.api import QCLanguage
from lean.models.errors import MoreInfoError
from lean.components.util.name_extraction import convert_to_class_name
from lean.components import forbidden_characters

DEFAULT_PYTHON_MAIN = '''
from AlgorithmImports import *
Expand Down Expand Up @@ -293,8 +294,8 @@ def create_project(name: str, language: str) -> None:
full_path = Path.cwd() / name

if not container.path_manager.is_path_valid(full_path):
raise MoreInfoError(f"'{name}' is not a valid path",
"https://www.lean.io/docs/v2/lean-cli/key-concepts/troubleshooting#02-Common-Errors")
raise MoreInfoError(f"'{name}' is not a valid path, can not contain [ {', '.join(forbidden_characters)} ], start with dot '.' or empty char ' '",
"https://www.lean.io/docs/v2/lean-cli/key-concepts/troubleshooting#02-Common-Errors")

is_library_project = False
try:
Expand Down
2 changes: 2 additions & 0 deletions lean/components/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@
reserved_names = ["CON", "PRN", "AUX", "NUL",
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"]

forbidden_characters = ["\\", ":", "*", "?", '"', "<", ">", "|"]
12 changes: 7 additions & 5 deletions lean/components/cloud/pull_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,13 @@ def _pull_project(self, project: QCProject) -> Path:
project.name = local_project_name

# rename project on disk if we find a directory with the old name (invalid/renamed name)
project_path_on_disk = self._project_manager.try_get_project_path_by_cloud_id(project.projectId)
if project_path_on_disk:
project_name_on_disk = project_path_on_disk.relative_to(Path.cwd()).as_posix()
if project_name_on_disk != project.name:
self._project_manager.rename_project_and_contents(project_path_on_disk, Path.cwd() / project.name)
# only check for old directory if expected directory does not exist
if not local_project_path.exists():
project_path_on_disk = self._project_manager.try_get_project_path_by_cloud_id(project.projectId)
if project_path_on_disk:
project_name_on_disk = project_path_on_disk.relative_to(Path.cwd()).as_posix()
if project_name_on_disk != project.name:
self._project_manager.rename_project_and_contents(project_path_on_disk, Path.cwd() / project.name)

# Pull the cloud files to the local drive
self._pull_files(project, local_project_path)
Expand Down
7 changes: 4 additions & 3 deletions lean/components/cloud/push_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,11 @@ def _push_metadata(self, project: Path, cloud_project: QCProject) -> None:
update_args = {}

expected_correct_project_name = project.relative_to(Path.cwd()).as_posix()

# update project name in cloud in case it was incorrect and renamed locally otherwise update the same name
update_args["name"] = expected_correct_project_name
if cloud_project.name != expected_correct_project_name:
# update project name in cloud
update_args["name"] = expected_correct_project_name
self._logger.info(f"Renaming project in cloud from '{cloud_project.name}' to '{expected_correct_project_name}'")
self._logger.info(f"Renaming project in cloud from '{cloud_project.name}' to '{expected_correct_project_name}'")

if local_description != cloud_description:
update_args["description"] = local_description
Expand Down
4 changes: 2 additions & 2 deletions lean/components/util/path_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# limitations under the License.

from pathlib import Path
from lean.components import reserved_names
from lean.components import reserved_names, forbidden_characters
from lean.components.util.platform_manager import PlatformManager

class PathManager:
Expand Down Expand Up @@ -65,7 +65,7 @@ def is_path_valid(self, path: Path) -> bool:
if component.upper() == reserved_name or component.upper().startswith(reserved_name + "."):
return False

for forbidden_character in [":", "*", "?", '"', "<", ">", "|"]:
for forbidden_character in forbidden_characters:
if forbidden_character in component:
return False
return True
7 changes: 3 additions & 4 deletions lean/components/util/project_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from lean.constants import PROJECT_CONFIG_FILE_NAME
from lean.models.api import QCLanguage, QCProject
from lean.models.utils import LeanLibraryReference

from lean.components import forbidden_characters

class ProjectManager:
"""The ProjectManager class provides utilities for handling a single project."""
Expand Down Expand Up @@ -248,11 +248,11 @@ def rename_project_and_contents(self, old_path: Path, new_path: Path,) -> None:
:param old_path: the local project to rename
:param new_path: the new path of the project
"""
from shutil import move
if not old_path.exists():
raise RuntimeError(f"Failed to rename project. Could not find the specified path {old_path}.")
if old_path == new_path:
return
from shutil import move
move(old_path, new_path)
self._rename_csproj_file(new_path)

Expand Down Expand Up @@ -366,7 +366,6 @@ def _format_local_path(self, cloud_path: str) -> str:
# Windows, \":*?"<>| are forbidden
# Windows, \ is a path separator, but \ is not a path separator on QuantConnect
# We follow the rules of windows for every OS
forbidden_characters = ["\\", ":", "*", "?", '"', "<", ">", "|"]

for forbidden_character in forbidden_characters:
cloud_path = cloud_path.replace(forbidden_character, " ")
Expand Down Expand Up @@ -705,13 +704,13 @@ def _rename_csproj_file(self, project_path: Path) -> None:
:param project_path: the local project path
"""
from shutil import move
csproj_file = next(project_path.glob("*.csproj"), None)
if not csproj_file:
return
new_csproj_file = project_path / f'{project_path.name}.csproj'
if new_csproj_file.exists():
return
from shutil import move
move(csproj_file, new_csproj_file)

def _generate_file(self, file: Path, content: str) -> None:
Expand Down
7 changes: 4 additions & 3 deletions tests/components/util/test_pull_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from lean.models.api import QCProject, QCLanguage
from tests.test_helpers import create_fake_lean_cli_directory, create_api_project, create_lean_environments
from tests.test_helpers import create_fake_lean_cli_project
from lean.components import forbidden_characters

def _create_pull_manager(api_client: mock.Mock,
project_config_manager: mock.Mock,
Expand Down Expand Up @@ -326,7 +327,7 @@ def test_pull_projects_updates_lean_config() -> None:
any_order=True)


@pytest.mark.parametrize("unsupported_character", ["\\", ":", "*", "?", '"', "<", ">", "|"])
@pytest.mark.parametrize("unsupported_character", forbidden_characters)
def test_pull_projects_detects_unsupported_paths(unsupported_character: str) -> None:

create_fake_lean_cli_directory()
Expand Down Expand Up @@ -367,7 +368,7 @@ def test_pull_projects_detects_unsupported_paths(unsupported_character: str) ->
library_manager.remove_lean_library_from_project.assert_not_called()


@pytest.mark.parametrize("unsupported_character", ["\\", ":", "*", "?", '"', "<", ">", "|"])
@pytest.mark.parametrize("unsupported_character", forbidden_characters)
def test_push_projects_updates_name_in_cloud_if_required(unsupported_character: str) -> None:
create_fake_lean_cli_directory()
project_name = "Project 1"
Expand All @@ -393,7 +394,7 @@ def test_push_projects_updates_name_in_cloud_if_required(unsupported_character:
assert "name" in kwargs and kwargs['name'] == project_name

@pytest.mark.parametrize("test_platform, unsupported_character", [
*[("linux", char) for char in ["\\", ":", "*", "?", '"', "<", ">", "|"]],
*[("linux", char) for char in forbidden_characters],
("macos", ":")
])
def test_pull_projects_renames_project_if_required(test_platform: str, unsupported_character: str) -> None:
Expand Down
7 changes: 4 additions & 3 deletions tests/components/util/test_push_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from lean.models.api import QCLanguage, QCProject
from tests.test_helpers import create_fake_lean_cli_directory, create_api_project, create_lean_environments
from tests.test_helpers import create_fake_lean_cli_project
from lean.components import forbidden_characters

def _create_organization_manager() -> mock.Mock:
organization_manager = mock.Mock()
Expand Down Expand Up @@ -416,7 +417,7 @@ def get_local_project_path(project_name, *args):


@pytest.mark.parametrize("test_platform, unsupported_character", [
*[("linux", char) for char in ["\\", ":", "*", "?", '"', "<", ">", "|"]],
*[("linux", char) for char in forbidden_characters],
("macos", ":")
])
def test_push_projects_detects_unsupported_paths(test_platform: str, unsupported_character: str) -> None:
Expand All @@ -441,7 +442,7 @@ def test_push_projects_detects_unsupported_paths(test_platform: str, unsupported


@pytest.mark.parametrize("test_platform, unsupported_character", [
*[("linux", char) for char in ["\\", ":", "*", "?", '"', "<", ">", "|"]],
*[("linux", char) for char in forbidden_characters],
("macos", ":")
])
def test_push_projects_renames_project_if_required(test_platform: str, unsupported_character: str) -> None:
Expand Down Expand Up @@ -469,7 +470,7 @@ def test_push_projects_renames_project_if_required(test_platform: str, unsupport
assert (Path.cwd() / expected_correct_project_name).exists()

@pytest.mark.parametrize("test_platform, unsupported_character", [
*[("linux", char) for char in ["\\", ":", "*", "?", '"', "<", ">", "|"]],
*[("linux", char) for char in forbidden_characters],
("macos", ":")
])
def test_push_projects_updates_name_in_cloud_if_required(test_platform: str, unsupported_character: str) -> None:
Expand Down

0 comments on commit f247f08

Please sign in to comment.