Skip to content

Commit

Permalink
Import statements improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
Martin-Molinero committed Nov 3, 2022
1 parent a0024ff commit 57040d2
Show file tree
Hide file tree
Showing 89 changed files with 761 additions and 722 deletions.
8 changes: 4 additions & 4 deletions lean/click.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import itertools
import os
import shutil
import sys
from datetime import datetime
from pathlib import Path
from typing import Optional, List
Expand Down Expand Up @@ -79,13 +75,16 @@ def invoke(self, ctx: click.Context):
"https://www.lean.io/docs/v2/lean-cli/key-concepts/troubleshooting#02-Common-Errors"
)

import sys
if self._requires_docker and "pytest" not in sys.modules:
import os
is_system_linux = container.platform_manager().is_system_linux()

# The CLI uses temporary directories in /tmp because sometimes it may leave behind files owned by root
# These files cannot be deleted by the CLI itself, so we rely on the OS to empty /tmp on reboot
# The Snap version of Docker does not provide access to files outside $HOME, so we can't support it
if is_system_linux:
import shutil
docker_path = shutil.which("docker")
if docker_path is not None and docker_path.startswith("/snap"):
raise MoreInfoError(
Expand All @@ -102,6 +101,7 @@ def invoke(self, ctx: click.Context):
os.execlp(args[0], *args)

if self._allow_unknown_options:
import itertools
# Unknown options are passed to ctx.args and need to be parsed manually
# We parse them to ctx.params so they're available like normal options
# Because of this all commands with allow_unknown_options=True must have a **kwargs argument
Expand Down
59 changes: 32 additions & 27 deletions lean/commands/backtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import os
from datetime import datetime
from json import dumps, loads

from pathlib import Path
from typing import Optional
import click
from click import command, option, argument, Choice
from lean.click import LeanCommand, PathParameter
from lean.constants import DEFAULT_ENGINE_IMAGE, LEAN_ROOT_PATH
from lean.container import container, Logger
Expand All @@ -35,6 +34,9 @@
# These methods checks if the project has outdated configurations, and if so, update them to keep it working.

def _migrate_python_pycharm(logger: Logger, project_dir: Path) -> None:
from os import path
from click import Abort

workspace_xml_path = project_dir / ".idea" / "workspace.xml"
if not workspace_xml_path.is_file():
return
Expand All @@ -54,8 +56,9 @@ def _migrate_python_pycharm(logger: Logger, project_dir: Path) -> None:
has_library_mapping = False

library_dir = container.lean_config_manager().get_cli_root_directory() / "Library"

if library_dir.is_dir():
library_dir = f"$PROJECT_DIR$/{os.path.relpath(library_dir, project_dir)}".replace("\\", "/")
library_dir = f"$PROJECT_DIR$/{path.relpath(library_dir, project_dir)}".replace("\\", "/")
else:
library_dir = None

Expand Down Expand Up @@ -83,15 +86,15 @@ def _migrate_python_pycharm(logger: Logger, project_dir: Path) -> None:
logger.warn("Your run configuration has been updated to work with the latest version of LEAN")
logger.warn("Please restart the debugger in PyCharm and run this command again")

raise click.Abort()
raise Abort()


def _migrate_python_vscode(project_dir: Path) -> None:
launch_json_path = project_dir / ".vscode" / "launch.json"
if not launch_json_path.is_file():
return

current_content = json.loads(launch_json_path.read_text(encoding="utf-8"))
current_content = loads(launch_json_path.read_text(encoding="utf-8"))
if "configurations" not in current_content or not isinstance(current_content["configurations"], list):
return

Expand Down Expand Up @@ -122,10 +125,12 @@ def _migrate_python_vscode(project_dir: Path) -> None:
made_changes = True

if made_changes:
launch_json_path.write_text(json.dumps(current_content, indent=4), encoding="utf-8")
launch_json_path.write_text(dumps(current_content, indent=4), encoding="utf-8")


def _migrate_csharp_rider(logger: Logger, project_dir: Path) -> None:
from click import Abort

made_changes = False
xml_manager = container.xml_manager()

Expand Down Expand Up @@ -157,15 +162,15 @@ def _migrate_csharp_rider(logger: Logger, project_dir: Path) -> None:
logger.warn(
"See https://www.lean.io/docs/v2/lean-cli/backtesting/debugging#05-C-and-Rider for the updated instructions")

raise click.Abort()
raise Abort()


def _migrate_csharp_vscode(project_dir: Path) -> None:
launch_json_path = project_dir / ".vscode" / "launch.json"
if not launch_json_path.is_file():
return

current_content = json.loads(launch_json_path.read_text(encoding="utf-8"))
current_content = loads(launch_json_path.read_text(encoding="utf-8"))
if "configurations" not in current_content or not isinstance(current_content["configurations"], list):
return

Expand Down Expand Up @@ -194,7 +199,7 @@ def _migrate_csharp_vscode(project_dir: Path) -> None:
"moduleLoad": False
}

