Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Introduce isort and Python-based code quality tools #446

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
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
17 changes: 15 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ dev = [
"pylint",
"mypy",
"pyright",
"isort>=5.13.2",
# security:
"bandit",
# packaging:
Expand Down Expand Up @@ -83,8 +84,8 @@ app = "python -m kleinanzeigen_bot"
compile.cmd = "python -O -m PyInstaller pyinstaller.spec --clean"
compile.env = {PYTHONHASHSEED = "1", SOURCE_DATE_EPOCH = "0"} # https://pyinstaller.org/en/stable/advanced-topics.html#creating-a-reproducible-build
debug = "python -m pdb -m kleinanzeigen_bot"
format = "autopep8 --recursive --in-place src tests --verbose"
lint = {shell = "pylint -v src tests && autopep8 -v --exit-code --recursive --diff src tests && mypy" }
format = "python -m scripts.format_code"
lint = "python -m scripts.lint_code"
audit = "bandit -c pyproject.toml -r src"
test = "python -m pytest --capture=tee-sys -v"
utest = "python -m pytest --capture=tee-sys -v -m 'not itest'"
Expand Down Expand Up @@ -266,3 +267,15 @@ filterwarnings = [
"ignore:Exception ignored in:pytest.PytestUnraisableExceptionWarning",
"ignore::DeprecationWarning"
]

# Add isort configuration to format imports
[tool.isort]
profile = "black"
line_length = 160
combine_as_imports = true
combine_star = true
multi_line_output = 3
force_grid_wrap = 0
ensure_newline_before_comments = true
no_lines_before = ["LOCALFOLDER"]
combine_straight_imports = true
1 change: 1 addition & 0 deletions scripts/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Helper scripts for development tasks."""
33 changes: 33 additions & 0 deletions scripts/format_code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env python3
"""Helper script to format Python code using isort and autopep8."""

import subprocess, sys

from scripts.git_utils import get_modified_python_files


def format_files() -> int:
"""Format Python files using isort and autopep8.

Returns:
int: 0 on success, non-zero on failure
"""
try:
# Format imports in modified files
py_files = get_modified_python_files()
if py_files:
subprocess.run(['isort', *py_files], check=True)

# Format all files with autopep8
subprocess.run(
['autopep8', '--recursive', '--in-place', 'src', 'tests', '--verbose'],
check=True
)
return 0
except subprocess.CalledProcessError as e:
print(f"Error during formatting: {e}", file=sys.stderr)
return 1


if __name__ == '__main__':
sys.exit(format_files())
18 changes: 18 additions & 0 deletions scripts/git_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env python3
"""Utility functions for git operations."""

import subprocess
from collections.abc import Sequence


def get_modified_python_files() -> Sequence[str]:
"""Get list of modified Python files from git.

Returns:
Sequence[str]: List of modified Python file paths
"""
git_cmd = ['git', 'diff', '--name-only', '--diff-filter=ACMR', 'HEAD']
result = subprocess.run(git_cmd, capture_output=True, text=True, check=True)
if not result.stdout.strip():
return []
return [f for f in result.stdout.splitlines() if f.endswith('.py')]
65 changes: 65 additions & 0 deletions scripts/lint_code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/usr/bin/env python3
"""Helper script to lint Python code using multiple tools."""

import subprocess, sys
from collections.abc import Sequence

from scripts.git_utils import get_modified_python_files


def run_tool(cmd: Sequence[str], tool_name: str) -> tuple[int, str]:
"""Run a lint tool and return its exit code and error message.

Args:
cmd: Command to run as list of strings
tool_name: Name of the tool for error messages

Returns:
tuple[int, str]: tuple of (exit_code, error_message)
"""
try:
subprocess.run(list(cmd), check=True)
return 0, ""
except subprocess.CalledProcessError as e:
return e.returncode, f"{tool_name} fehlgeschlagen\n"


def lint_files() -> int:
"""Run all linting tools.

Returns:
int: Number of errors encountered
"""
errors = 0
error_messages = []

# Run isort on modified files
py_files = get_modified_python_files()
if py_files:
code, msg = run_tool(['isort', *py_files], 'isort')
errors += code
if msg:
error_messages.append(msg)

# Run other linting tools
tools = [
(['pylint', '-v', 'src', 'tests'], 'pylint'),
(['autopep8', '-v', '--in-place', '--recursive', 'src', 'tests'], 'autopep8'),
(['mypy', 'src', 'tests'], 'mypy')
]

for cmd, name in tools:
code, msg = run_tool(cmd, name)
errors += code
if msg:
error_messages.append(msg)

# Print all error messages at the end
if error_messages:
print("\n".join(error_messages), file=sys.stderr)

return errors


if __name__ == '__main__':
sys.exit(lint_files())