Ejemplo n.º 1
0
def test_http_client_makes_request(requests_mock: RequestsMock, method: str, use_request: bool) -> None:
    requests_mock.add(method.upper(), EXAMPLE_URL, "Example body")

    http_client = HTTPClient(mock.Mock())

    if use_request:
        response = http_client.request(method, EXAMPLE_URL)
    else:
        response = getattr(http_client, method)(EXAMPLE_URL)

    assert response.text == "Example body"
Ejemplo n.º 2
0
def test_http_client_raises_when_request_unsuccessful(requests_mock: RequestsMock,
                                                      method: str,
                                                      use_request: bool) -> None:
    requests_mock.add(method.upper(), EXAMPLE_URL, status=404)

    http_client = HTTPClient(mock.Mock())

    with pytest.raises(requests.HTTPError):
        if use_request:
            http_client.request(method, EXAMPLE_URL)
        else:
            getattr(http_client, method)(EXAMPLE_URL)
Ejemplo n.º 3
0
def test_http_client_does_not_raise_when_raise_for_status_false(requests_mock: RequestsMock,
                                                                method: str,
                                                                use_request: bool) -> None:
    requests_mock.add(method.upper(), EXAMPLE_URL, status=404)

    http_client = HTTPClient(mock.Mock())

    if use_request:
        response = http_client.request(method, EXAMPLE_URL, raise_for_status=False)
    else:
        response = getattr(http_client, method)(EXAMPLE_URL, raise_for_status=False)

    assert response.status_code == 404
Ejemplo n.º 4
0
def test_http_client_logs_debug_message_about_request(requests_mock: RequestsMock,
                                                      method: str,
                                                      use_request: bool) -> None:
    requests_mock.add(method.upper(), EXAMPLE_URL, "Example body")

    logger = mock.Mock()
    http_client = HTTPClient(logger)

    if use_request:
        http_client.request(method, EXAMPLE_URL)
    else:
        getattr(http_client, method)(EXAMPLE_URL)

    logger.debug.assert_called_once()
Ejemplo n.º 5
0
def test_http_client_logs_debug_message_about_response_when_unsuccessful(requests_mock: RequestsMock,
                                                                         method: str,
                                                                         use_request: bool) -> None:
    requests_mock.add(method.upper(), EXAMPLE_URL, "Example body", status=404)

    logger = mock.Mock()
    http_client = HTTPClient(logger)

    with pytest.raises(requests.HTTPError):
        if use_request:
            http_client.request(method, EXAMPLE_URL)
        else:
            getattr(http_client, method)(EXAMPLE_URL)

    assert logger.debug.call_count == 2
Ejemplo n.º 6
0
def test_api_client_raises_request_failed_error_on_failing_response_non_http_500(
        method: str, requests_mock: RequestsMock) -> None:
    requests_mock.add(method.upper(), API_BASE_URL + "endpoint", status=404)

    api = APIClient(mock.Mock(), HTTPClient(mock.Mock()), "123", "456")

    with pytest.raises(RequestFailedError):
        getattr(api, method)("endpoint")
Ejemplo n.º 7
0
def test_api_client_raises_authentication_error_on_http_500(
        method: str, requests_mock: RequestsMock) -> None:
    requests_mock.add(method.upper(), API_BASE_URL + "endpoint", status=500)

    api = APIClient(mock.Mock(), HTTPClient(mock.Mock()), "123", "456")

    with pytest.raises(AuthenticationError):
        getattr(api, method)("endpoint")
Ejemplo n.º 8
0
def test_data_client_get_info() -> None:
    api_client = create_api_client()
    account_client = AccountClient(api_client)
    data_client = DataClient(api_client, HTTPClient(mock.Mock()))

    # Test data information can be parsed
    preferred_organization = account_client.get_organization()
    data_client.get_info(preferred_organization.organizationId)
Ejemplo n.º 9
0
def create_objects() -> Tuple[mock.Mock, Storage, mock.Mock, UpdateManager]:
    logger = mock.Mock()
    storage = Storage(str(Path("~/.lean/cache").expanduser()))
    docker_manager = mock.Mock()

    update_manager = UpdateManager(logger, HTTPClient(logger), storage,
                                   docker_manager)

    return logger, storage, docker_manager, update_manager
