Skip to content

Commit 3eeae3c

Browse files
Merge pull request #132 from rjra2611/bug-fix-download-missing-modules-when-envrionment-given
Install modules when using environment option
2 parents e97b595 + a19de3f commit 3eeae3c

File tree

4 files changed

+78
-12
lines changed

4 files changed

+78
-12
lines changed

lean/commands/live/deploy.py

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@
1616
import time
1717
from datetime import datetime
1818
from pathlib import Path
19-
from typing import Any, Dict, List, Optional
19+
from typing import Any, Dict, List, Optional, Tuple
2020
import click
2121
from lean.click import LeanCommand, PathParameter, ensure_options
2222
from lean.constants import DEFAULT_ENGINE_IMAGE
2323
from lean.container import container
2424
from lean.models.brokerages.local import all_local_brokerages, local_brokerage_data_feeds, all_local_data_feeds
2525
from lean.models.errors import MoreInfoError
26+
from lean.models.lean_config_configurer import LeanConfigConfigurer
2627
from lean.models.logger import Option
27-
from lean.models.configuration import Configuration, InfoConfiguration, InternalInputUserInput
28+
from lean.models.configuration import Configuration, InfoConfiguration, InternalInputUserInput, OrganzationIdConfiguration
2829
from lean.models.click_options import options_from_json
2930
from lean.models.json_module import JsonModule
3031
from lean.commands.live.live import live
@@ -38,12 +39,13 @@
3839
"real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler"
3940
}
4041

