diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000000..da6ab9d16e0 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,43 @@ +--- +name: Linting + +on: + push: + paths-ignore: + - 'aider/website/**' + - README.md + - HISTORY.md + branches: + - main + pull_request: + paths-ignore: + - 'aider/website/**' + - README.md + branches: + - main + +jobs: + mypy: + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install '.[browser,dev,playwright,mypy]' + + - name: Run Mypy + uses: wearerequired/lint-action@v2.3.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + mypy: true + mypy_args: "." + continue_on_error: false diff --git a/aider/coders/__init__.py b/aider/coders/__init__.py index e9d334bc99f..b87c395ccaa 100644 --- a/aider/coders/__init__.py +++ b/aider/coders/__init__.py @@ -12,15 +12,15 @@ # from .single_wholefile_func_coder import SingleWholeFileFunctionCoder __all__ = [ - HelpCoder, - AskCoder, - Coder, - EditBlockCoder, - EditBlockFencedCoder, - WholeFileCoder, - UnifiedDiffCoder, - # SingleWholeFileFunctionCoder, - ArchitectCoder, - EditorEditBlockCoder, - EditorWholeFileCoder, + "HelpCoder", + "AskCoder", + "Coder", + "EditBlockCoder", + "EditBlockFencedCoder", + "WholeFileCoder", + "UnifiedDiffCoder", + # "SingleWholeFileFunctionCoder", + "ArchitectCoder", + "EditorEditBlockCoder", + "EditorWholeFileCoder", ] diff --git a/aider/coders/ask_coder.py b/aider/coders/ask_coder.py index 33da037d4dd..e0777e60846 100644 --- a/aider/coders/ask_coder.py +++ b/aider/coders/ask_coder.py @@ -1,9 +1,10 @@ from .ask_prompts import AskPrompts from .base_coder import Coder +from .base_prompts import CoderPrompts class AskCoder(Coder): """Ask questions about code without making any changes.""" edit_format = "ask" - gpt_prompts = AskPrompts() + gpt_prompts: CoderPrompts = AskPrompts() diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index ab662aebd5a..2baa493368b 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -879,7 +879,7 @@ def check_and_open_urls(self, exc, friendly_msg=None): self.io.offer_url(url) return urls - def check_for_urls(self, inp: str) -> List[str]: + def check_for_urls(self, inp: str) -> str: """Check input for URLs and offer to add them to the chat.""" if not self.detect_urls: return inp diff --git a/aider/coders/base_prompts.py b/aider/coders/base_prompts.py index c431c752018..447613e4156 100644 --- a/aider/coders/base_prompts.py +++ b/aider/coders/base_prompts.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + class CoderPrompts: system_reminder = "" @@ -37,7 +40,7 @@ class CoderPrompts: " stop and wait for your approval." ) - repo_content_prefix = """Here are summaries of some files present in my git repository. + repo_content_prefix: str | None = """Here are summaries of some files present in my git repository. Do not propose changes to these files, treat them as *read-only*. If you need to edit any of these files, ask me to *add them to the chat* first. """ diff --git a/aider/coders/search_replace.py b/aider/coders/search_replace.py index a72a7845b6a..fbe78bd9cf3 100755 --- a/aider/coders/search_replace.py +++ b/aider/coders/search_replace.py @@ -2,11 +2,15 @@ import sys from pathlib import Path +from typing import TYPE_CHECKING -try: +if TYPE_CHECKING: import git -except ImportError: - git = None +else: + try: + import git + except ImportError: + git = None from diff_match_patch import diff_match_patch from tqdm import tqdm diff --git a/aider/exceptions.py b/aider/exceptions.py index 27ab3e13f02..95ba05924e8 100644 --- a/aider/exceptions.py +++ b/aider/exceptions.py @@ -5,7 +5,7 @@ class ExInfo: name: str retry: bool - description: str + description: str | None EXCEPTIONS = [ diff --git a/aider/io.py b/aider/io.py index ea9fd9a2bc4..f2bcd6d0607 100644 --- a/aider/io.py +++ b/aider/io.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import base64 import os import signal @@ -35,7 +37,7 @@ @dataclass class ConfirmGroup: - preference: str = None + preference: str | None = None show_group: bool = True def __init__(self, items=None): diff --git a/aider/llm.py b/aider/llm.py index c01df0ce1f7..fbabf06e560 100644 --- a/aider/llm.py +++ b/aider/llm.py @@ -42,4 +42,4 @@ def _load_litellm(self): litellm = LazyLiteLLM() -__all__ = [litellm] +__all__ = ["litellm"] diff --git a/aider/main.py b/aider/main.py index ee55261a292..329e002219d 100644 --- a/aider/main.py +++ b/aider/main.py @@ -8,11 +8,15 @@ import webbrowser from dataclasses import fields from pathlib import Path +from typing import TYPE_CHECKING -try: +if TYPE_CHECKING: import git -except ImportError: - git = None +else: + try: + import git + except ImportError: + git = None import importlib_resources from dotenv import load_dotenv diff --git a/aider/models.py b/aider/models.py index cef4503fa4a..89d2ad2cec3 100644 --- a/aider/models.py +++ b/aider/models.py @@ -19,7 +19,7 @@ DEFAULT_MODEL_NAME = "gpt-4o" ANTHROPIC_BETA_HEADER = "prompt-caching-2024-07-31,pdfs-2024-09-25" -OPENAI_MODELS = """ +_OPENAI_MODELS = """ gpt-4 gpt-4o gpt-4o-2024-05-13 @@ -46,9 +46,9 @@ gpt-3.5-turbo-16k-0613 """ -OPENAI_MODELS = [ln.strip() for ln in OPENAI_MODELS.splitlines() if ln.strip()] +OPENAI_MODELS = [ln.strip() for ln in _OPENAI_MODELS.splitlines() if ln.strip()] -ANTHROPIC_MODELS = """ +_ANTHROPIC_MODELS = """ claude-2 claude-2.1 claude-3-haiku-20240307 @@ -59,7 +59,7 @@ claude-3-5-sonnet-20241022 """ -ANTHROPIC_MODELS = [ln.strip() for ln in ANTHROPIC_MODELS.splitlines() if ln.strip()] +ANTHROPIC_MODELS = [ln.strip() for ln in _ANTHROPIC_MODELS.splitlines() if ln.strip()] # Mapping of model aliases to their canonical names MODEL_ALIASES = { diff --git a/aider/repo.py b/aider/repo.py index 35905df6edc..f6205fbbeaa 100644 --- a/aider/repo.py +++ b/aider/repo.py @@ -1,17 +1,22 @@ import os import time from pathlib import Path, PurePosixPath +from typing import TYPE_CHECKING -try: +if TYPE_CHECKING: import git - - ANY_GIT_ERROR = [ - git.exc.ODBError, - git.exc.GitError, - ] -except ImportError: - git = None - ANY_GIT_ERROR = [] + _ANY_GIT_ERROR = [] +else: + try: + import git + + _ANY_GIT_ERROR = [ + git.exc.ODBError, + git.exc.GitError, + ] + except ImportError: + git = None + _ANY_GIT_ERROR = [] import pathspec @@ -20,15 +25,14 @@ from .dump import dump # noqa: F401 -ANY_GIT_ERROR += [ +_ANY_GIT_ERROR += [ OSError, IndexError, BufferError, TypeError, ValueError, ] -ANY_GIT_ERROR = tuple(ANY_GIT_ERROR) - +ANY_GIT_ERROR = tuple(_ANY_GIT_ERROR) class GitRepo: repo = None diff --git a/aider/repomap.py b/aider/repomap.py index 0ec4f710a00..ad785a0c6ca 100644 --- a/aider/repomap.py +++ b/aider/repomap.py @@ -25,7 +25,7 @@ warnings.simplefilter("ignore", category=FutureWarning) from tree_sitter_languages import get_language, get_parser # noqa: E402 -Tag = namedtuple("Tag", "rel_fname fname line name kind".split()) +Tag = namedtuple("Tag", ("rel_fname", "fname", "line", "name", "kind")) SQLITE_ERRORS = (sqlite3.OperationalError, sqlite3.DatabaseError, OSError) diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 00000000000..8522531c118 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,46 @@ +[mypy] +exclude = (?x)( + ^\.git/ + | ^benchmark/$ + | ^build/$ + | ^dist/$ + | ^scripts/$ + ) +allow_untyped_globals = True +check_untyped_defs = False + +[mypy-configargparse] +ignore_missing_imports = True + +[mypy-diff_match_patch] +ignore_missing_imports = True + +[mypy-diskcache] +ignore_missing_imports = True + +[mypy-grep_ast.*] +ignore_missing_imports = True + +[mypy-imgcat] +ignore_missing_imports = True + +[mypy-llama_index.*] +ignore_missing_imports = True + +[mypy-mixpanel] +ignore_missing_imports = True + +[mypy-pydub] +ignore_missing_imports = True + +[mypy-pypandoc] +ignore_missing_imports = True + +[mypy-pyperclip] +ignore_missing_imports = True + +[mypy-soundfile] +ignore_missing_imports = True + +[mypy-sounddevice] +ignore_missing_imports = True diff --git a/pyproject.toml b/pyproject.toml index f7b25eee8a4..f99c5713e4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ dev = { file = "requirements/requirements-dev.txt" } help = { file = "requirements/requirements-help.txt" } browser = { file = "requirements/requirements-browser.txt" } playwright = { file = "requirements/requirements-playwright.txt" } +mypy = { file = "requirements/requirements-mypy.txt" } [tool.setuptools] include-package-data = true diff --git a/requirements/requirements-mypy.in b/requirements/requirements-mypy.in new file mode 100644 index 00000000000..f1de9120b3e --- /dev/null +++ b/requirements/requirements-mypy.in @@ -0,0 +1,13 @@ +-c ../requirements.txt + +mypy +types-Pygments +types-PyYAML +types-beautifulsoup4 +types-jsonschema +types-networkx +types-pexpect +types-psutil +types-requests +types-tqdm +types-tree-sitter-languages diff --git a/requirements/requirements-mypy.txt b/requirements/requirements-mypy.txt new file mode 100644 index 00000000000..fe6cc1e4376 --- /dev/null +++ b/requirements/requirements-mypy.txt @@ -0,0 +1,73 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --allow-unsafe --constraint=requirements.txt --output-file=requirements/requirements-mypy.txt requirements/requirements-mypy.in +# +attrs==24.2.0 + # via + # -c /home/akaihola/repos/ai/aider-chat/requirements.txt + # -c requirements.txt + # referencing +mypy==1.14.0 + # via -r requirements/requirements-mypy.in +mypy-extensions==1.0.0 + # via mypy +numpy==1.26.4 + # via + # -c /home/akaihola/repos/ai/aider-chat/requirements.txt + # -c requirements.txt + # types-networkx +referencing==0.35.1 + # via + # -c /home/akaihola/repos/ai/aider-chat/requirements.txt + # -c requirements.txt + # types-jsonschema +rpds-py==0.22.3 + # via + # -c /home/akaihola/repos/ai/aider-chat/requirements.txt + # -c requirements.txt + # referencing +tree-sitter==0.21.3 + # via + # -c /home/akaihola/repos/ai/aider-chat/requirements.txt + # -c requirements.txt + # types-tree-sitter-languages +types-beautifulsoup4==4.12.0.20241020 + # via -r requirements/requirements-mypy.in +types-docutils==0.21.0.20241128 + # via types-pygments +types-html5lib==1.1.11.20241018 + # via types-beautifulsoup4 +types-jsonschema==4.23.0.20241208 + # via -r requirements/requirements-mypy.in +types-networkx==3.4.2.20241227 + # via -r requirements/requirements-mypy.in +types-pexpect==4.9.0.20241208 + # via -r requirements/requirements-mypy.in +types-psutil==6.1.0.20241221 + # via -r requirements/requirements-mypy.in +types-pygments==2.18.0.20240506 + # via -r requirements/requirements-mypy.in +types-pyyaml==6.0.12.20241221 + # via -r requirements/requirements-mypy.in +types-requests==2.32.0.20241016 + # via + # -r requirements/requirements-mypy.in + # types-tqdm +types-setuptools==75.6.0.20241223 + # via types-pygments +types-tqdm==4.67.0.20241221 + # via -r requirements/requirements-mypy.in +types-tree-sitter-languages==1.10.0.20240612 + # via -r requirements/requirements-mypy.in +typing-extensions==4.12.2 + # via + # -c /home/akaihola/repos/ai/aider-chat/requirements.txt + # -c requirements.txt + # mypy +urllib3==2.2.3 + # via + # -c /home/akaihola/repos/ai/aider-chat/requirements.txt + # -c requirements.txt + # types-requests