Ejemplo n.º 10
0
def test_post_makes_post_request_to_given_endpoint(
        requests_mock: RequestsMock) -> None:
    requests_mock.add(requests_mock.POST, API_BASE_URL + "endpoint",
                      '{ "success": true }')

    api = APIClient(mock.Mock(), HTTPClient(mock.Mock()), "123", "456")
    api.post("endpoint")

    assert len(requests_mock.calls) == 1
    assert requests_mock.calls[0].request.url == API_BASE_URL + "endpoint"
Ejemplo n.º 11
0
def test_get_attaches_parameters_to_url(requests_mock: RequestsMock) -> None:
    requests_mock.add(requests_mock.GET, API_BASE_URL + "endpoint",
                      '{ "success": true }')

    api = APIClient(mock.Mock(), HTTPClient(mock.Mock()), "123", "456")
    api.get("endpoint", {"key1": "value1", "key2": "value2"})

    assert len(requests_mock.calls) == 1
    assert requests_mock.calls[
        0].request.url == API_BASE_URL + "endpoint?key1=value1&key2=value2"
Ejemplo n.º 12
0
def test_data_client_list_files() -> None:
    api_client = create_api_client()
    data_client = DataClient(api_client, HTTPClient(mock.Mock()))

    # Test files can be listed
    files = data_client.list_files("crypto/gdax/daily/")

    # Test all files start with the requested prefix
    for file in files:
        assert file.startswith("crypto/gdax/daily/")
Ejemplo n.º 13
0
def test_api_client_raises_authentication_error_on_error_complaining_about_hash(
        method: str, requests_mock: RequestsMock) -> None:
    requests_mock.add(
        method.upper(), API_BASE_URL + "endpoint",
        '{ "success": false, "errors": ["Hash doesn\'t match."] }')

    api = APIClient(mock.Mock(), HTTPClient(mock.Mock()), "123", "456")

    with pytest.raises(AuthenticationError):
        getattr(api, method)("endpoint")
Ejemplo n.º 14
0
def test_api_client_returns_data_when_success_is_true(
        method: str, requests_mock: RequestsMock) -> None:
    requests_mock.add(method.upper(), API_BASE_URL + "endpoint",
                      '{ "success": true }')

    api = APIClient(mock.Mock(), HTTPClient(mock.Mock()), "123", "456")
    response = getattr(api, method)("endpoint")

    assert "success" in response
    assert response["success"]
Ejemplo n.º 15
0
def test_api_client_raises_request_failed_error_when_response_contains_internal_error(
        method: str, requests_mock: RequestsMock) -> None:
    requests_mock.add(method.upper(), API_BASE_URL + "endpoint",
                      '{ "success": false, "Message": "Internal Error 21" }')

    api = APIClient(mock.Mock(), HTTPClient(mock.Mock()), "123", "456")

    with pytest.raises(RequestFailedError) as error:
        getattr(api, method)("endpoint")

    assert str(error.value) == "Internal Error 21"
Ejemplo n.º 16
0
def test_api_client_sets_user_agent(method: str,
                                    requests_mock: RequestsMock) -> None:
    requests_mock.add(method.upper(), API_BASE_URL + "endpoint",
                      '{ "success": true }')

    api = APIClient(mock.Mock(), HTTPClient(mock.Mock()), "123", "456")
    getattr(api, method)("endpoint")

    assert len(requests_mock.calls) == 1

    headers = requests_mock.calls[0].request.headers
    assert headers["User-Agent"].startswith("Lean CLI ")
Ejemplo n.º 17
0
def create_api_client() -> APIClient:
    if os.environ.get("QC_API", "") == "local":
        user_id = "123"
        api_token = "abc"
    else:
        user_id = USER_ID or os.environ.get("QC_USER_ID", "")
        api_token = API_TOKEN or os.environ.get("QC_API_TOKEN", "")

    if user_id == "" or api_token == "":
        pytest.skip("API credentials not specified")

    return APIClient(mock.Mock(), HTTPClient(mock.Mock()), user_id, api_token)
Ejemplo n.º 18
0
def test_api_client_retries_request_when_response_is_http_5xx_error(
        method: str, status_code: int, expected_error: Any,
        requests_mock: RequestsMock) -> None:
    requests_mock.add(method.upper(),
                      API_BASE_URL + "endpoint",
                      status=status_code)

    api = APIClient(mock.Mock(), HTTPClient(mock.Mock()), "123", "456")

    with pytest.raises(expected_error):
        getattr(api, method)("endpoint")

    requests_mock.assert_call_count(API_BASE_URL + "endpoint", 2)