41-
def _raise_for_missing_properties(lean_config: Dict[str, Any], environment_name: str, lean_config_path: Path) -> None:
42-
"""Raises an error if any required properties are missing.
42+
43+
def _get_configurable_modules_from_environment(lean_config: Dict[str, Any], environment_name: str) -> Tuple[LeanConfigConfigurer, List[LeanConfigConfigurer]]:
44+
"""Returns the configurable modules from the given environment.
4345
4446
:param lean_config: the LEAN configuration that should be used
4547
:param environment_name: the name of the environment
46-
:param lean_config_path: the path to the LEAN configuration file
48+
:return: the configurable modules from the given environment
4749
"""
4850
environment = lean_config["environments"][environment_name]
4951
for key in ["live-mode-brokerage", "data-queue-handler"]:
@@ -53,9 +55,34 @@ def _raise_for_missing_properties(lean_config: Dict[str, Any], environment_name:
5355

5456
brokerage = environment["live-mode-brokerage"]
5557
data_queue_handlers = environment["data-queue-handler"]
56-
5758
[brokerage_configurer] = [local_brokerage for local_brokerage in all_local_brokerages if local_brokerage.get_live_name(environment_name) == brokerage]
5859
data_feed_configurers = [local_data_feed for local_data_feed in all_local_data_feeds if local_data_feed.get_live_name(environment_name) in data_queue_handlers]
60+
return brokerage_configurer, data_feed_configurers
61+
62+
63+
def _install_modules(modules: List[LeanConfigConfigurer], user_kwargs: Dict[str, Any]) -> None:
64+
"""Raises an error if any of the given modules are not installed.
65+
66+
:param modules: the modules to check
67+
"""
68+
for module in modules:
69+
if not module._installs:
70+
continue
71+
organization_id = module.get_organzation_id()
72+
if organization_id is None or organization_id == "":
73+
[organization_config] = module.get_config_from_type(OrganzationIdConfiguration)
74+
organization_id = _get_non_interactive_organization_id(module, organization_config, user_kwargs)
75+
module.ensure_module_installed(organization_id)
76+
77+
78+
def _raise_for_missing_properties(lean_config: Dict[str, Any], environment_name: str, lean_config_path: Path) -> None:
79+
"""Raises an error if any required properties are missing.
80+
81+
:param lean_config: the LEAN configuration that should be used
82+
:param environment_name: the name of the environment
83+
:param lean_config_path: the path to the LEAN configuration file
84+
"""
85+
brokerage_configurer, data_feed_configurers = _get_configurable_modules_from_environment(lean_config, environment_name)
5986
brokerage_properties = brokerage_configurer.get_required_properties()
6087
data_queue_handler_properties = []
6188
for data_feed_configurer in data_feed_configurers:
@@ -136,7 +163,7 @@ def _configure_lean_config_interactively(lean_config: Dict[str, Any], environmen
136163
essential_properties_value = {config._id : config._value for config in brokerage.get_essential_configs()}
137164
data_feed.update_configs(essential_properties_value)
138165
container.logger().debug(f"interactive: essential_properties_value: {brokerage._id} {essential_properties_value}")
139-
# now required properties can be fetched as per data/filter provider from esssential properties
166+
# now required properties can be fetched as per data/filter provider from essential properties
140167
required_properties_value = {config._id : config._value for config in brokerage.get_required_configs([InternalInputUserInput])}
141168
data_feed.update_configs(required_properties_value)
142169
container.logger().debug(f"interactive: required_properties_value: {required_properties_value}")
@@ -178,6 +205,10 @@ def _get_organization_id(given_input: Optional[str], label: str) -> str:
178205
container.logger().debug(f"deploy._get_organization_id: user selected organization id: {organization.id}")
179206
return organization.id
180207

208+
def _get_non_interactive_organization_id(module: LeanConfigConfigurer,
209+
organization_config: OrganzationIdConfiguration, user_kwargs: Dict[str, Any]) -> str:
210+
return _get_organization_id(user_kwargs[module._convert_lean_key_to_variable(organization_config._id)], module._id)
211+
181212
def _get_and_build_module(target_module_name: str, module_list: List[JsonModule], properties: Dict[str, Any]):
182213
[target_module] = [module for module in module_list if module.get_name() == target_module_name]
183214
# update essential properties from brokerage to datafeed
@@ -187,12 +218,12 @@ def _get_and_build_module(target_module_name: str, module_list: List[JsonModule]
187218
essential_properties_value = {target_module._convert_variable_to_lean_key(prop) : properties[prop] for prop in essential_properties}
188219
target_module.update_configs(essential_properties_value)
189220
container.logger().debug(f"non-interactive: essential_properties_value with module {target_module_name}: {essential_properties_value}")
190-
# now required properties can be fetched as per data/filter provider from esssential properties
221+
# now required properties can be fetched as per data/filter provider from essential properties
191222
required_properties: List[str] = []
192223
organization_info: Dict[str,str] = {}
193224
for config in target_module.get_required_configs([InternalInputUserInput]):
194225
if config.is_type_organization_id:
195-
organization_info[config._id] = _get_organization_id(properties[target_module._convert_lean_key_to_variable(config._id)], target_module._id)
226+
organization_info[config._id] = _get_non_interactive_organization_id(target_module, config, properties)
196227
properties[target_module._convert_lean_key_to_variable(config._id)] = organization_info[config._id]
197228
# skip organization id from ensure_options() because it is fetched using _get_organization_id()
198229
continue
@@ -362,6 +393,9 @@ def deploy(project: Path,
362393
raise MoreInfoError(f"The '{environment_name}' is not a live trading environment (live-mode is set to false)",
363394
"https://www.lean.io/docs/lean-cli/live-trading")
364395

396+
env_brokerage, env_data_queue_handlers = _get_configurable_modules_from_environment(lean_config, environment_name)
397+
_install_modules([env_brokerage] + env_data_queue_handlers, kwargs)
398+
365399
_raise_for_missing_properties(lean_config, environment_name, lean_config_manager.get_lean_config_path())
366400

367401
project_config_manager = container.project_config_manager()

lean/models/json_module.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ def get_configurations_env_values_from_name(self, target_env: str) -> List[Dict[
8989
env_config_values = env_config._env_and_values[target_env]
9090
return env_config_values
9191

92+
def get_config_from_type(self, config_type: Configuration) -> str:
93+
return [copy.copy(config) for config in self._lean_configs if type(config) is config_type]
94+
9295
def get_organzation_id(self) -> str:
9396
[organization_id] = [
9497
config._value for config in self._lean_configs if config.is_type_organization_id]

lean/models/lean_config_configurer.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ def _configure_environment(self, lean_config: Dict[str, Any], environment_name:
3939
:param lean_config: the Lean configuration dict to write to
4040
:param environment_name: the name of the environment to update
4141
"""
42-
self.ensure_module_installed()
4342
for environment_config in self.get_configurations_env_values_from_name(environment_name):
4443
environment_config_name = environment_config["name"]
4544
if self.__class__.__name__ == 'DataFeed':
@@ -86,11 +85,11 @@ def configure_credentials(self, lean_config: Dict[str, Any]) -> None:
8685
container.logger().debug(f"LeanConfigConfigurer.ensure_module_installed(): _save_properties for module {self._id}: {self.get_required_properties()}")
8786
self._save_properties(lean_config, self.get_required_properties())
8887

