diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ad8dfd0..1b7150a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -4,7 +4,6 @@ on: push: branches: - main - - dev - feat/archit/dev pull_request: diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..9085d63 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,88 @@ +[MASTER] +# Specify a configuration file. +load-plugins= + +[MESSAGES CONTROL] +# C0111: Missing module/function/class docstring +# C0103: Invalid constant name +# R0913: Too many arguments +# R0914: Too many local variables +# R0904: Too many public methods +# W0212: Access to protected member +# W0611: Unused import +disable=missing-module-docstring, + missing-function-docstring, + missing-class-docstring, + invalid-name, + too-many-arguments, + too-many-locals, + too-many-public-methods, + protected-access, + unused-import, + too-few-public-methods, + duplicate-code, + line-too-long, + +[FORMAT] +# Maximum number of characters on a single line. +max-line-length=120 + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +[DESIGN] +# Maximum number of arguments for function / method +max-args=7 + +# Maximum number of attributes for a class +max-attributes=10 + +# Maximum number of locals in a function +max-locals=20 + +[MISCELLANEOUS] +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,TODO,XXX + +[VARIABLE NAMES] +# Argument naming style +argument-naming-style=snake_case + +# Attribute naming style +attr-naming-style=snake_case + +# Class naming style +class-naming-style=PascalCase + +# Constant naming style +const-naming-style=UPPER_CASE + +# Function naming style +function-naming-style=snake_case + +# Method naming style +method-naming-style=snake_case + +# Module naming style +module-naming-style=snake_case + +# Variable naming style +variable-naming-style=snake_case + +[LOGGING] +# Format of logging format string. +logging-format-style=new + +[BASIC] +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_,x,y,z,df,ax + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=yes + +[SIMILARITIES] +# Ignore imports when computing similarities. +ignore-imports=yes + +# Minimum lines number d a similarity to trigger off a duplication warning +min-similarity-lines=10 diff --git a/examples/check_pylint.py b/examples/check_pylint.py new file mode 100644 index 0000000..e22f47a --- /dev/null +++ b/examples/check_pylint.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +""" +Pylint checker script for RoboQA-Temporal + +This script runs pylint on the specified directory to ensure code quality. + +Usage: + python scripts/check_pylint.py # Check all source code + python scripts/check_pylint.py src # Check specific directory + python scripts/check_pylint.py --help # Show help +""" + +import argparse +import subprocess +import sys +from pathlib import Path + + +def run_pylint(path: str, min_score: float = 7.0, strict: bool = False) -> int: + """ + Run pylint on specified path. + + Args: + path: Path to check + min_score: Minimum score to pass + strict: If True, fail on any issues (uses higher threshold) + + Returns: + Exit code + """ + cmd = [sys.executable, "-m", "pylint", path] + + threshold = 9.0 if strict else min_score + cmd.extend(["--fail-under", str(threshold)]) + + result = subprocess.run(cmd, text=True) + return result.returncode + + +def main(): + """Main entry point.""" + parser = argparse.ArgumentParser(description="Run pylint checks on RoboQA-Temporal") + parser.add_argument( + "path", + nargs="?", + default="src/roboqa_temporal", + help="Path to check (default: src/roboqa_temporal)", + ) + parser.add_argument( + "--min-score", + type=float, + default=7.0, + help="Minimum pylint score to pass (default: 7.0)", + ) + parser.add_argument( + "--strict", + action="store_true", + help="Use strict mode (fail-under 9.0)", + ) + + args = parser.parse_args() + + check_path = Path(args.path) + if not check_path.is_absolute(): + check_path = Path.cwd() / check_path + + if not check_path.exists(): + print(f"Error: Path does not exist: {check_path}", file=sys.stderr) + return 1 + + print(f"Running pylint on {check_path}...") + exit_code = run_pylint(str(check_path), args.min_score, args.strict) + + if exit_code == 0: + print("\nPylint check passed!") + else: + print("\nPylint check failed!") + + return exit_code + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/pyproject.toml b/pyproject.toml index a83eb9f..79e16df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,7 @@ dev = [ "black>=22.0.0", "flake8>=4.0.0", "mypy>=0.950", + "pylint>=3.0.0", ] [project.scripts] diff --git a/requirements.txt b/requirements.txt index 2b7dbe2..abf5970 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,4 +9,5 @@ pyyaml>=6.0 tqdm>=4.62.0 tabulate>=0.9.0 lark-parser>=0.12.0 -plotly>=5.0.0 \ No newline at end of file +plotly>=5.0.0 +pylint>=3.0.0 \ No newline at end of file diff --git a/tests/test_pylint.py b/tests/test_pylint.py new file mode 100644 index 0000000..2bed3b0 --- /dev/null +++ b/tests/test_pylint.py @@ -0,0 +1,84 @@ +""" +Pylint integration tests for RoboQA-Temporal + +This module provides pytest integration with pylint to check code quality. +""" + +import subprocess +import sys +from pathlib import Path + +import pytest + + +def get_python_files(directory: Path) -> list[Path]: + """Get all Python files in the source directory.""" + return list(directory.rglob("*.py")) + + +class TestPylint: + """Pylint tests for code quality checking.""" + + @pytest.fixture(scope="session", autouse=True) + def source_dir(self): + """Get the source directory path.""" + return Path(__file__).resolve().parents[1] / "src" / "roboqa_temporal" + + def test_pylint_src(self, source_dir): + """Run pylint on source code.""" + if not source_dir.exists(): + pytest.skip(f"Source directory not found: {source_dir}") + + result = subprocess.run( + [sys.executable, "-m", "pylint", str(source_dir), "--fail-under=7.0"], + capture_output=True, + text=True, + ) + + if result.returncode != 0: + pytest.fail(f"Pylint check failed:\n{result.stdout}\n{result.stderr}") + + def test_pylint_tests(self): + """Run pylint on test code with relaxed standards.""" + test_dir = Path(__file__).resolve().parent + + result = subprocess.run( + [ + sys.executable, + "-m", + "pylint", + str(test_dir), + "--disable=missing-docstring", + "--fail-under=6.0", + ], + capture_output=True, + text=True, + ) + + if result.returncode != 0: + print(f"\nPylint found issues in tests:\n{result.stdout}") + + +def test_pylint_single_module(): + """Test a specific module for pylint compliance.""" + source_dir = Path(__file__).resolve().parents[1] / "src" / "roboqa_temporal" + + if not source_dir.exists(): + pytest.skip(f"Source directory not found: {source_dir}") + + python_files = get_python_files(source_dir) + if not python_files: + pytest.skip("No Python files found") + + detection_dir = source_dir / "detection" + if detection_dir.exists(): + result = subprocess.run( + [sys.executable, "-m", "pylint", str(detection_dir)], + capture_output=True, + text=True, + ) + + lines = result.stdout.split("\n") + summary = [l for l in lines if "rated at" in l] + if summary: + print(f"\nDetection module pylint rating: {summary[0]}")