Ejemplo n.º 19
0
def test_is_authenticated_returns_false_when_authenticated_request_fails(
        requests_mock: RequestsMock) -> None:
    requests_mock.assert_all_requests_are_fired = False
    requests_mock.add(requests_mock.GET,
                      re.compile(".*"),
                      body='{ "success": false }')
    requests_mock.add(requests_mock.POST,
                      re.compile(".*"),
                      body='{ "success": false }')

    api = APIClient(mock.Mock(), HTTPClient(mock.Mock()), "123", "456")

    assert not api.is_authenticated()
Ejemplo n.º 20
0
def test_api_client_makes_authenticated_requests(
        method: str, requests_mock: RequestsMock) -> None:
    requests_mock.add(method.upper(), API_BASE_URL + "endpoint",
                      '{ "success": true }')

    api = APIClient(mock.Mock(), HTTPClient(mock.Mock()), "123", "456")
    getattr(api, method)("endpoint")

    assert len(requests_mock.calls) == 1

    headers = requests_mock.calls[0].request.headers
    assert "Timestamp" in headers
    assert "Authorization" in headers
    assert headers["Authorization"].startswith("Basic ")
Ejemplo n.º 21
0
def test_post_sets_body_of_request_as_json(
        requests_mock: RequestsMock) -> None:
    requests_mock.add(requests_mock.POST, API_BASE_URL + "endpoint",
                      '{ "success": true }')

    api = APIClient(mock.Mock(), HTTPClient(mock.Mock()), "123", "456")
    api.post("endpoint", {"key1": "value1", "key2": "value2"})

    assert len(requests_mock.calls) == 1
    assert requests_mock.calls[0].request.url == API_BASE_URL + "endpoint"

    body = json.loads(requests_mock.calls[0].request.body)

    assert body["key1"] == "value1"
    assert body["key2"] == "value2"
Ejemplo n.º 22
0
def test_post_sets_body_of_request_as_form_data(
        requests_mock: RequestsMock) -> None:
    requests_mock.add(requests_mock.POST, API_BASE_URL + "endpoint",
                      '{ "success": true }')

    api = APIClient(mock.Mock(), HTTPClient(mock.Mock()), "123", "456")
    api.post("endpoint", {
        "key1": "value1",
        "key2": "value2"
    },
             data_as_json=False)

    assert len(requests_mock.calls) == 1
    assert requests_mock.calls[0].request.url == API_BASE_URL + "endpoint"

    assert requests_mock.calls[0].request.body == "key1=value1&key2=value2"
