From 65a91d64e98170a0c3fe7d2d71933313dbc57c8f Mon Sep 17 00:00:00 2001 From: Martin-Molinero Date: Tue, 2 Apr 2024 17:04:14 -0300 Subject: [PATCH] Remove hardcoded python and dotnet version (#441) * Remove hardcoded python and dotnet version - Remove hardcoded python and dotnet version, these will be retrieved from the docker labels of the image being used * Move default python dotnet values to constant --- lean/commands/library/add.py | 13 ++++-- lean/commands/optimize.py | 6 ++- lean/commands/research.py | 25 +++++------ lean/components/docker/docker_manager.py | 14 +++++- lean/components/docker/lean_runner.py | 43 +++++++++++++------ lean/components/util/project_manager.py | 28 ++++++++---- lean/constants.py | 14 +++--- lean/container.py | 12 +++--- tests/commands/test_research.py | 3 +- tests/components/docker/test_lean_runner.py | 4 +- tests/components/util/test_library_manager.py | 35 ++------------- tests/components/util/test_project_manager.py | 25 ++--------- tests/conftest.py | 3 ++ tests/test_helpers.py | 6 ++- 14 files changed, 117 insertions(+), 114 deletions(-) diff --git a/lean/commands/library/add.py b/lean/commands/library/add.py index 01fc74af..d6f7db9a 100644 --- a/lean/commands/library/add.py +++ b/lean/commands/library/add.py @@ -13,10 +13,10 @@ from pathlib import Path from typing import Any, Dict, Optional, Tuple -from lean.constants import LEAN_STRICT_PYTHON_VERSION from click import command, argument, option from lean.click import LeanCommand, PathParameter +from lean.constants import DEFAULT_LEAN_STRICT_PYTHON_VERSION from lean.container import container from lean.models.errors import MoreInfoError @@ -124,7 +124,7 @@ def _is_pypi_file_compatible(file: Dict[str, Any], required_python_version) -> b return True -def _get_pypi_package(name: str, version: Optional[str]) -> Tuple[str, str]: +def _get_pypi_package(name: str, version: Optional[str], python_version: str) -> Tuple[str, str]: """Retrieves the properly-capitalized name and the latest compatible version of a package from PyPI. If the version is already given, this method checks whether that version is compatible with the Docker images. @@ -148,7 +148,7 @@ def _get_pypi_package(name: str, version: Optional[str]) -> Tuple[str, str]: pypi_data = loads(response.text) name = pypi_data["info"]["name"] - required_python_version = StrictVersion(LEAN_STRICT_PYTHON_VERSION) + required_python_version = StrictVersion(python_version) last_compatible_version = None last_compatible_version_upload_time = None @@ -237,7 +237,12 @@ def _add_pypi_package_to_python_project(project_dir: Path, name: str, version: O else: logger.info("Retrieving latest compatible version from PyPI") - name, version = _get_pypi_package(name, version) + project_config = container.project_config_manager.get_project_config(project_dir) + engine_image = container.cli_config_manager.get_engine_image(project_config.get("engine-image", None)) + python_version = container.docker_manager.get_image_label(engine_image, 'strict_python_version', + DEFAULT_LEAN_STRICT_PYTHON_VERSION) + + name, version = _get_pypi_package(name, version, python_version) requirements_file = project_dir / "requirements.txt" logger.info(f"Adding {name} {version} to '{path_manager.get_relative_path(requirements_file)}'") diff --git a/lean/commands/optimize.py b/lean/commands/optimize.py index bf78e359..92611b2c 100644 --- a/lean/commands/optimize.py +++ b/lean/commands/optimize.py @@ -336,7 +336,10 @@ def optimize(project: Path, build_and_configure_modules(addon_module, cli_addon_modules, organization_id, lean_config, kwargs, logger, environment_name) - run_options = lean_runner.get_basic_docker_config(lean_config, algorithm_file, output, None, release, should_detach) + container.update_manager.pull_docker_image_if_necessary(engine_image, update, no_update) + + run_options = lean_runner.get_basic_docker_config(lean_config, algorithm_file, output, None, release, should_detach, + engine_image) run_options["working_dir"] = "/Lean/Optimizer.Launcher/bin/Debug" run_options["commands"].append(f"dotnet QuantConnect.Optimizer.Launcher.dll{' --estimate' if estimate else ''}") @@ -346,7 +349,6 @@ def optimize(project: Path, type="bind", read_only=True) ) - container.update_manager.pull_docker_image_if_necessary(engine_image, update, no_update) # Add known additional run options from the extra docker config LeanRunner.parse_extra_docker_config(run_options, loads(extra_docker_config)) diff --git a/lean/commands/research.py b/lean/commands/research.py index 739640ae..42715584 100644 --- a/lean/commands/research.py +++ b/lean/commands/research.py @@ -128,12 +128,24 @@ def research(project: Path, for key, value in extra_config: lean_config[key] = value + project_config_manager = container.project_config_manager + cli_config_manager = container.cli_config_manager + + project_config = project_config_manager.get_project_config(algorithm_file.parent) + research_image = cli_config_manager.get_research_image(image or project_config.get("research-image", None)) + + container.update_manager.pull_docker_image_if_necessary(research_image, update, no_update) + + if str(research_image) != DEFAULT_RESEARCH_IMAGE: + logger.warn(f'A custom research image: "{research_image}" is being used!') + run_options = lean_runner.get_basic_docker_config(lean_config, algorithm_file, temp_manager.create_temporary_directory(), None, False, - detach) + detach, + research_image) # Mount the config in the notebooks directory as well local_config_path = next(m["Source"] for m in run_options["mounts"] if m["Target"].endswith("config.json")) @@ -174,17 +186,6 @@ def research(project: Path, # Add known additional run options from the extra docker config LeanRunner.parse_extra_docker_config(run_options, loads(extra_docker_config)) - project_config_manager = container.project_config_manager - cli_config_manager = container.cli_config_manager - - project_config = project_config_manager.get_project_config(algorithm_file.parent) - research_image = cli_config_manager.get_research_image(image or project_config.get("research-image", None)) - - if str(research_image) != DEFAULT_RESEARCH_IMAGE: - logger.warn(f'A custom research image: "{research_image}" is being used!') - - container.update_manager.pull_docker_image_if_necessary(research_image, update, no_update) - try: container.docker_manager.run_image(research_image, **run_options) except APIError as error: diff --git a/lean/components/docker/docker_manager.py b/lean/components/docker/docker_manager.py index 5f08a226..57ab9f90 100644 --- a/lean/components/docker/docker_manager.py +++ b/lean/components/docker/docker_manager.py @@ -38,6 +38,16 @@ def __init__(self, logger: Logger, temp_manager: TempManager, platform_manager: self._temp_manager = temp_manager self._platform_manager = platform_manager + def get_image_label(self, image: DockerImage, label: str, default: str) -> str: + docker_image = self._get_docker_client().images.get(image.name) + + for name, value in docker_image.labels.items(): + if name == label: + self._logger.debug(f"Label '{label}' found in image '{image.name}', value {value}") + return value + self._logger.info(f"Label '{label}' not found in image '{image.name}', using default {default}") + return default + def pull_image(self, image: DockerImage) -> None: """Pulls a Docker image. @@ -563,13 +573,13 @@ def _format_source_path(self, path: str) -> str: break return path - + def get_container_port(self, container_name: str, internal_port: str) -> Optional[int]: """ Returns a containers external port for a mapped internal port :param container_name: Name of the container :param internal_port: The internal port of container. If protocol not included - we assume /tcp. ex. 5678/tcp + we assume /tcp. ex. 5678/tcp :return: The external port that is linked to it, or None if it does not exist """ diff --git a/lean/components/docker/lean_runner.py b/lean/components/docker/lean_runner.py index 531cd201..9a1e0507 100644 --- a/lean/components/docker/lean_runner.py +++ b/lean/components/docker/lean_runner.py @@ -23,7 +23,8 @@ from lean.components.util.project_manager import ProjectManager from lean.components.util.temp_manager import TempManager from lean.components.util.xml_manager import XMLManager -from lean.constants import MODULES_DIRECTORY, TERMINAL_LINK_PRODUCT_ID, LEAN_ROOT_PATH, DEFAULT_DATA_DIRECTORY_NAME +from lean.constants import MODULES_DIRECTORY, LEAN_ROOT_PATH, DEFAULT_DATA_DIRECTORY_NAME, \ + DEFAULT_LEAN_DOTNET_FRAMEWORK, DEFAULT_LEAN_PYTHON_VERSION from lean.constants import DOCKER_PYTHON_SITE_PACKAGES_PATH from lean.models.docker import DockerImage from lean.models.utils import DebuggingMethod @@ -97,7 +98,8 @@ def run_lean(self, output_dir, debugging_method, release, - detach) + detach, + image) # Add known additional run options from the extra docker config self.parse_extra_docker_config(run_options, extra_docker_config) @@ -175,7 +177,8 @@ def get_basic_docker_config(self, output_dir: Path, debugging_method: Optional[DebuggingMethod], release: bool, - detach: bool) -> Dict[str, Any]: + detach: bool, + image: DockerImage) -> Dict[str, Any]: """Creates a basic Docker config to run the engine with. This method constructs the parts of the Docker config that is the same for both the engine and the optimizer. @@ -186,6 +189,7 @@ def get_basic_docker_config(self, :param debugging_method: the debugging method if debugging needs to be enabled, None if not :param release: whether C# projects should be compiled in release configuration instead of debug :param detach: whether LEAN should run in a detached container + :param image: The docker image that will be used :return: the Docker configuration containing basic configuration to run Lean """ from docker.types import Mount @@ -309,7 +313,10 @@ def get_basic_docker_config(self, # Create a C# project used to resolve the dependencies of the modules run_options["commands"].append("mkdir /ModulesProject") run_options["commands"].append("dotnet new sln -o /ModulesProject") - run_options["commands"].append("dotnet new classlib -o /ModulesProject -f net6.0 --no-restore") + + framework_ver = self._docker_manager.get_image_label(image, 'target_framework', + DEFAULT_LEAN_DOTNET_FRAMEWORK) + run_options["commands"].append(f"dotnet new classlib -o /ModulesProject -f {framework_ver} --no-restore") run_options["commands"].append("rm /ModulesProject/Class1.cs") # Add all modules to the project, automatically resolving all dependencies @@ -325,7 +332,8 @@ def get_basic_docker_config(self, # Set up language-specific run options self.setup_language_specific_run_options(run_options, project_dir, algorithm_file, - set_up_common_csharp_options_called, release) + set_up_common_csharp_options_called, release, + image) # Save the final Lean config to a temporary file so we can mount it into the container config_path = self._temp_manager.create_temporary_directory() / "config.json" @@ -359,11 +367,12 @@ def get_basic_docker_config(self, return run_options - def set_up_python_options(self, project_dir: Path, run_options: Dict[str, Any]) -> None: + def set_up_python_options(self, project_dir: Path, run_options: Dict[str, Any], image: DockerImage) -> None: """Sets up Docker run options specific to Python projects. :param project_dir: the path to the project directory :param run_options: the dictionary to append run options to + :param image: the docker image that will be used """ from docker.types import Mount @@ -410,10 +419,14 @@ def set_up_python_options(self, project_dir: Path, run_options: Dict[str, Any]) "mode": "rw" } + python_version = self._docker_manager.get_image_label(image, 'python_version', + DEFAULT_LEAN_PYTHON_VERSION) + site_packages_path = DOCKER_PYTHON_SITE_PACKAGES_PATH.replace('{LEAN_PYTHON_VERSION}', python_version) + # Mount a volume to the user packages directory so we don't install packages every time site_packages_volume = self._docker_manager.create_site_packages_volume(requirements_txt) run_options["volumes"][site_packages_volume] = { - "bind": f"{DOCKER_PYTHON_SITE_PACKAGES_PATH}", + "bind": f"{site_packages_path}", "mode": "rw" } @@ -424,7 +437,7 @@ def set_up_python_options(self, project_dir: Path, run_options: Dict[str, Any]) # We only need to do this if it hasn't already been done before for this site packages volume # To keep track of this we create a special file in the site packages directory after installation # If this file already exists we can skip pip install completely - marker_file = f"{DOCKER_PYTHON_SITE_PACKAGES_PATH}/pip-install-done" + marker_file = f"{site_packages_path}/pip-install-done" run_options["commands"].extend([ f"! test -f {marker_file} && pip install --user --progress-bar off -r /requirements.txt", f"touch {marker_file}" @@ -452,12 +465,14 @@ def _concat_python_requirements(self, requirements_files: List[Path]) -> str: requirements = sorted(set(requirements)) return "\n".join(requirements) - def set_up_csharp_options(self, project_dir: Path, run_options: Dict[str, Any], release: bool) -> None: + def set_up_csharp_options(self, project_dir: Path, run_options: Dict[str, Any], release: bool, + image: DockerImage) -> None: """Sets up Docker run options specific to C# projects. :param project_dir: the path to the project directory :param run_options: the dictionary to append run options to :param release: whether C# projects should be compiled in release configuration instead of debug + :param image: the docker image that will be used """ compile_root = self._get_csharp_compile_root(project_dir) @@ -474,11 +489,13 @@ def set_up_csharp_options(self, project_dir: Path, run_options: Dict[str, Any], for path in compile_root.rglob("*.csproj"): self._ensure_csproj_uses_correct_lean(compile_root, path, csproj_temp_dir, run_options) + framework_ver = self._docker_manager.get_image_label(image, 'target_framework', + DEFAULT_LEAN_DOTNET_FRAMEWORK) # Set up the MSBuild properties msbuild_properties = { "Configuration": "Release" if release else "Debug", "Platform": "AnyCPU", - "TargetFramework": "net6.0", + "TargetFramework": framework_ver, "OutputPath": "/Compile/bin", "GenerateAssemblyInfo": "false", "GenerateTargetFrameworkAttribute": "false", @@ -722,14 +739,14 @@ def _force_disk_provider_if_necessary(self, lean_config[config_key] = disk_provider def setup_language_specific_run_options(self, run_options, project_dir, algorithm_file, - set_up_common_csharp_options_called, release) -> None: + set_up_common_csharp_options_called, release, image: DockerImage) -> None: # Set up language-specific run options if algorithm_file.name.endswith(".py"): - self.set_up_python_options(project_dir, run_options) + self.set_up_python_options(project_dir, run_options, image) else: if not set_up_common_csharp_options_called: self.set_up_common_csharp_options(run_options) - self.set_up_csharp_options(project_dir, run_options, release) + self.set_up_csharp_options(project_dir, run_options, release, image) def format_error_before_logging(self, chunk: str): from lean.components.util import compiler diff --git a/lean/components/util/project_manager.py b/lean/components/util/project_manager.py index 12cb5e79..21d9518d 100644 --- a/lean/components/util/project_manager.py +++ b/lean/components/util/project_manager.py @@ -15,16 +15,19 @@ from pathlib import Path from typing import List, Optional, Union, Tuple from lean.components import reserved_names +from lean.components.config.cli_config_manager import CLIConfigManager from lean.components.config.lean_config_manager import LeanConfigManager from lean.components.config.project_config_manager import ProjectConfigManager +from lean.components.docker.docker_manager import DockerManager from lean.components.util.logger import Logger from lean.components.util.path_manager import PathManager from lean.components.util.platform_manager import PlatformManager from lean.components.util.xml_manager import XMLManager -from lean.constants import PROJECT_CONFIG_FILE_NAME +from lean.constants import PROJECT_CONFIG_FILE_NAME, DEFAULT_LEAN_DOTNET_FRAMEWORK from lean.models.api import QCLanguage, QCProject, QCProjectLibrary from lean.models.utils import LeanLibraryReference + class ProjectManager: """The ProjectManager class provides utilities for handling a single project.""" @@ -34,7 +37,9 @@ def __init__(self, lean_config_manager: LeanConfigManager, path_manager: PathManager, xml_manager: XMLManager, - platform_manager: PlatformManager) -> None: + platform_manager: PlatformManager, + cli_config_manager: CLIConfigManager, + docker_manager: DockerManager) -> None: """Creates a new ProjectManager instance. :param logger: the logger to use to log messages with @@ -50,6 +55,8 @@ def __init__(self, self._path_manager = path_manager self._xml_manager = xml_manager self._platform_manager = platform_manager + self._cli_config_manager = cli_config_manager + self._docker_manager = docker_manager def find_algorithm_file(self, input: Path) -> Path: """Returns the path to the file containing the algorithm. @@ -186,7 +193,11 @@ def create_new_project(self, project_dir: Path, language: QCLanguage) -> None: self._generate_pycharm_config(project_dir) else: self._generate_vscode_csharp_config(project_dir) - self._generate_csproj(project_dir) + + image = self._cli_config_manager.get_engine_image() + framework_ver = self._docker_manager.get_image_label(image, 'target_framework', + DEFAULT_LEAN_DOTNET_FRAMEWORK) + self._generate_csproj(project_dir, framework_ver) self.generate_rider_config(project_dir) def delete_project(self, project_dir: Path) -> None: @@ -677,12 +688,13 @@ def _generate_vscode_csharp_config(self, project_dir: Path) -> None: }} """) - def _generate_csproj(self, project_dir: Path) -> None: + def _generate_csproj(self, project_dir: Path, framework_version: str) -> None: """Generates a .csproj file for the given project and returns the path to it. :param project_dir: the path of the new project """ - self._generate_file(project_dir / f"{project_dir.name}.csproj", self.get_csproj_file_default_content()) + self._generate_file(project_dir / f"{project_dir.name}.csproj", + self.get_csproj_file_default_content(framework_version)) def generate_rider_config(self, project_dir: Path) -> bool: """Generates C# debugging configuration for Rider. @@ -952,13 +964,13 @@ def get_cloud_projects_libraries(self, return list(set(libraries)), list(set(libraries_not_found)) @staticmethod - def get_csproj_file_default_content() -> str: + def get_csproj_file_default_content(framework_version: str) -> str: return """ Debug AnyCPU - net6.0 + {TARGET_FRAMEWORK} bin/$(Configuration) false $(DefaultItemExcludes);backtests/*/code/**;live/*/code/**;optimizations/*/code/** @@ -969,7 +981,7 @@ def get_csproj_file_default_content() -> str: - """ + """.replace('{TARGET_FRAMEWORK}', framework_version) @staticmethod def get_csproj_file_path(project_dir: Path) -> Path: diff --git a/lean/constants.py b/lean/constants.py index 53e4ff33..f2fe3482 100644 --- a/lean/constants.py +++ b/lean/constants.py @@ -21,14 +21,13 @@ CUSTOM_ENGINE = "lean-cli/engine" CUSTOM_RESEARCH = "lean-cli/research" -# The python version of docker image -LEAN_PYTHON_VERSION = "3.8" - -# The strict python version of docker image -LEAN_STRICT_PYTHON_VERSION = f"{LEAN_PYTHON_VERSION}.13" +# we get these values from the image labels, but we still have defaults just in case +DEFAULT_LEAN_PYTHON_VERSION = "3.11" +DEFAULT_LEAN_STRICT_PYTHON_VERSION = f"{DEFAULT_LEAN_PYTHON_VERSION}.7" +DEFAULT_LEAN_DOTNET_FRAMEWORK = "net6.0" # The path to the root python directory in docker image -DOCKER_PYTHON_SITE_PACKAGES_PATH = f"/root/.local/lib/python{LEAN_PYTHON_VERSION}/site-packages" +DOCKER_PYTHON_SITE_PACKAGES_PATH = "/root/.local/lib/python{LEAN_PYTHON_VERSION}/site-packages" # The file in which general CLI configuration is stored LEAN_ROOT_PATH = "/Lean/Launcher/bin/Debug" @@ -91,9 +90,6 @@ # The interval in hours at which the CLI checks for new announcements UPDATE_CHECK_INTERVAL_ANNOUNCEMENTS = 24 -# The product id of the Terminal Link module -TERMINAL_LINK_PRODUCT_ID = 44 - # The name of the Docker network which all Lean CLI containers are ran on DOCKER_NETWORK = "lean_cli" diff --git a/lean/container.py b/lean/container.py index 3f82d5f6..f4f44667 100644 --- a/lean/container.py +++ b/lean/container.py @@ -95,12 +95,18 @@ def initialize(self, self.output_config_manager = OutputConfigManager(self.lean_config_manager) self.optimizer_config_manager = OptimizerConfigManager(self.logger) + self.docker_manager = docker_manager + if not self.docker_manager: + self.docker_manager = DockerManager(self.logger, self.temp_manager, self.platform_manager) + self.project_manager = ProjectManager(self.logger, self.project_config_manager, self.lean_config_manager, self.path_manager, self.xml_manager, - self.platform_manager) + self.platform_manager, + self.cli_config_manager, + self.docker_manager) self.library_manager = LibraryManager(self.logger, self.project_manager, self.project_config_manager, @@ -139,10 +145,6 @@ def initialize(self, self.project_manager, self.organization_manager) - self.docker_manager = docker_manager - if not self.docker_manager: - self.docker_manager = DockerManager(self.logger, self.temp_manager, self.platform_manager) - self.lean_runner = lean_runner if not self.lean_runner: self.lean_runner = LeanRunner(self.logger, diff --git a/tests/commands/test_research.py b/tests/commands/test_research.py index c00e65dd..a6a2281a 100644 --- a/tests/commands/test_research.py +++ b/tests/commands/test_research.py @@ -18,7 +18,7 @@ from click.testing import CliRunner from lean.commands import lean -from lean.constants import DEFAULT_RESEARCH_IMAGE, LEAN_ROOT_PATH, LEAN_PYTHON_VERSION +from lean.constants import DEFAULT_RESEARCH_IMAGE, LEAN_ROOT_PATH from lean.container import container from lean.models.docker import DockerImage from tests.test_helpers import create_fake_lean_cli_directory @@ -166,7 +166,6 @@ def test_research_opens_browser_when_container_started(open) -> None: logs = f""" [I 21:06:21.500 LabApp] Writing notebook server cookie secret to /root/.local/share/jupyter/runtime/notebook_cookie_secret [W 21:06:21.692 LabApp] All authentication is disabled. Anyone who can connect to this server will be able to run code. -[I 21:06:21.698 LabApp] JupyterLab extension loaded from /opt/miniconda3/lib/python{LEAN_PYTHON_VERSION}/site-packages/jupyterlab [I 21:06:21.698 LabApp] JupyterLab application directory is /opt/miniconda3/share/jupyter/lab [I 21:06:21.700 LabApp] Serving notebooks from local directory: /Lean/Launcher/bin/Debug/Notebooks [I 21:06:21.700 LabApp] The Jupyter Notebook is running at: diff --git a/tests/components/docker/test_lean_runner.py b/tests/components/docker/test_lean_runner.py index 36534aa8..1520b5f6 100644 --- a/tests/components/docker/test_lean_runner.py +++ b/tests/components/docker/test_lean_runner.py @@ -76,7 +76,9 @@ def create_lean_runner(docker_manager: mock.Mock) -> LeanRunner: lean_config_manager, path_manager, xml_manager, - platform_manager) + platform_manager, + cli_config_manager, + docker_manager) return LeanRunner(logger, project_config_manager, diff --git a/tests/components/util/test_library_manager.py b/tests/components/util/test_library_manager.py index f0263e33..92382d76 100644 --- a/tests/components/util/test_library_manager.py +++ b/tests/components/util/test_library_manager.py @@ -16,44 +16,15 @@ import pytest -from lean.components.config.lean_config_manager import LeanConfigManager -from lean.components.config.project_config_manager import ProjectConfigManager -from lean.components.config.storage import Storage from lean.components.util.library_manager import LibraryManager -from lean.components.util.path_manager import PathManager -from lean.components.util.platform_manager import PlatformManager -from lean.components.util.project_manager import ProjectManager -from lean.components.util.xml_manager import XMLManager from lean.container import container from lean.models.utils import LeanLibraryReference +from tests.conftest import initialize_container from tests.test_helpers import create_fake_lean_cli_directory def _create_library_manager() -> LibraryManager: - cache_storage = Storage(str(Path("~/.lean/cache").expanduser())) - lean_config_manager = LeanConfigManager(mock.Mock(), - mock.Mock(), - mock.Mock(), - mock.Mock(), - cache_storage) - platform_manager = PlatformManager() - path_manager = PathManager(lean_config_manager, platform_manager) - xml_manager = XMLManager() - project_config_manager = ProjectConfigManager(xml_manager) - logger = mock.Mock() - project_manager = ProjectManager(logger, - project_config_manager, - lean_config_manager, - path_manager, - xml_manager, - platform_manager) - - return LibraryManager(logger, - project_manager, - project_config_manager, - lean_config_manager, - path_manager, - xml_manager) + return initialize_container().library_manager def _project_has_library_reference_in_config(project_dir: Path, library_dir: Path) -> bool: @@ -96,7 +67,7 @@ def test_get_csharp_lean_library_path_for_csproj_file(project_depth: int) -> Non # plus 'Library/CSharp Library/CSharp Project.csproj' (2 more parts), # plus the csproj file name (1 more part) expected_csproj_file_path = \ - (Path("/".join(".." for i in range(project_depth + 1))) / library_dir_relative_to_cli / "CSharp Library.csproj") + (Path("/".join(".." for _ in range(project_depth + 1))) / library_dir_relative_to_cli / "CSharp Library.csproj") assert Path(csproj_file_path) == expected_csproj_file_path diff --git a/tests/components/util/test_project_manager.py b/tests/components/util/test_project_manager.py index cd331625..604c2308 100644 --- a/tests/components/util/test_project_manager.py +++ b/tests/components/util/test_project_manager.py @@ -20,37 +20,18 @@ import pytest from lxml import etree -from lean.components.config.lean_config_manager import LeanConfigManager from lean.components.config.project_config_manager import ProjectConfigManager from lean.components.config.storage import Storage -from lean.components.util.path_manager import PathManager -from lean.components.util.platform_manager import PlatformManager from lean.components.util.project_manager import ProjectManager from lean.components.util.xml_manager import XMLManager from lean.container import container -from lean.models.api import QCLanguage, QCProjectLibrary, QCProject +from lean.models.api import QCLanguage, QCProjectLibrary +from tests.conftest import initialize_container from tests.test_helpers import create_fake_lean_cli_directory, create_api_project def _create_project_manager() -> ProjectManager: - logger = mock.Mock() - xml_manager = XMLManager() - project_config_manager = ProjectConfigManager(xml_manager) - cache_storage = Storage(str(Path("~/.lean/cache").expanduser())) - platform_manager = PlatformManager() - lean_config_manager = LeanConfigManager(mock.Mock(), - mock.Mock(), - project_config_manager, - mock.Mock(), - cache_storage) - path_manager = PathManager(lean_config_manager, platform_manager) - - return ProjectManager(logger, - project_config_manager, - lean_config_manager, - path_manager, - xml_manager, - platform_manager) + return initialize_container().project_manager def _lists_are_equal(list1: List[Any], list2: List[Any]) -> bool: diff --git a/tests/conftest.py b/tests/conftest.py index 547f3caa..c3a8f4c3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -19,6 +19,8 @@ import pytest from pyfakefs.fake_filesystem import FakeFilesystem from responses import RequestsMock + +from lean.constants import DEFAULT_LEAN_DOTNET_FRAMEWORK from lean.models.api import QCMinimalOrganization from lean.container import container @@ -38,6 +40,7 @@ def initialize_container(docker_manager_to_use=None, lean_runner_to_use=None, ap docker_manager = mock.Mock() if docker_manager_to_use: docker_manager = docker_manager_to_use + docker_manager.get_image_label = mock.MagicMock(return_value=DEFAULT_LEAN_DOTNET_FRAMEWORK) lean_runner = mock.Mock() if lean_runner_to_use: diff --git a/tests/test_helpers.py b/tests/test_helpers.py index df289aa8..1738797f 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -15,6 +15,8 @@ from datetime import datetime from pathlib import Path from typing import List + +from lean.constants import DEFAULT_LEAN_DOTNET_FRAMEWORK from lean.models.cli import (cli_brokerages, cli_data_downloaders, cli_data_queue_handlers, cli_addon_modules, cli_history_provider) @@ -44,7 +46,7 @@ def _get_csharp_project_files(path: Path) -> dict: "algorithm-language": "CSharp", "parameters": {} }), - (path / "CSharp Project.csproj"): ProjectManager.get_csproj_file_default_content() + (path / "CSharp Project.csproj"): ProjectManager.get_csproj_file_default_content(DEFAULT_LEAN_DOTNET_FRAMEWORK) } @@ -65,7 +67,7 @@ def _get_fake_libraries() -> dict: "parameters": {} }), (Path.cwd() / "Library" / "CSharp Library" / "CSharp Library.csproj"): - ProjectManager.get_csproj_file_default_content() + ProjectManager.get_csproj_file_default_content(DEFAULT_LEAN_DOTNET_FRAMEWORK) }