Skip to content

Commit

Permalink
Fix: not pulling docker image (#503)
Browse files Browse the repository at this point in the history
* fix: change position of pull docker image

* feat: generic manage_docker_image

* refactor: reuse manage_docker_image

* feat: add research in manage_docker_image
refactor: use project_directory instead of AlgorithmFile Path

* fix: missed project_config in download/generate
  • Loading branch information
Romazes authored Sep 25, 2024
1 parent 55e773d commit 2456258
Show file tree
Hide file tree
Showing 8 changed files with 68 additions and 88 deletions.
17 changes: 3 additions & 14 deletions lean/commands/backtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from click import command, option, argument, Choice

from lean.click import LeanCommand, PathParameter
from lean.constants import DEFAULT_ENGINE_IMAGE, LEAN_ROOT_PATH, CONTAINER_LABEL_LEAN_VERSION_NAME
from lean.constants import DEFAULT_ENGINE_IMAGE, LEAN_ROOT_PATH
from lean.container import container, Logger
from lean.models.utils import DebuggingMethod
from lean.models.cli import cli_data_downloaders, cli_addon_modules
Expand Down Expand Up @@ -359,14 +359,8 @@ def backtest(project: Path,
organization_id = container.organization_manager.try_get_working_organization_id()
paths_to_mount = None

cli_config_manager = container.cli_config_manager
project_config_manager = container.project_config_manager

project_config = project_config_manager.get_project_config(algorithm_file.parent)
engine_image = cli_config_manager.get_engine_image(image or project_config.get("engine-image", None))

container_module_version = container.docker_manager.get_image_label(engine_image,
CONTAINER_LABEL_LEAN_VERSION_NAME, None)
engine_image, container_module_version, project_config = container.manage_docker_image(image, update, no_update,
algorithm_file.parent)

if data_provider_historical is not None:
data_provider = non_interactive_config_build_for_name(lean_config, data_provider_historical,
Expand All @@ -377,11 +371,6 @@ def backtest(project: Path,

lean_config_manager.configure_data_purchase_limit(lean_config, data_purchase_limit)

if str(engine_image) != DEFAULT_ENGINE_IMAGE:
logger.warn(f'A custom engine image: "{engine_image}" is being used!')

container.update_manager.pull_docker_image_if_necessary(engine_image, update, no_update)

if not output.exists():
output.mkdir(parents=True)

Expand Down
13 changes: 2 additions & 11 deletions lean/commands/data/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from click import command, option, confirm, pass_context, Context, Choice, prompt
from lean.click import LeanCommand, ensure_options
from lean.components.util.json_modules_handler import config_build_for_name
from lean.constants import DEFAULT_ENGINE_IMAGE, CONTAINER_LABEL_LEAN_VERSION_NAME
from lean.constants import DEFAULT_ENGINE_IMAGE
from lean.container import container
from lean.models.api import QCDataInformation, QCDataVendor, QCFullOrganization, QCDatasetDelivery, QCResolution, QCSecurityType, QCDataType
from lean.models.click_options import get_configs_for_options, options_from_json
Expand Down Expand Up @@ -675,16 +675,7 @@ def download(ctx: Context,
logger = container.logger
lean_config = container.lean_config_manager.get_complete_lean_config(None, None, None)

engine_image = container.cli_config_manager.get_engine_image(image)

if str(engine_image) != DEFAULT_ENGINE_IMAGE:
# Custom engine image should not be updated.
logger.warn(f'A custom engine image: "{engine_image}" is being used!')

container.update_manager.pull_docker_image_if_necessary(engine_image, update, no_update)

container_module_version = container.docker_manager.get_image_label(engine_image,
CONTAINER_LABEL_LEAN_VERSION_NAME, None)
engine_image, container_module_version, project_config = container.manage_docker_image(image, update, no_update)

data_downloader_provider = config_build_for_name(lean_config, data_downloader_provider.get_name(),
cli_data_downloaders, kwargs, logger, interactive=True)
Expand Down
4 changes: 1 addition & 3 deletions lean/commands/data/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,7 @@ def generate(start: datetime,
}
}

engine_image = container.cli_config_manager.get_engine_image(image)

container.update_manager.pull_docker_image_if_necessary(engine_image, update)
engine_image, container_module_version, project_config = container.manage_docker_image(image, update, no_update=False)

success = container.docker_manager.run_image(engine_image, **run_options)
if not success:
Expand Down
21 changes: 5 additions & 16 deletions lean/commands/live/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from click import option, argument, Choice
from lean.click import LeanCommand, PathParameter
from lean.components.util.name_rename import rename_internal_config_to_user_friendly_format
from lean.constants import DEFAULT_ENGINE_IMAGE, CONTAINER_LABEL_LEAN_VERSION_NAME
from lean.constants import DEFAULT_ENGINE_IMAGE
from lean.container import container
from lean.models.cli import (cli_brokerages, cli_data_queue_handlers, cli_data_downloaders,
cli_addon_modules, cli_history_provider)
Expand Down Expand Up @@ -271,14 +271,8 @@ def deploy(project: Path,
kwargs, logger, interactive=True,
environment_name=environment_name))

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)
engine_image = cli_config_manager.get_engine_image(image or project_config.get("engine-image", None))

container_module_version = container.docker_manager.get_image_label(engine_image,
CONTAINER_LABEL_LEAN_VERSION_NAME, None)
engine_image, container_module_version, project_config = container.manage_docker_image(image, update, no_update,
algorithm_file.parent)

organization_id = container.organization_manager.try_get_working_organization_id()
paths_to_mount = {}
Expand All @@ -291,15 +285,13 @@ def deploy(project: Path,
raise MoreInfoError(f"The '{environment_name}' is not a live trading environment (live-mode is set to false)",
"https://www.lean.io/docs/v2/lean-cli/live-trading/brokerages/quantconnect-paper-trading")

container.update_manager.pull_docker_image_if_necessary(engine_image, update, no_update)

_start_iqconnect_if_necessary(lean_config, environment_name)

if python_venv is not None and python_venv != "":
lean_config["python-venv"] = f'{"/" if python_venv[0] != "/" else ""}{python_venv}'

cash_balance_option, holdings_option, last_cash, last_holdings = get_last_portfolio_cash_holdings(container.api_client, brokerage_instance,
project_config.get("cloud-id", None), project)
cash_balance_option, holdings_option, last_cash, last_holdings = get_last_portfolio_cash_holdings(
container.api_client, brokerage_instance, project_config.get("cloud-id", None), project)

# We cannot create the output directory before calling get_last_portfolio_holdings, since then the most recently
# deployment would be always the local one (it has the current time in its name), and we would never be able to
Expand Down Expand Up @@ -336,9 +328,6 @@ def deploy(project: Path,
"AveragePrice": holding["averagePrice"]
} for holding in live_holdings]

if str(engine_image) != DEFAULT_ENGINE_IMAGE:
logger.warn(f'A custom engine image: "{engine_image}" is being used!')

# Set extra config
given_algorithm_id = None
for key, value in extra_config:
Expand Down
20 changes: 3 additions & 17 deletions lean/commands/optimize.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

from lean.click import LeanCommand, PathParameter, ensure_options
from lean.components.docker.lean_runner import LeanRunner
from lean.constants import DEFAULT_ENGINE_IMAGE, CONTAINER_LABEL_LEAN_VERSION_NAME
from lean.constants import DEFAULT_ENGINE_IMAGE
from lean.container import container
from lean.models.api import QCParameter, QCBacktest
from lean.models.click_options import options_from_json, get_configs_for_options
Expand Down Expand Up @@ -227,6 +227,8 @@ def optimize(project: Path,
if optimizer_config is not None and strategy is not None:
raise RuntimeError("--optimizer-config and --strategy are mutually exclusive")

engine_image, container_module_version, project_config = container.manage_docker_image(image, update, no_update,
algorithm_file.parent)
if optimizer_config is not None:
config = loads(optimizer_config.read_text(encoding="utf-8"))

Expand All @@ -242,8 +244,6 @@ def optimize(project: Path,
optimization_parameters = optimizer_config_manager.parse_parameters(parameter)
optimization_constraints = optimizer_config_manager.parse_constraints(constraint)
else:
project_config_manager = container.project_config_manager
project_config = project_config_manager.get_project_config(algorithm_file.parent)
project_parameters = [QCParameter(key=k, value=v) for k, v in project_config.get("parameters", {}).items()]

if len(project_parameters) == 0:
Expand Down Expand Up @@ -286,17 +286,8 @@ def optimize(project: Path,
with config_path.open("w+", encoding="utf-8") as file:
file.write(dumps(config, indent=4) + "\n")

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)
engine_image = cli_config_manager.get_engine_image(image or project_config.get("engine-image", None))

logger = container.logger

if str(engine_image) != DEFAULT_ENGINE_IMAGE:
logger.warn(f'A custom engine image: "{engine_image}" is being used!')

lean_config_manager = container.lean_config_manager
lean_config = lean_config_manager.get_complete_lean_config(environment_name, algorithm_file, None)

Expand All @@ -307,9 +298,6 @@ def optimize(project: Path,

paths_to_mount = None

container_module_version = container.docker_manager.get_image_label(engine_image,
CONTAINER_LABEL_LEAN_VERSION_NAME, None)

if data_provider_historical is not None:
data_provider = non_interactive_config_build_for_name(lean_config, data_provider_historical,
cli_data_downloaders, kwargs, logger, environment_name)
Expand Down Expand Up @@ -342,8 +330,6 @@ def optimize(project: Path,
build_and_configure_modules(addon_module, cli_addon_modules, organization_id, lean_config,
kwargs, logger, environment_name, container_module_version)

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, paths_to_mount)

Expand Down
13 changes: 2 additions & 11 deletions lean/commands/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,17 +258,8 @@ def report(backtest_results: Optional[Path],
type="bind",
read_only=True))

cli_config_manager = container.cli_config_manager
engine_image_override = image

if engine_image_override is None and project_directory is not None:
project_config_manager = container.project_config_manager
project_config = project_config_manager.get_project_config(project_directory)
engine_image_override = project_config.get("engine-image", None)

engine_image = cli_config_manager.get_engine_image(engine_image_override)

container.update_manager.pull_docker_image_if_necessary(engine_image, update)
engine_image, container_module_version, project_config = container.manage_docker_image(image, update, False,
project_directory)

success = container.docker_manager.run_image(engine_image, **run_options)
if not success:
Expand Down
18 changes: 4 additions & 14 deletions lean/commands/research.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from click import command, argument, option, Choice
from lean.click import LeanCommand, PathParameter
from lean.components.docker.lean_runner import LeanRunner
from lean.constants import DEFAULT_RESEARCH_IMAGE, LEAN_ROOT_PATH, CONTAINER_LABEL_LEAN_VERSION_NAME
from lean.constants import DEFAULT_RESEARCH_IMAGE, LEAN_ROOT_PATH
from lean.container import container
from lean.models.cli import cli_data_downloaders
from lean.components.util.name_extraction import convert_to_class_name
Expand Down Expand Up @@ -118,19 +118,9 @@ def research(project: Path,
if download_data:
data_provider_historical = "QuantConnect"

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)

container_module_version = container.docker_manager.get_image_label(research_image,
CONTAINER_LABEL_LEAN_VERSION_NAME, None)

if str(research_image) != DEFAULT_RESEARCH_IMAGE:
logger.warn(f'A custom research image: "{research_image}" is being used!')
research_image, container_module_version, project_config = container.manage_docker_image(image, update, no_update,
algorithm_file.parent,
False)

paths_to_mount = None

Expand Down
50 changes: 48 additions & 2 deletions lean/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Union, Any
from pathlib import Path
from typing import Union, Any, Optional, Tuple

from lean.components.api.api_client import APIClient
from lean.components.config.lean_config_manager import LeanConfigManager
Expand Down Expand Up @@ -41,12 +42,16 @@
from lean.components.util.temp_manager import TempManager
from lean.components.util.update_manager import UpdateManager
from lean.components.util.xml_manager import XMLManager
from lean.constants import CACHE_PATH, CREDENTIALS_CONFIG_PATH, GENERAL_CONFIG_PATH
from lean.constants import CACHE_PATH, CREDENTIALS_CONFIG_PATH, GENERAL_CONFIG_PATH, DEFAULT_RESEARCH_IMAGE
from lean.constants import DEFAULT_ENGINE_IMAGE, CONTAINER_LABEL_LEAN_VERSION_NAME
from lean.models.docker import DockerImage


class Container:

def __init__(self):
self.project_config_manager = None
self.cli_config_manager = None
self.initialize()

def initialize(self,
Expand Down Expand Up @@ -161,6 +166,47 @@ def initialize(self,

self.update_manager = UpdateManager(self.logger, self.http_client, self.cache_storage, self.docker_manager)

def manage_docker_image(self, image: Optional[str], update: bool, no_update: bool,
project_directory: Path = None,
is_engine_image: bool = True) -> Tuple[DockerImage, str, Optional[Storage]]:
"""
Manages the Docker image for the LEAN engine by:
1. Retrieving the engine image from the provided image or project config.
2. Pulling the image if necessary based on the update flags.
3. Logging a warning if a custom image is used.
:param project_directory: Path to the project directory, used to get the project configuration.
:param image: Optional custom Docker image. Defaults to the project configuration if not provided.
:param update: Whether to update the Docker image.
:param no_update: Whether to skip updating the Docker image.
:param is_engine_image: True to manage the 'engine-image', False to manage the 'research-image'.
:return: A tuple containing the engine image, its version label, and the project configuration.
"""

project_config = None
image_project_config = None
image_type_name = "engine-image" if is_engine_image else "research-image"
if project_directory:
project_config = self.project_config_manager.get_project_config(project_directory)
image_project_config = project_config.get(image_type_name, None)

if is_engine_image:
engine_image = self.cli_config_manager.get_engine_image(image or image_project_config)
else:
engine_image = self.cli_config_manager.get_research_image(image or image_project_config)

container.update_manager.pull_docker_image_if_necessary(engine_image, update, no_update)

container_module_version = container.docker_manager.get_image_label(
engine_image, CONTAINER_LABEL_LEAN_VERSION_NAME, None
)

default_image_name = DEFAULT_ENGINE_IMAGE if is_engine_image else DEFAULT_RESEARCH_IMAGE
if str(engine_image) != default_image_name:
self.logger.warn(f'A custom {image_type_name} image: "{engine_image}" is being used!')

return engine_image, container_module_version, project_config


container = Container()
container.data_downloader.update_database_files()

0 comments on commit 2456258

Please sign in to comment.