Ejemplo n.º 23
0
def test_cli() -> None:
    """Tests the CLI by actually calling it like a real user would do.

    Unlike "normal" tests, this file only contains a single test method which steps through all commands.
    This is done on purpose to make the test as close to what real users do as possible.
    """
    user_id = USER_ID or os.environ.get("QC_USER_ID", "")
    api_token = API_TOKEN or os.environ.get("QC_API_TOKEN", "")

    if user_id == "" or api_token == "":
        pytest.skip("API credentials not specified")

    credentials_path = Path("~/.lean").expanduser() / "credentials"

    # Create an empty directory to perform tests in
    test_dir = Path(tempfile.mkdtemp())

    # We use project names suffixed by a timestamp to prevent conflicts when we synchronize with the cloud
    timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
    python_project_name = f"Python Project {timestamp}"
    csharp_project_name = f"CSharp Project {timestamp}"

    # Log in
    run_command(["lean", "login"], input=[user_id, api_token])
    assert credentials_path.exists()
    assert json.loads(credentials_path.read_text(encoding="utf-8")) == {
        "user-id": user_id,
        "api-token": api_token
    }

    # Check that we are logged in
    run_command(["lean", "whoami"])

    # Download sample data and LEAN configuration file
    run_command(["lean", "init"], cwd=test_dir, input=["python"])
    assert (test_dir / "data").is_dir()
    assert (test_dir / "lean.json").is_file()

    # Generate random data
    # This is the first command that uses the LEAN Docker image, so we increase the timeout to have time to pull it
    generate_output = run_command([
        "lean", "data", "generate", "--start", "20150101", "--symbol-count",
        "1", "--resolution", "Daily"
    ],
                                  cwd=test_dir,
                                  timeout=600)
    matches = re.findall(
        r"Begin data generation of 1 randomly generated Equity assets\.\.\.\r?\n\s*Symbol\[1]: ([A-Z]+)",
        generate_output)
    assert len(matches) == 1
    assert (test_dir / "data" / "equity" / "usa" / "daily" /
            f"{matches[0].lower()}.zip").is_file()

    # Configure global settings
    run_command(["lean", "config", "set", "default-language", "csharp"])
    run_command(["lean", "config", "get", "default-language"],
                expected_output="csharp")
    run_command(["lean", "config", "unset", "default-language"])
    run_command(["lean", "config", "get", "default-language"],
                expected_return_code=1)
    run_command(["lean", "config", "set", "default-language", "python"])
    run_command(["lean", "config", "get", "default-language"],
                expected_output="python")
    list_output = run_command(["lean", "config", "list"])
    assert len(re.findall(r"default-language[ ]+[^ ] python",
                          list_output)) == 1

    # Create Python project
    run_command([
        "lean", "create-project", "--language", "python", python_project_name
    ],
                cwd=test_dir)
    python_project_dir = test_dir / python_project_name
    assert (python_project_dir / "main.py").is_file()
    assert (python_project_dir / "research.ipynb").is_file()
    assert (python_project_dir / "config.json").is_file()
    assert (python_project_dir / ".vscode" / "launch.json").is_file()
    assert (python_project_dir / ".vscode" / "settings.json").is_file()
    assert (python_project_dir / ".idea" /
            f"{python_project_name}.iml").is_file()
    assert (python_project_dir / ".idea" / "misc.xml").is_file()
    assert (python_project_dir / ".idea" / "modules.xml").is_file()
    assert (python_project_dir / ".idea" / "workspace.xml").is_file()

    # Create C# project
    run_command([
        "lean", "create-project", "--language", "csharp", csharp_project_name
    ],
                cwd=test_dir)
    csharp_project_dir = test_dir / csharp_project_name
    assert (csharp_project_dir / "Main.cs").is_file()
    assert (csharp_project_dir / "research.ipynb").is_file()
    assert (csharp_project_dir / "config.json").is_file()
    assert (csharp_project_dir / f"{csharp_project_name}.csproj").is_file()
    assert (csharp_project_dir / ".vscode" / "launch.json").is_file()

    # Add custom Python library
    run_command(["lean", "library", "add", python_project_name, "altair"],
                cwd=test_dir)
    assert (python_project_dir / "requirements.txt").is_file()
    assert f"altair==" in (python_project_dir /
                           "requirements.txt").read_text(encoding="utf-8")

    # Cannot add custom Python library incompatible with Python 3.6
    run_command(["lean", "library", "add", python_project_name, "PyS3DE"],
                cwd=test_dir,
                expected_return_code=1)

    # Cannot add custom Python library without version when it's not on PyPI
    run_command(
        ["lean", "library", "add", python_project_name,
         str(uuid.uuid4())],
        cwd=test_dir,
        expected_return_code=1)

    # Cannot add custom Python library with version when version is invalid
    run_command([
        "lean", "library", "add", python_project_name, "matplotlib",
        "--version", "0.0.0.0.0.1"
    ],
                cwd=test_dir,
                expected_return_code=1)

    # Cannot add custom Python library with version when version is incompatible with Python 3.6
    run_command([
        "lean", "library", "add", python_project_name, "matplotlib",
        "--version", "3.4.2"
    ],
                cwd=test_dir,
                expected_return_code=1)

    # Add custom C# library
    run_command(
        ["lean", "library", "add", csharp_project_name, "Microsoft.ML"],
        cwd=test_dir)
    csproj_file = csharp_project_dir / f"{csharp_project_name}.csproj"
    assert 'Include="Microsoft.ML"' in csproj_file.read_text(encoding="utf-8")

    # Cannot add custom C# library without version when it's not on NuGet
    run_command(
        ["lean", "library", "add", csharp_project_name,
         str(uuid.uuid4())],
        cwd=test_dir,
        expected_return_code=1)

    # Copy over algorithms containing a SPY buy-and-hold strategy with custom libraries
    fixtures_dir = Path(__file__).parent / "fixtures"
    shutil.copy(fixtures_dir / "local" / "main.py",
                python_project_dir / "main.py")
    shutil.copy(fixtures_dir / "local" / "Main.cs",
                csharp_project_dir / "Main.cs")

    # Backtest Python project locally
    run_command(["lean", "backtest", python_project_name],
                cwd=test_dir,
                expected_output="Total Trades 1")
    python_backtest_dirs = list((python_project_dir / "backtests").iterdir())
    assert len(python_backtest_dirs) == 1

    # Backtest C# project locally
    run_command(["lean", "backtest", csharp_project_name],
                cwd=test_dir,
                expected_output="Total Trades 1")
    csharp_backtest_dirs = list((csharp_project_dir / "backtests").iterdir())
    assert len(csharp_backtest_dirs) == 1

    # Remove custom Python library
    run_command(["lean", "library", "remove", python_project_name, "altair"],
                cwd=test_dir)
    assert f"altair==" not in (python_project_dir /
                               "requirements.txt").read_text(encoding="utf-8")

    # Remove custom C# library
    run_command(
        ["lean", "library", "remove", csharp_project_name, "Microsoft.ML"],
        cwd=test_dir)
    assert 'Include="Microsoft.ML"' not in csproj_file.read_text(
        encoding="utf-8")

    # Custom Python library is removed, so Python backtest should now fail
    run_command(["lean", "backtest", python_project_name],
                cwd=test_dir,
                expected_return_code=1)

    # Custom C# library is removed, so C# backtest should now fail
    run_command(["lean", "backtest", csharp_project_name],
                cwd=test_dir,
                expected_return_code=1)

    # Generate reports
    python_results_file = next(f for f in python_backtest_dirs[0].iterdir()
                               if f.name.endswith(".json")
                               and not f.name.endswith("-order-events.json"))
    run_command([
        "lean", "report", "--backtest-results",
        str(python_results_file), "--report-destination", "python.html"
    ],
                cwd=test_dir)

    csharp_results_file = next(f for f in csharp_backtest_dirs[0].iterdir()
                               if f.name.endswith(".json")
                               and not f.name.endswith("-order-events.json"))
    run_command([
        "lean", "report", "--backtest-results",
        str(csharp_results_file), "--report-destination", "csharp.html"
    ],
                cwd=test_dir)

    assert (test_dir / "python.html").is_file()
    assert (test_dir / "csharp.html").is_file()

    # Copy over algorithms containing a SPY buy-and-hold strategy without custom libraries
    shutil.copy(fixtures_dir / "cloud" / "main.py",
                python_project_dir / "main.py")
    shutil.copy(fixtures_dir / "cloud" / "Main.cs",
                csharp_project_dir / "Main.cs")

    # Push projects to the cloud
    run_command(["lean", "cloud", "push", "--project", python_project_name],
                cwd=test_dir)
    run_command(["lean", "cloud", "push", "--project", csharp_project_name],
                cwd=test_dir)

    # Remove some files and see if we can successfully pull them from the cloud
    (python_project_dir / "main.py").unlink()
    (csharp_project_dir / "Main.cs").unlink()

    # Pull projects from the cloud
    run_command(["lean", "cloud", "pull", "--project", python_project_name],
                cwd=test_dir)
    run_command(["lean", "cloud", "pull", "--project", csharp_project_name],
                cwd=test_dir)

    # Ensure deleted files have been pulled
    (python_project_dir / "main.py").is_file()
    (csharp_project_dir / "Main.cs").is_file()

    # Run Python backtest in the cloud
    run_command(["lean", "cloud", "backtest", python_project_name],
                cwd=test_dir)

    # Run C# backtest in the cloud
    run_command(["lean", "cloud", "backtest", csharp_project_name],
                cwd=test_dir)

    # Get cloud project status
    run_command(["lean", "cloud", "status", python_project_name], cwd=test_dir)
    run_command(["lean", "cloud", "status", csharp_project_name], cwd=test_dir)

    # Log out
    run_command(["lean", "logout"])
    assert not credentials_path.exists()

    # Delete the test directory that we used
    shutil.rmtree(test_dir, ignore_errors=True)

    # Delete the cloud projects that we used
    api_client = APIClient(Logger(), HTTPClient(Logger()), user_id, api_token)
    cloud_projects = api_client.projects.get_all()
    api_client.projects.delete(
        next(p.projectId for p in cloud_projects
             if p.name == python_project_name))
    api_client.projects.delete(
        next(p.projectId for p in cloud_projects
             if p.name == csharp_project_name))