launch_json_path.write_text(json.dumps(current_content, indent=4), encoding="utf-8")
launch_json_path.write_text(dumps(current_content, indent=4), encoding="utf-8")


def _migrate_csharp_csproj(project_dir: Path) -> None:
Expand Down Expand Up @@ -234,43 +239,43 @@ def _select_organization() -> QCMinimalOrganization:
return logger.prompt_list("Select the organization to purchase and download data with", options)


@click.command(cls=LeanCommand, requires_lean_config=True, requires_docker=True)
@click.argument("project", type=PathParameter(exists=True, file_okay=True, dir_okay=True))
@click.option("--output",
@command(cls=LeanCommand, requires_lean_config=True, requires_docker=True)
@argument("project", type=PathParameter(exists=True, file_okay=True, dir_okay=True))
@option("--output",
type=PathParameter(exists=False, file_okay=False, dir_okay=True),
help="Directory to store results in (defaults to PROJECT/backtests/TIMESTAMP)")
@click.option("--detach", "-d",
@option("--detach", "-d",
is_flag=True,
default=False,
help="Run the backtest in a detached Docker container and return immediately")
@click.option("--debug",
type=click.Choice(["pycharm", "ptvsd", "vsdbg", "rider"], case_sensitive=False),
@option("--debug",
type=Choice(["pycharm", "ptvsd", "vsdbg", "rider"], case_sensitive=False),
help="Enable a certain debugging method (see --help for more information)")
@click.option("--data-provider",
type=click.Choice([dp.get_name() for dp in all_data_providers], case_sensitive=False),
@option("--data-provider",
type=Choice([dp.get_name() for dp in all_data_providers], case_sensitive=False),
help="Update the Lean configuration file to retrieve data from the given provider")
@click.option("--download-data",
@option("--download-data",
is_flag=True,
default=False,
help="Update the Lean configuration file to download data from the QuantConnect API, alias for --data-provider QuantConnect")
@click.option("--data-purchase-limit",
@option("--data-purchase-limit",
type=int,
help="The maximum amount of QCC to spend on downloading data during the backtest when using QuantConnect as data provider")
@click.option("--release",
@option("--release",
is_flag=True,
default=False,
help="Compile C# projects in release configuration instead of debug")
@click.option("--image",
@option("--image",
type=str,
help=f"The LEAN engine image to use (defaults to {DEFAULT_ENGINE_IMAGE})")
@click.option("--python-venv",
@option("--python-venv",
type=str,
help=f"The path of the python virtual environment to be used")
@click.option("--update",
@option("--update",
is_flag=True,
default=False,
help="Pull the LEAN engine image before running the backtest")
@click.option("--backtest-name",
@option("--backtest-name",
type=str,
help="Backtest name")
def backtest(project: Path,
Expand Down Expand Up @@ -303,7 +308,7 @@ def backtest(project: Path,
project_manager = container.project_manager()
algorithm_file = project_manager.find_algorithm_file(Path(project))
lean_config_manager = container.lean_config_manager()

from datetime import datetime
if output is None:
output = algorithm_file.parent / "backtests" / datetime.now().strftime("%Y-%m-%d_%H-%M-%S")

Expand Down
13 changes: 7 additions & 6 deletions lean/commands/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import re
from pathlib import Path
from typing import Optional

import click
from click import option, command, argument

from lean.click import LeanCommand, PathParameter
from lean.container import container
Expand Down Expand Up @@ -93,6 +92,8 @@ def _build_image(root: Path, dockerfile: Path, base_image: Optional[DockerImage]
:param base_image: the base image to use, or None if the default should be used
:param target_image: the name of the new image
"""
from re import MULTILINE, sub

logger = container.logger()
if base_image is not None:
logger.info(f"Building '{target_image}' from '{dockerfile}' using '{base_image}' as base image")
Expand All @@ -105,7 +106,7 @@ def _build_image(root: Path, dockerfile: Path, base_image: Optional[DockerImage]
current_content = dockerfile.read_text(encoding="utf-8")

if base_image is not None:
new_content = re.sub(r"^FROM.*$", f"FROM {base_image}", current_content, flags=re.MULTILINE)
new_content = sub(r"^FROM.*$", f"FROM {base_image}", current_content, flags=MULTILINE)
dockerfile.write_text(new_content, encoding="utf-8")

try:
Expand All @@ -116,9 +117,9 @@ def _build_image(root: Path, dockerfile: Path, base_image: Optional[DockerImage]
dockerfile.write_text(current_content, encoding="utf-8")


@click.command(cls=LeanCommand, requires_docker=True)
@click.argument("root", type=PathParameter(exists=True, file_okay=False, dir_okay=True), default=lambda: Path.cwd())
@click.option("--tag", type=str, default="latest", help="The tag to apply to custom images (defaults to latest)")
@command(cls=LeanCommand, requires_docker=True)
@argument("root", type=PathParameter(exists=True, file_okay=False, dir_okay=True), default=lambda: Path.cwd())
@option("--tag", type=str, default="latest", help="The tag to apply to custom images (defaults to latest)")
def build(root: Path, tag: str) -> None:
"""Build Docker images of your own version of LEAN.
Expand Down
4 changes: 2 additions & 2 deletions lean/commands/cloud/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import click
from click import group

from lean.commands.cloud.backtest import backtest
from lean.commands.cloud.live.live import live
Expand All @@ -21,7 +21,7 @@
from lean.commands.cloud.status import status


@click.group()
@group()
def cloud() -> None:
"""Interact with the QuantConnect cloud."""
# This method is intentionally empty
Expand Down
17 changes: 8 additions & 9 deletions lean/commands/cloud/backtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import webbrowser
from typing import Optional
import click
from click import command, argument, option
from lean.click import LeanCommand
from lean.container import container
from pathlib import Path
from lean.models.errors import RequestFailedError

@click.command(cls=LeanCommand)
@click.argument("project", type=str)
@click.option("--name", type=str, help="The name of the backtest (a random one is generated if not specified)")
@click.option("--push",
@command(cls=LeanCommand)
@argument("project", type=str)
@option("--name", type=str, help="The name of the backtest (a random one is generated if not specified)")
@option("--push",
is_flag=True,
default=False,
help="Push local modifications to the cloud before running the backtest")
@click.option("--open", "open_browser",
@option("--open", "open_browser",
is_flag=True,
default=False,
help="Automatically open the results in the browser when the backtest is finished")
Expand Down Expand Up @@ -79,4 +77,5 @@ def backtest(project: str, name: Optional[str], push: bool, open_browser: bool)
open_browser = False

if open_browser:
webbrowser.open(finished_backtest.get_url())
from webbrowser import open
open(finished_backtest.get_url())
Loading

0 comments on commit 57040d2

Please sign in to comment.