From f7d40eeb0c88ba7daaabb2ede8e1df8f4bbe8a8e Mon Sep 17 00:00:00 2001 From: simone99n Date: Wed, 4 Feb 2026 15:26:33 +0100 Subject: [PATCH 1/4] [1766][1742] fix lint and unit-test --- scripts/actions.sh | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/scripts/actions.sh b/scripts/actions.sh index f0f4852ec..1703eb431 100755 --- a/scripts/actions.sh +++ b/scripts/actions.sh @@ -18,14 +18,18 @@ case "$1" in ;; lint) ( - cd "$SCRIPT_DIR" || exit 1 - uv run --no-project --with "ruff==0.12.2" ruff format --target-version py312 \ - src/ scripts/ packages/ \ - && \ - uv run --no-project --with "ruff==0.12.2" \ - ruff check --target-version py312 \ - --fix \ - src/ scripts/ packages/ + echo "LINTING WORKFLOW STARTING..." + + echo "lint-check..." + ./scripts/actions.sh lint-check + echo "lint-check" + ./scripts/actions.sh lint-fix + echo "toml-checking..." + ./scripts/actions.sh toml-check + echo "type-checking..." + ./scripts/actions.sh type-check + + echo "LINTING WORKFLOW COMPLETED." ) ;; lint-check) @@ -43,6 +47,18 @@ case "$1" in pylint src/ packages/ ) ;; + lint-fix) + ( + cd "$SCRIPT_DIR" || exit 1 + uv run --no-project --with "ruff==0.12.2" ruff format --target-version py312 \ + src/ scripts/ packages/ \ + && \ + uv run --no-project --with "ruff==0.12.2" \ + ruff check --target-version py312 \ + --fix \ + src/ scripts/ packages/ + ) + ;; type-check) ( # The dependencies are rebuilt for each package to ensure that they do not rely on implicit imports. @@ -50,7 +66,6 @@ case "$1" in # weathergen-common uv sync --project packages/common --no-install-workspace - uv pip list uv run --project packages/common --frozen pyrefly check packages/common # Fail for errors on weathergen-common: if [ $? -ne 0 ]; then @@ -60,7 +75,6 @@ case "$1" in # weathergen-metrics uv sync --project packages/metrics --no-install-workspace - uv pip list uv run --project packages/metrics --frozen pyrefly check packages/metrics # Fail for errors on weathergen-metrics: if [ $? -ne 0 ]; then @@ -70,13 +84,11 @@ case "$1" in # weathergen-evaluate uv sync --project packages/evaluate --no-install-workspace --package weathergen-evaluate - uv pip list uv run --project packages/evaluate --frozen pyrefly check packages/evaluate # weathergen (root) # Install the whole workspace. It also needs the extra cpu option for the right version of pytorch. uv sync --all-packages --extra cpu --no-install-workspace - uv pip list uv run --all-packages pyrefly check src echo "Type checking completed." ) @@ -84,7 +96,6 @@ case "$1" in unit-test) ( cd "$SCRIPT_DIR" || exit 1 - uv sync --extra cpu uv run --extra cpu pytest src/ ) ;; From 5c9a7c7241e840299a040a691ec7d7983adaf601 Mon Sep 17 00:00:00 2001 From: simone99n Date: Wed, 4 Feb 2026 16:34:11 +0100 Subject: [PATCH 2/4] [1766] fix linter --- scripts/actions.sh | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/scripts/actions.sh b/scripts/actions.sh index 1703eb431..9b2573195 100755 --- a/scripts/actions.sh +++ b/scripts/actions.sh @@ -19,10 +19,8 @@ case "$1" in lint) ( echo "LINTING WORKFLOW STARTING..." - - echo "lint-check..." - ./scripts/actions.sh lint-check - echo "lint-check" + + echo "lint-fix" ./scripts/actions.sh lint-fix echo "toml-checking..." ./scripts/actions.sh toml-check @@ -35,16 +33,16 @@ case "$1" in lint-check) ( cd "$SCRIPT_DIR" || exit 1 - uv run --no-project --with "ruff==0.12.2" ruff format --target-version py312 \ - -n \ - src/ scripts/ packages/ \ + uv run --no-project --with "ruff==0.12.2" \ + ruff format --target-version py312 -n src/ scripts/ packages/ \ && \ uv run --no-project --with "ruff==0.12.2" \ - ruff check --target-version py312 \ - src/ scripts/ packages/ \ + ruff check --target-version py312 src/ scripts/ packages/ \ && \ uv run --no-project --with "pylint==4.0.3" \ - pylint src/ packages/ + pylint src/ packages/ \ + && \ + uv run --no-project python scripts/check_badfunctions.py ) ;; lint-fix) @@ -56,7 +54,9 @@ case "$1" in uv run --no-project --with "ruff==0.12.2" \ ruff check --target-version py312 \ --fix \ - src/ scripts/ packages/ + src/ scripts/ packages/ \ + && \ + uv run --no-project python scripts/check_badfunctions.py ) ;; type-check) From 56f37891d58eabf34ebbd0f381d6ba3253d552b1 Mon Sep 17 00:00:00 2001 From: simone99n Date: Wed, 4 Feb 2026 17:49:49 +0100 Subject: [PATCH 3/4] [1766] lint local and global consistent --- scripts/actions.sh | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/scripts/actions.sh b/scripts/actions.sh index 9b2573195..a7b3fe32a 100755 --- a/scripts/actions.sh +++ b/scripts/actions.sh @@ -20,7 +20,9 @@ case "$1" in ( echo "LINTING WORKFLOW STARTING..." - echo "lint-fix" + echo "lint-check..." + ./scripts/actions.sh lint-check + echo "lint-fix..." ./scripts/actions.sh lint-fix echo "toml-checking..." ./scripts/actions.sh toml-check @@ -48,15 +50,8 @@ case "$1" in lint-fix) ( cd "$SCRIPT_DIR" || exit 1 - uv run --no-project --with "ruff==0.12.2" ruff format --target-version py312 \ - src/ scripts/ packages/ \ - && \ - uv run --no-project --with "ruff==0.12.2" \ - ruff check --target-version py312 \ - --fix \ - src/ scripts/ packages/ \ - && \ - uv run --no-project python scripts/check_badfunctions.py + uv run --no-project --with "ruff==0.12.2" ruff check --target-version py312 \ + --fix src/ scripts/ packages/ ) ;; type-check) @@ -187,9 +182,11 @@ case "$1" in ) ;; *) - # Automatically extract all options from the case statement - options=$(grep -oP '^\s*\K[\w-]+(?=\))' "$0" | tr '\n' '|' | sed 's/|$//') - echo "Usage: $0 {$options}" - exit 1 + ( + # Automatically extract all options from the case statement + options=$(grep -oP '^\s*\K[\w-]+(?=\))' "$0" | tr '\n' '|' | sed 's/|$//') + echo "Usage: $0 {$options}" + exit 1 + ) ;; esac From bee363edbd60a195ec885712626345b0ce25c120 Mon Sep 17 00:00:00 2001 From: simone99n Date: Thu, 5 Feb 2026 09:22:38 +0100 Subject: [PATCH 4/4] [1332] add script to detect bad functions (getattr) --- scripts/check_badfunctions.py | 150 ++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 scripts/check_badfunctions.py diff --git a/scripts/check_badfunctions.py b/scripts/check_badfunctions.py new file mode 100644 index 000000000..41d20947b --- /dev/null +++ b/scripts/check_badfunctions.py @@ -0,0 +1,150 @@ +# ruff: noqa: T201, N802 + +""" +Check for usage of banned functions in Python source files. + +This script uses the Abstract Syntax Tree (AST) module to parse Python files +and detect calls to functions that are explicitly banned in the codebase. +""" + +import ast +import sys +from pathlib import Path + +# List of function names that are not allowed in the codebase +BANNED_FUNCTIONS: set[str] = { + "getattr", + # "setattr", + # "delattr", + # "eval", + # "exec", + # Add more banned functions here as needed +} + + +class BannedFunctionVisitor(ast.NodeVisitor): + """ + AST visitor that detects calls to banned functions. + + This visitor walks through the Abstract Syntax Tree of a Python file + and records any calls to functions that are in the BANNED_FUNCTIONS set. + """ + + def __init__(self, filepath: Path): + """ + Initialize the visitor. + + Args: + filepath: Path to the file being checked (for error reporting) + """ + self.filepath = filepath + self.errors: list[str] = [] + + def visit_Call(self, node: ast.Call) -> None: + """ + Visit a function call node in the AST. + + This method is called automatically by the AST walker whenever + it encounters a function call. We check if the called function + is in our banned list. + + Args: + node: The AST Call node representing a function call + """ + # Check if this is a simple function call (e.g., getattr(...)) + # as opposed to a method call (e.g., obj.method(...)) + if isinstance(node.func, ast.Name): + function_name = node.func.id + + # If the function is banned, record an error + if function_name in BANNED_FUNCTIONS: + error_msg = ( + f"{self.filepath}:{node.lineno}:{node.col_offset}: " + f"Use of banned function '{function_name}()' is not allowed" + ) + self.errors.append(error_msg) + + # Continue visiting child nodes + self.generic_visit(node) + + +def check_file(filepath: Path) -> list[str]: + """ + Check a single Python file for banned function usage. + + Args: + filepath: Path to the Python file to check + + Returns: + List of error messages (empty if no banned functions found) + """ + try: + # Read and parse the Python file into an AST + with open(filepath, encoding="utf-8") as f: + source_code = f.read() + + tree = ast.parse(source_code, filename=str(filepath)) + + except SyntaxError as e: + # If the file has syntax errors, report them but don't fail + # (syntax errors will be caught by other linters) + return [f"{filepath}: Syntax error, skipping banned function check: {e}"] + + except Exception as e: + # Handle other unexpected errors (e.g., encoding issues) + return [f"{filepath}: Error reading file: {e}"] + + # Create a visitor and walk through the AST + visitor = BannedFunctionVisitor(filepath) + visitor.visit(tree) + + return visitor.errors + + +def main() -> int: + """ + Main entry point for the banned functions checker. + + Scans all Python files in the configured directories and reports + any usage of banned functions. + + Returns: + Exit code: 0 if no banned functions found, 1 otherwise + """ + # Directories to check (relative to the script's parent directory) + script_dir = Path(__file__).parent.parent + directories_to_check = ["src/", "scripts/", "packages/"] + + all_errors: list[str] = [] + + # Walk through each directory and check all Python files + for directory in directories_to_check: + dir_path = script_dir / directory + + if not dir_path.exists(): + print(f"Warning: Directory {directory} does not exist, skipping") + continue + + # Find all .py files recursively + for py_file in dir_path.rglob("*.py"): + errors = check_file(py_file) + all_errors.extend(errors) + + # Print all errors found + if all_errors: + print("=" * 70) + print("BANNED FUNCTION USAGE DETECTED") + print("=" * 70) + for error in all_errors: + print(error) + print("=" * 70) + print(f"\nTotal violations: {len(all_errors)}") + print(f"Banned functions: {', '.join(sorted(BANNED_FUNCTIONS))}") + return 1 + + print("✓ No banned functions detected") + return 0 + + +if __name__ == "__main__": + sys.exit(main())