89-
def ensure_module_installed(self) -> None:
88+
def ensure_module_installed(self, organization_id: str) -> None:
9089
if not self._is_module_installed and self._installs:
9190
container.logger().debug(f"LeanConfigConfigurer.ensure_module_installed(): installing module for module {self._id}: {self._product_id}")
9291
container.module_manager().install_module(
93-
self._product_id, self.get_organzation_id())
92+
self._product_id, organization_id)
9493
self._is_module_installed = True
9594

9695
def _get_default(cls, lean_config: Dict[str, Any], key: str) -> Optional[Any]:

tests/commands/test_live.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ def test_live_calls_lean_runner_with_correct_algorithm_file() -> None:
7575
lean_runner = mock.Mock()
7676
container.lean_runner.override(providers.Object(lean_runner))
7777

78+
api_client = mock.MagicMock()
79+
api_client.organizations.get_all.return_value = [
80+
QCMinimalOrganization(id="abc", name="abc", type="type", ownerName="You", members=1, preferred=True)
81+
]
82+
container.api_client.override(providers.Object(api_client))
83+
7884
result = CliRunner().invoke(lean, ["live", "Python Project", "--environment", "live-paper"])
7985

8086
traceback.print_exception(*result.exc_info)
@@ -134,6 +140,12 @@ def test_live_calls_lean_runner_with_default_output_directory() -> None:
134140
lean_runner = mock.Mock()
135141
container.lean_runner.override(providers.Object(lean_runner))
136142

143+
api_client = mock.MagicMock()
144+
api_client.organizations.get_all.return_value = [
145+
QCMinimalOrganization(id="abc", name="abc", type="type", ownerName="You", members=1, preferred=True)
146+
]
147+
container.api_client.override(providers.Object(api_client))
148+
137149
result = CliRunner().invoke(lean, ["live", "Python Project", "--environment", "live-paper"])
138150

139151
assert result.exit_code == 0
@@ -155,6 +167,12 @@ def test_live_calls_lean_runner_with_custom_output_directory() -> None:
155167
lean_runner = mock.Mock()
156168
container.lean_runner.override(providers.Object(lean_runner))
157169

170+
api_client = mock.MagicMock()
171+
api_client.organizations.get_all.return_value = [
172+
QCMinimalOrganization(id="abc", name="abc", type="type", ownerName="You", members=1, preferred=True)
173+
]
174+
container.api_client.override(providers.Object(api_client))
175+
158176
result = CliRunner().invoke(lean, ["live",
159177
"Python Project",
160178
"--environment", "live-paper",
@@ -179,6 +197,12 @@ def test_live_calls_lean_runner_with_release_mode() -> None:
179197
lean_runner = mock.Mock()
180198
container.lean_runner.override(providers.Object(lean_runner))
181199

200+
api_client = mock.MagicMock()
201+
api_client.organizations.get_all.return_value = [
202+
QCMinimalOrganization(id="abc", name="abc", type="type", ownerName="You", members=1, preferred=True)
203+
]
204+
container.api_client.override(providers.Object(api_client))
205+
182206
result = CliRunner().invoke(lean, ["live", "CSharp Project", "--environment", "live-paper", "--release"])
183207

184208
assert result.exit_code == 0
@@ -203,6 +227,12 @@ def test_live_calls_lean_runner_with_detach() -> None:
203227
lean_runner = mock.Mock()
204228
container.lean_runner.override(providers.Object(lean_runner))
205229

230+
api_client = mock.MagicMock()
231+
api_client.organizations.get_all.return_value = [
232+
QCMinimalOrganization(id="abc", name="abc", type="type", ownerName="You", members=1, preferred=True)
233+
]
234+
container.api_client.override(providers.Object(api_client))
235+
206236
result = CliRunner().invoke(lean, ["live", "Python Project", "--environment", "live-paper", "--detach"])
207237

208238
assert result.exit_code == 0

0 commit comments

Comments
 (0)