diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d189e03..85d9f5e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,9 +27,17 @@ jobs: run: | uv sync - - name: Run pre-commit hooks (formatters, linters) + - name: Run ruff linter run: | - uv run pre-commit run --all-files + uv run ruff check . + + - name: Run ruff formatter + run: | + uv run ruff format --check . + + - name: Run mypy type checker + run: | + uv run mypy . # If ever there are tests to run, uncomment the following lines. # diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 43a672d..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,44 +0,0 @@ -repos: -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 - hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-added-large-files - - id: check-yaml -- repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.14.0 - hooks: - - id: mypy - args: ["--strict", "--pretty", "--show-error-codes"] - additional_dependencies: - - anthropic - - click - - google-genai - - google-cloud-aiplatform - - openai -- repo: https://github.com/psf/black - rev: 24.10.0 - hooks: - - id: black -- repo: https://github.com/pycqa/isort - rev: 5.13.2 - hooks: - - id: isort - args: ["--profile", "black"] -- repo: https://github.com/pycqa/flake8 - rev: 7.1.1 - hooks: - - id: flake8 - additional_dependencies: - - flake8-type-checking - args: - # Make flake compatible with black's line lengths. - - --max-line-length=88 - # Make flake compatible with black's whitespace before ':' rule. - - --extend-ignore=E203 - # Make flake8 OK with forward references. For more, see: - # https://pypi.org/project/flake8-type-checking/ - - --extend-select=TC,TC1 - # Allow e.g. `assert foo() == True` in test files. - - --per-file-ignores=test_*.py:E712 diff --git a/README.md b/README.md index 74c8763..7f86b2e 100644 --- a/README.md +++ b/README.md @@ -49,17 +49,28 @@ This project leverages Python and [uv](https://docs.astral.sh/uv/) to manage dep uv sync ``` -5. **Pre-commit Hooks:** - - Install pre-commit hooks: +5. **Git Hooks (Optional):** + - Set up a git pre-commit hook that runs ruff automatically: ```bash - pip install pre-commit - pre-commit install + ./setup-git-hooks.sh ``` + - This will run `ruff check` and `ruff format --check` before each commit + - To skip the hook for a specific commit: `git commit --no-verify` -6. **Running the linter:** - - Run the linter with: +6. **Code Quality:** + - Run ruff for linting and formatting: ```bash - pre-commit run --all-files + # Check for linting issues + uv run ruff check . + + # Auto-fix linting issues + uv run ruff check --fix . + + # Format code + uv run ruff format . + + # Run type checking + uv run mypy . ``` 7. **Running Tests:** diff --git a/anthropic_common/streaming.py b/anthropic_common/streaming.py index 8d1ae22..6fcfc4e 100644 --- a/anthropic_common/streaming.py +++ b/anthropic_common/streaming.py @@ -1,12 +1,13 @@ """Common streaming utilities for Anthropic API.""" -from typing import IO, TYPE_CHECKING, Any, Dict, Optional, Tuple, Union +from typing import IO, TYPE_CHECKING, Any, Union # Runtime imports with type checking separation import anthropic # noqa: E402 import click from anthropic.types import MessageParam # noqa: E402, F401 + # Define type aliases for better readability if TYPE_CHECKING: from anthropic import Anthropic, AnthropicVertex # noqa: F401 @@ -22,10 +23,10 @@ def stream_anthropic_response( temperature: float, max_tokens: int, thinking_budget_tokens: int, - conv_log_writer: Optional[IO[str]] = None, - resp_log_writer: Optional[IO[str]] = None, + conv_log_writer: IO[str] | None = None, + resp_log_writer: IO[str] | None = None, echo_to_terminal: bool = True, -) -> Tuple[str, Dict[str, Any]]: +) -> tuple[str, dict[str, Any]]: """ Streams the Anthropic API response using the provided client. @@ -48,7 +49,7 @@ def stream_anthropic_response( messages: list[MessageParam] = [{"role": "user", "content": prompt}] # Build the parameters for the API call - api_params: Dict[str, Any] = { + api_params: dict[str, Any] = { "model": model, "system": system_prompt, "messages": messages, @@ -67,7 +68,7 @@ def stream_anthropic_response( api_params["temperature"] = 1.0 # Initialize token usage tracking and text collection - token_usage: Dict[str, Any] = {} + token_usage: dict[str, Any] = {} text_chunks: list[str] = [] try: @@ -136,14 +137,13 @@ def stream_anthropic_response( return response_text, token_usage -def print_token_usage(token_usage: Dict[str, Any]) -> None: +def print_token_usage(token_usage: dict[str, Any]) -> None: """Print token usage information in a consistent format.""" click.echo("\n\n--- Token Usage ---") click.echo(f"Input tokens: {token_usage.get('input_tokens', 0)}") click.echo(f"Output tokens: {token_usage.get('output_tokens', 0)}") click.echo( - f"Cache creation tokens: " - f"{token_usage.get('cache_creation_input_tokens', 0)}" + f"Cache creation tokens: {token_usage.get('cache_creation_input_tokens', 0)}" ) click.echo(f"Cache read tokens: {token_usage.get('cache_read_input_tokens', 0)}") total_input = ( diff --git a/claude_cli/__main__.py b/claude_cli/__main__.py index 39765eb..584ecd0 100644 --- a/claude_cli/__main__.py +++ b/claude_cli/__main__.py @@ -63,7 +63,7 @@ def main( """ # Read the main prompt try: - with open(prompt_file, "r", encoding="utf-8") as f: + with open(prompt_file, encoding="utf-8") as f: prompt = f.read() except OSError as exc: click.echo(f"Error reading prompt file: {exc}", err=True) @@ -71,7 +71,7 @@ def main( # Read the system prompt try: - with open(system_prompt_file, "r", encoding="utf-8") as f: + with open(system_prompt_file, encoding="utf-8") as f: system_prompt = f.read() except OSError as exc: click.echo(f"Error reading system prompt file: {exc}", err=True) @@ -90,7 +90,6 @@ def main( open(conversation_path, "a", encoding="utf-8") as conv_f, open(response_path, "w", encoding="utf-8") as resp_f, ): - # Log the prompt part to the conversation file conv_f.write(f"--- Prompt: {timestamp} ---\n") conv_f.write(f"{prompt}\n") diff --git a/gemini_cli/__main__.py b/gemini_cli/__main__.py index b412ef4..3415474 100644 --- a/gemini_cli/__main__.py +++ b/gemini_cli/__main__.py @@ -59,7 +59,7 @@ def main( """ # Read the prompt from the specified file try: - with open(prompt_file, "r", encoding="utf-8") as f: + with open(prompt_file, encoding="utf-8") as f: prompt = f.read() except OSError as exc: click.echo(f"Error reading prompt file: {exc}", err=True) @@ -67,7 +67,7 @@ def main( # Read the system prompt from the specified file try: - with open(system_prompt_file, "r", encoding="utf-8") as f: + with open(system_prompt_file, encoding="utf-8") as f: system_prompt = f.read() except OSError as exc: click.echo(f"Error reading system prompt file: {exc}", err=True) diff --git a/openai_cli/__main__.py b/openai_cli/__main__.py index d7b18dc..ab34751 100644 --- a/openai_cli/__main__.py +++ b/openai_cli/__main__.py @@ -55,7 +55,7 @@ def main( """ # Read the main prompt try: - with open(prompt_file, "r", encoding="utf-8") as f: + with open(prompt_file, encoding="utf-8") as f: prompt = f.read() except OSError as exc: click.echo(f"Error reading prompt file: {exc}", err=True) @@ -63,7 +63,7 @@ def main( # Read the system prompt try: - with open(system_prompt_file, "r", encoding="utf-8") as f: + with open(system_prompt_file, encoding="utf-8") as f: system_prompt = f.read() except OSError as exc: click.echo(f"Error reading system prompt file: {exc}", err=True) diff --git a/pyproject.toml b/pyproject.toml index 75f45d9..b27d071 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,8 +25,38 @@ packages = ["anthropic_common", "claude_cli", "gemini_cli", "gemini_common", "op [dependency-groups] dev = [ - "flake8>=7.1.1", - "pre-commit>=4.1.0", - "flake8-type-checking>=3.0.0", + "ruff>=0.8.0", "mypy>=1.14.1" ] + +[tool.ruff] +target-version = "py311" +line-length = 88 + +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "UP", # pyupgrade + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "TCH", # flake8-type-checking +] +ignore = [ + "E203", # whitespace before ':' + "TC002", # Move third-party import into type-checking block (requires careful handling) +] + +[tool.ruff.lint.per-file-ignores] +"test_*.py" = ["E712"] # Allow e.g. `assert foo() == True` in test files + +[tool.ruff.lint.isort] +# Configure isort to be compatible with black formatting +force-single-line = false +lines-after-imports = 2 + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" diff --git a/setup-git-hooks.sh b/setup-git-hooks.sh new file mode 100755 index 0000000..cca5feb --- /dev/null +++ b/setup-git-hooks.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# Setup script for git hooks that use ruff for code quality + +set -e + +HOOK_DIR=".git/hooks" +HOOK_FILE="$HOOK_DIR/pre-commit" + +echo "Setting up git pre-commit hook with ruff..." + +# Create the pre-commit hook +cat > "$HOOK_FILE" << 'HOOK_EOF' +#!/usr/bin/env bash +# Git pre-commit hook that runs ruff for code quality checks +set -e + +echo "Running ruff checks before commit..." + +# Check if uv is available +if ! command -v uv &> /dev/null; then + echo "Error: uv is not installed or not in PATH" + echo "Please install uv: curl -LsSf https://astral.sh/uv/install.sh < /dev/null | sh" + exit 1 +fi + +# Run ruff linting on main package files only +echo "→ Running ruff check..." +if ! uv run ruff check anthropic_common/ claude_cli/ gemini_cli/ openai_cli/ vertex_cli/ 2>/dev/null; then + echo "❌ Ruff linting failed. Please fix the issues above." + echo "💡 You can auto-fix many issues with: uv run ruff check --fix ." + exit 1 +fi + +# Run ruff formatting check on main package files only +echo "→ Running ruff format check..." +if ! uv run ruff format --check anthropic_common/ claude_cli/ gemini_cli/ openai_cli/ vertex_cli/ 2>/dev/null; then + echo "❌ Ruff formatting check failed. Please format your code." + echo "💡 You can auto-format with: uv run ruff format ." + exit 1 +fi + +# Run mypy type checking on main package files only +echo "→ Running mypy type check..." +if ! uv run mypy anthropic_common/ claude_cli/ gemini_cli/ openai_cli/ vertex_cli/ 2>/dev/null; then + echo "❌ Mypy type checking failed. Please fix the type issues above." + exit 1 +fi + +echo "✅ All checks passed!" +HOOK_EOF + +# Make the hook executable +chmod +x "$HOOK_FILE" + +echo "✅ Git pre-commit hook installed successfully!" +echo "" +echo "The hook will now run 'uv run ruff check', 'uv run ruff format --check', and 'uv run mypy'" +echo "before each commit to ensure code quality." +echo "" +echo "To skip the hook for a specific commit, use: git commit --no-verify"