⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ jobs:
pip install setuptools==69.5.1 wheel
pip install -r requirements.txt

# Static analysis tools
- name: Static Code Analysis
if: runner.os == 'Linux'
run: |
pip install mypy==1.14.1 flake8==7.0.0
python static_analysis.py

- name: Run tests
run: python -m pytest -s -rs

Expand Down
1 change: 0 additions & 1 deletion lean/commands/lean.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Optional

from click import group, option, Context, pass_context, echo

Expand Down
2 changes: 1 addition & 1 deletion lean/commands/live/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# limitations under the License.

from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
from typing import List, Optional, Tuple
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
Expand Down
3 changes: 1 addition & 2 deletions lean/components/api/live_client.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.

from datetime import datetime
from typing import List, Optional

from lean.components.api.api_client import *
from lean.models.api import QCFullLiveAlgorithm, QCLiveAlgorithmStatus, QCMinimalLiveAlgorithm, QCNotificationMethod, QCRestResponse
from lean.models.api import QCFullLiveAlgorithm, QCMinimalLiveAlgorithm, QCNotificationMethod, QCRestResponse


class LiveClient:
Expand Down
8 changes: 4 additions & 4 deletions lean/components/util/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,14 @@ def _compile() -> Dict[str, Any]:
"mounts": [],
"volumes": {}
}
lean_runner.mount_project_and_library_directories(project_dir, run_options)
lean_runner.setup_language_specific_run_options(run_options, project_dir, algorithm_file, False, False)

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

lean_runner.mount_project_and_library_directories(project_dir, run_options)
lean_runner.setup_language_specific_run_options(run_options, project_dir, algorithm_file, False, False, engine_image)

message["result"] = docker_manager.run_image(engine_image, **run_options)
temp_manager.delete_temporary_directories_when_done = False
return message
Expand Down Expand Up @@ -153,8 +154,7 @@ def _parse_python_errors(python_output: str, color_coding_required: bool) -> lis
errors.append(f"{bcolors.FAIL}Build Error File: {match[0]} Line {match[1]} Column {match[2]} - {match[3]}{bcolors.ENDC}\n")
else:
errors.append(f"Build Error File: {match[0]} Line {match[1]} Column {match[2]} - {match[3]}\n")

for match in re.findall(r"\*\*\* Sorry: ([^(]+) \(([^,]+), line (\d+)\)", python_output):
for match in findall(r"\*\*\* Sorry: ([^(]+) \(([^,]+), line (\d+)\)", python_output):
if color_coding_required:
errors.append(f"{bcolors.FAIL}Build Error File: {match[1]} Line {match[2]} Column 0 - {match[0]}{bcolors.ENDC}\n")
else:
Expand Down
1 change: 0 additions & 1 deletion lean/components/util/project_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,6 @@ def restore_csharp_project(self, csproj_file: Path, no_local: bool) -> None:
"""
from shutil import which
from subprocess import run, STDOUT, PIPE
from lean.models.errors import MoreInfoError

if no_local:
return
Expand Down
2 changes: 1 addition & 1 deletion lean/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def main() -> None:
if temp_manager.delete_temporary_directories_when_done:
temp_manager.delete_temporary_directories()
except Exception as exception:
from traceback import format_exc, print_exc
from traceback import format_exc
from click import UsageError, Abort
from requests import exceptions
from io import StringIO
Expand Down
152 changes: 152 additions & 0 deletions static_analysis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import subprocess
import sys

def display_warning_summary(warnings):
print("\nWarnings:")
unused_count = sum(1 for e in warnings if e.startswith('F401:'))
if unused_count > 0:
print(f" - Unused imports: {unused_count}")

print(" Consider addressing warnings in future updates.")

def run_analysis():
print("Running static analysis...")
print("=" * 60)

all_critical_errors = []
all_warnings = []

# Check for missing arguments with mypy - CRITICAL
print("\n1. Checking for missing function arguments...")
print("-" * 40)

result = subprocess.run(
["python", "-m", "mypy", "lean/",
"--show-error-codes",
"--no-error-summary",
"--ignore-missing-imports",
"--check-untyped-defs"],
capture_output=True,
text=True
)

# Filter for critical call argument mismatches
call_arg_errors = []

for line in (result.stdout + result.stderr).split('\n'):
if not line.strip():
continue

# Look for call-arg errors (this covers both "too many" and "missing" arguments)
if '[call-arg]' in line:
# Skip false positives
if any(pattern in line for pattern in
['click.', 'subprocess.', 'Module "', 'has incompatible type "Optional',
'validator', 'pydantic', '__call__', 'OnlyValueValidator', 'V1Validator',
'QCParameter', 'QCBacktest']):
continue
call_arg_errors.append(line.strip())

# Display call argument mismatches
if call_arg_errors:
print("CRITICAL: Missing function arguments found:")
for error in call_arg_errors:
# Clean path for better display
clean_error = error.replace('/home/runner/work/lean-cli/lean-cli/', '')
print(f" {clean_error}")

all_critical_errors.extend(call_arg_errors)
else:
print("No argument mismatch errors found")

# Check for undefined variables with flake8 - CRITICAL
print("\n2. Checking for undefined variables...")
print("-" * 40)

result = subprocess.run(
["python", "-m", "flake8", "lean/",
"--select=F821",
"--ignore=ALL",
"--count"],
capture_output=True,
text=True
)

if result.stdout.strip() and result.stdout.strip() != "0":
detail = subprocess.run(
["python", "-m", "flake8", "lean/", "--select=F821", "--ignore=ALL"],
capture_output=True,
text=True
)

undefined_errors = [e.strip() for e in detail.stdout.split('\n') if e.strip()]
print(f"CRITICAL: {len(undefined_errors)} undefined variable(s) found:")

for error in undefined_errors:
print(f" {error}")

all_critical_errors.extend([f"F821: {e}" for e in undefined_errors])
else:
print("No undefined variables found")

# Check for unused imports with flake8 - WARNING
print("\n3. Checking for unused imports...")
print("-" * 40)

result = subprocess.run(
["python", "-m", "flake8", "lean/",
"--select=F401",
"--ignore=ALL",
"--count",
"--exit-zero"],
capture_output=True,
text=True
)

if result.stdout.strip() and result.stdout.strip() != "0":
detail = subprocess.run(
["python", "-m", "flake8", "lean/", "--select=F401", "--ignore=ALL", "--exit-zero"],
capture_output=True,
text=True
)

unused_imports = [e.strip() for e in detail.stdout.split('\n') if e.strip()]
if unused_imports:
print(f"WARNING: {len(unused_imports)} unused import(s) found:")

for error in unused_imports:
print(f" {error}")

all_warnings.extend([f"F401: {e}" for e in unused_imports])
else:
print("No unused imports found")
else:
print("No unused imports found")

print("\n" + "=" * 60)

# Summary
if all_critical_errors:
total_errors = len(all_critical_errors)
print(f"BUILD FAILED: Found {total_errors} critical error(s)")

print("\nSummary of critical errors:")
print(f" - Function call argument mismatches: {len(call_arg_errors)}")
undefined_count = sum(1 for e in all_critical_errors if e.startswith('F821:'))
print(f" - Undefined variables: {undefined_count}")

if all_warnings:
display_warning_summary(all_warnings)

return 1

if all_warnings:
print(f"BUILD PASSED with {len(all_warnings)} warning(s)")
display_warning_summary(all_warnings)
return 0

print("SUCCESS: All checks passed with no warnings")
return 0

if __name__ == "__main__":
sys.exit(run_analysis())
Loading