Skip to content

Commit 2456258

Browse files
authored
Fix: not pulling docker image (#503)
* 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
1 parent 55e773d commit 2456258

File tree

8 files changed

+68
-88
lines changed

8 files changed

+68
-88
lines changed

lean/commands/backtest.py

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from click import command, option, argument, Choice
1717

1818
from lean.click import LeanCommand, PathParameter
19-
from lean.constants import DEFAULT_ENGINE_IMAGE, LEAN_ROOT_PATH, CONTAINER_LABEL_LEAN_VERSION_NAME
19+
from lean.constants import DEFAULT_ENGINE_IMAGE, LEAN_ROOT_PATH
2020
from lean.container import container, Logger
2121
from lean.models.utils import DebuggingMethod
2222
from lean.models.cli import cli_data_downloaders, cli_addon_modules
@@ -359,14 +359,8 @@ def backtest(project: Path,
359359
organization_id = container.organization_manager.try_get_working_organization_id()
360360
paths_to_mount = None
361361

362-
cli_config_manager = container.cli_config_manager
363-
project_config_manager = container.project_config_manager
364-
365-
project_config = project_config_manager.get_project_config(algorithm_file.parent)
366-
engine_image = cli_config_manager.get_engine_image(image or project_config.get("engine-image", None))
367-
368-
container_module_version = container.docker_manager.get_image_label(engine_image,
369-
CONTAINER_LABEL_LEAN_VERSION_NAME, None)
362+
engine_image, container_module_version, project_config = container.manage_docker_image(image, update, no_update,
363+
algorithm_file.parent)
370364

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

378372
lean_config_manager.configure_data_purchase_limit(lean_config, data_purchase_limit)
379373

380-
if str(engine_image) != DEFAULT_ENGINE_IMAGE:
381-
logger.warn(f'A custom engine image: "{engine_image}" is being used!')
382-
383-
container.update_manager.pull_docker_image_if_necessary(engine_image, update, no_update)
384-
385374
if not output.exists():
386375
output.mkdir(parents=True)
387376

lean/commands/data/download.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from click import command, option, confirm, pass_context, Context, Choice, prompt
1919
from lean.click import LeanCommand, ensure_options
2020
from lean.components.util.json_modules_handler import config_build_for_name
21-
from lean.constants import DEFAULT_ENGINE_IMAGE, CONTAINER_LABEL_LEAN_VERSION_NAME
21+
from lean.constants import DEFAULT_ENGINE_IMAGE
2222
from lean.container import container
2323
from lean.models.api import QCDataInformation, QCDataVendor, QCFullOrganization, QCDatasetDelivery, QCResolution, QCSecurityType, QCDataType
2424
from lean.models.click_options import get_configs_for_options, options_from_json
@@ -675,16 +675,7 @@ def download(ctx: Context,
675675
logger = container.logger
676676
lean_config = container.lean_config_manager.get_complete_lean_config(None, None, None)
677677

678-
engine_image = container.cli_config_manager.get_engine_image(image)
679-
680-
if str(engine_image) != DEFAULT_ENGINE_IMAGE:
681-
# Custom engine image should not be updated.
682-
logger.warn(f'A custom engine image: "{engine_image}" is being used!')
683-
684-
container.update_manager.pull_docker_image_if_necessary(engine_image, update, no_update)
685-
686-
container_module_version = container.docker_manager.get_image_label(engine_image,
687-
CONTAINER_LABEL_LEAN_VERSION_NAME, None)
678+
engine_image, container_module_version, project_config = container.manage_docker_image(image, update, no_update)
688679

689680
data_downloader_provider = config_build_for_name(lean_config, data_downloader_provider.get_name(),
690681
cli_data_downloaders, kwargs, logger, interactive=True)

lean/commands/data/generate.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,9 +208,7 @@ def generate(start: datetime,
208208
}
209209
}
210210

211-
engine_image = container.cli_config_manager.get_engine_image(image)
212-
213-
container.update_manager.pull_docker_image_if_necessary(engine_image, update)
211+
engine_image, container_module_version, project_config = container.manage_docker_image(image, update, no_update=False)
214212

215213
success = container.docker_manager.run_image(engine_image, **run_options)
216214
if not success:

lean/commands/live/deploy.py

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from click import option, argument, Choice
1717
from lean.click import LeanCommand, PathParameter
1818
from lean.components.util.name_rename import rename_internal_config_to_user_friendly_format
19-
from lean.constants import DEFAULT_ENGINE_IMAGE, CONTAINER_LABEL_LEAN_VERSION_NAME
19+
from lean.constants import DEFAULT_ENGINE_IMAGE
2020
from lean.container import container
2121
from lean.models.cli import (cli_brokerages, cli_data_queue_handlers, cli_data_downloaders,
2222
cli_addon_modules, cli_history_provider)
@@ -271,14 +271,8 @@ def deploy(project: Path,
271271
kwargs, logger, interactive=True,
272272
environment_name=environment_name))
273273

274-
project_config_manager = container.project_config_manager
275-
cli_config_manager = container.cli_config_manager
276-
277-
project_config = project_config_manager.get_project_config(algorithm_file.parent)
278-
engine_image = cli_config_manager.get_engine_image(image or project_config.get("engine-image", None))
279-
280-
container_module_version = container.docker_manager.get_image_label(engine_image,
281-
CONTAINER_LABEL_LEAN_VERSION_NAME, None)
274+
engine_image, container_module_version, project_config = container.manage_docker_image(image, update, no_update,
275+
algorithm_file.parent)
282276

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

294-
container.update_manager.pull_docker_image_if_necessary(engine_image, update, no_update)
295-
296288
_start_iqconnect_if_necessary(lean_config, environment_name)
297289

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

301-
cash_balance_option, holdings_option, last_cash, last_holdings = get_last_portfolio_cash_holdings(container.api_client, brokerage_instance,
302-
project_config.get("cloud-id", None), project)
293+
cash_balance_option, holdings_option, last_cash, last_holdings = get_last_portfolio_cash_holdings(
294+
container.api_client, brokerage_instance, project_config.get("cloud-id", None), project)
303295

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

339-
if str(engine_image) != DEFAULT_ENGINE_IMAGE:
340-
logger.warn(f'A custom engine image: "{engine_image}" is being used!')
341-
342331
# Set extra config
343332
given_algorithm_id = None
344333
for key, value in extra_config:

lean/commands/optimize.py

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

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

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

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

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

289-
project_config_manager = container.project_config_manager
290-
cli_config_manager = container.cli_config_manager
291-
292-
project_config = project_config_manager.get_project_config(algorithm_file.parent)
293-
engine_image = cli_config_manager.get_engine_image(image or project_config.get("engine-image", None))
294-
295289
logger = container.logger
296290

297-
if str(engine_image) != DEFAULT_ENGINE_IMAGE:
298-
logger.warn(f'A custom engine image: "{engine_image}" is being used!')
299-
300291
lean_config_manager = container.lean_config_manager
301292
lean_config = lean_config_manager.get_complete_lean_config(environment_name, algorithm_file, None)
302293

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

308299
paths_to_mount = None
309300

310-
container_module_version = container.docker_manager.get_image_label(engine_image,
311-
CONTAINER_LABEL_LEAN_VERSION_NAME, None)
312-
313301
if data_provider_historical is not None:
314302
data_provider = non_interactive_config_build_for_name(lean_config, data_provider_historical,
315303
cli_data_downloaders, kwargs, logger, environment_name)
@@ -342,8 +330,6 @@ def optimize(project: Path,
342330
build_and_configure_modules(addon_module, cli_addon_modules, organization_id, lean_config,
343331
kwargs, logger, environment_name, container_module_version)
344332

345-
container.update_manager.pull_docker_image_if_necessary(engine_image, update, no_update)
346-
347333
run_options = lean_runner.get_basic_docker_config(lean_config, algorithm_file, output, None, release, should_detach,
348334
engine_image, paths_to_mount)
349335

lean/commands/report.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -258,17 +258,8 @@ def report(backtest_results: Optional[Path],
258258
type="bind",
259259
read_only=True))
260260

261-
cli_config_manager = container.cli_config_manager
262-
engine_image_override = image
263-
264-
if engine_image_override is None and project_directory is not None:
265-
project_config_manager = container.project_config_manager
266-
project_config = project_config_manager.get_project_config(project_directory)
267-
engine_image_override = project_config.get("engine-image", None)
268-
269-
engine_image = cli_config_manager.get_engine_image(engine_image_override)
270-
271-
container.update_manager.pull_docker_image_if_necessary(engine_image, update)
261+
engine_image, container_module_version, project_config = container.manage_docker_image(image, update, False,
262+
project_directory)
272263

273264
success = container.docker_manager.run_image(engine_image, **run_options)
274265
if not success:

lean/commands/research.py

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from click import command, argument, option, Choice
1717
from lean.click import LeanCommand, PathParameter
1818
from lean.components.docker.lean_runner import LeanRunner
19-
from lean.constants import DEFAULT_RESEARCH_IMAGE, LEAN_ROOT_PATH, CONTAINER_LABEL_LEAN_VERSION_NAME
19+
from lean.constants import DEFAULT_RESEARCH_IMAGE, LEAN_ROOT_PATH
2020
from lean.container import container
2121
from lean.models.cli import cli_data_downloaders
2222
from lean.components.util.name_extraction import convert_to_class_name
@@ -118,19 +118,9 @@ def research(project: Path,
118118
if download_data:
119119
data_provider_historical = "QuantConnect"
120120

121-
project_config_manager = container.project_config_manager
122-
cli_config_manager = container.cli_config_manager
123-
124-
project_config = project_config_manager.get_project_config(algorithm_file.parent)
125-
research_image = cli_config_manager.get_research_image(image or project_config.get("research-image", None))
126-
127-
container.update_manager.pull_docker_image_if_necessary(research_image, update, no_update)
128-
129-
container_module_version = container.docker_manager.get_image_label(research_image,
130-
CONTAINER_LABEL_LEAN_VERSION_NAME, None)
131-
132-
if str(research_image) != DEFAULT_RESEARCH_IMAGE:
133-
logger.warn(f'A custom research image: "{research_image}" is being used!')
121+
research_image, container_module_version, project_config = container.manage_docker_image(image, update, no_update,
122+
algorithm_file.parent,
123+
False)
134124

135125
paths_to_mount = None
136126

lean/container.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
# See the License for the specific language governing permissions and
1212
# limitations under the License.
1313

14-
from typing import Union, Any
14+
from pathlib import Path
15+
from typing import Union, Any, Optional, Tuple
1516

1617
from lean.components.api.api_client import APIClient
1718
from lean.components.config.lean_config_manager import LeanConfigManager
@@ -41,12 +42,16 @@
4142
from lean.components.util.temp_manager import TempManager
4243
from lean.components.util.update_manager import UpdateManager
4344
from lean.components.util.xml_manager import XMLManager
44-
from lean.constants import CACHE_PATH, CREDENTIALS_CONFIG_PATH, GENERAL_CONFIG_PATH
45+
from lean.constants import CACHE_PATH, CREDENTIALS_CONFIG_PATH, GENERAL_CONFIG_PATH, DEFAULT_RESEARCH_IMAGE
46+
from lean.constants import DEFAULT_ENGINE_IMAGE, CONTAINER_LABEL_LEAN_VERSION_NAME
47+
from lean.models.docker import DockerImage
4548

4649

4750
class Container:
4851

4952
def __init__(self):
53+
self.project_config_manager = None
54+
self.cli_config_manager = None
5055
self.initialize()
5156

5257
def initialize(self,
@@ -161,6 +166,47 @@ def initialize(self,
161166

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

169+
def manage_docker_image(self, image: Optional[str], update: bool, no_update: bool,
170+
project_directory: Path = None,
171+
is_engine_image: bool = True) -> Tuple[DockerImage, str, Optional[Storage]]:
172+
"""
173+
Manages the Docker image for the LEAN engine by:
174+
1. Retrieving the engine image from the provided image or project config.
175+
2. Pulling the image if necessary based on the update flags.
176+
3. Logging a warning if a custom image is used.
177+
178+
:param project_directory: Path to the project directory, used to get the project configuration.
179+
:param image: Optional custom Docker image. Defaults to the project configuration if not provided.
180+
:param update: Whether to update the Docker image.
181+
:param no_update: Whether to skip updating the Docker image.
182+
:param is_engine_image: True to manage the 'engine-image', False to manage the 'research-image'.
183+
:return: A tuple containing the engine image, its version label, and the project configuration.
184+
"""
185+
186+
project_config = None
187+
image_project_config = None
188+
image_type_name = "engine-image" if is_engine_image else "research-image"
189+
if project_directory:
190+
project_config = self.project_config_manager.get_project_config(project_directory)
191+
image_project_config = project_config.get(image_type_name, None)
192+
193+
if is_engine_image:
194+
engine_image = self.cli_config_manager.get_engine_image(image or image_project_config)
195+
else:
196+
engine_image = self.cli_config_manager.get_research_image(image or image_project_config)
197+
198+
container.update_manager.pull_docker_image_if_necessary(engine_image, update, no_update)
199+
200+
container_module_version = container.docker_manager.get_image_label(
201+
engine_image, CONTAINER_LABEL_LEAN_VERSION_NAME, None
202+
)
203+
204+
default_image_name = DEFAULT_ENGINE_IMAGE if is_engine_image else DEFAULT_RESEARCH_IMAGE
205+
if str(engine_image) != default_image_name:
206+
self.logger.warn(f'A custom {image_type_name} image: "{engine_image}" is being used!')
207+
208+
return engine_image, container_module_version, project_config
209+
164210

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

0 commit comments

Comments
 (0)