diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..1f11ef5 --- /dev/null +++ b/.flake8 @@ -0,0 +1,4 @@ +[flake8] +max-line-length = 88 +extend-ignore = E203, E266, E501, W503 +select = B,C,E,F,W,T4,B9 diff --git a/.github/workflows/flake8.yml b/.github/workflows/flake8.yml new file mode 100644 index 0000000..0945fcb --- /dev/null +++ b/.github/workflows/flake8.yml @@ -0,0 +1,27 @@ +name: Flake8 Linter + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.10' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 flake8-unused-arguments + - name: Run Flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 ./codeaide --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings + flake8 ./codeaide --count --exit-zero --max-complexity=10 --max-line-length=88 --statistics diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9120c3c..5a1d2ab 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,31 @@ repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - repo: https://github.com/psf/black rev: 23.3.0 hooks: - id: black - language_version: python3 + +- repo: https://github.com/PyCQA/flake8 + rev: 6.0.0 + hooks: + - id: flake8 + additional_dependencies: [flake8-unused-arguments] + +- repo: https://github.com/PyCQA/autoflake + rev: v2.1.1 + hooks: + - id: autoflake + args: [ + --in-place, + --remove-all-unused-imports, + --remove-unused-variables, + --expand-star-imports, + --ignore-init-module-imports, + ] diff --git a/codeaide/logic/chat_handler.py b/codeaide/logic/chat_handler.py index 5ff5674..474e8b0 100644 --- a/codeaide/logic/chat_handler.py +++ b/codeaide/logic/chat_handler.py @@ -1,15 +1,12 @@ import json import os -import sys import re import traceback -import logging from codeaide.utils.api_utils import ( parse_response, send_api_request, get_api_client, save_api_key, - MissingAPIKeyException, ) from codeaide.utils.constants import ( MAX_RETRIES, @@ -19,15 +16,11 @@ INITIAL_MESSAGE, ) from codeaide.utils.cost_tracker import CostTracker -from codeaide.utils.environment_manager import EnvironmentManager from codeaide.utils.file_handler import FileHandler from codeaide.utils.terminal_manager import TerminalManager from codeaide.utils.general_utils import generate_session_id from codeaide.utils.logging_config import get_logger, setup_logger -from PyQt5.QtWidgets import QMessageBox, QTextEdit -from PyQt5.QtGui import QFont -from PyQt5.QtCore import QObject, QMetaObject, Qt, Q_ARG, pyqtSlot, pyqtSignal -from codeaide.ui.traceback_dialog import TracebackDialog +from PyQt5.QtCore import QObject, pyqtSignal class ChatHandler(QObject): @@ -354,7 +347,7 @@ def update_conversation_history(self, response): {"role": "assistant", "content": response.choices[0].message.content} ) else: - raise ValueError(f"Unsupported provider: {provider}") + raise ValueError(f"Unsupported provider: {self.current_provider}") self.file_handler.save_chat_history(self.conversation_history) def create_questions_response(self, text, questions): @@ -607,7 +600,7 @@ def send_traceback_to_agent(self, traceback_text): f"```\n{traceback_text}\n```\n\n" "Please provide a solution that avoids this error." ) - self.logger.info(f"ChatHandler: Setting input text in chat window") + self.logger.info("ChatHandler: Setting input text in chat window") self.chat_window.input_text.setPlainText(message) - self.logger.info(f"ChatHandler: Calling on_submit in chat window") + self.logger.info("ChatHandler: Calling on_submit in chat window") self.chat_window.on_submit() diff --git a/codeaide/ui/chat_window.py b/codeaide/ui/chat_window.py index aecda4a..10cdf6a 100644 --- a/codeaide/ui/chat_window.py +++ b/codeaide/ui/chat_window.py @@ -1,8 +1,4 @@ import signal -import sys -import traceback -import time -import logging from PyQt5.QtCore import Qt, QTimer from PyQt5.QtGui import QColor from PyQt5.QtWidgets import ( @@ -11,8 +7,6 @@ QMainWindow, QMessageBox, QPushButton, - QSizePolicy, - QSpacerItem, QTextEdit, QVBoxLayout, QWidget, @@ -35,7 +29,6 @@ USER_MESSAGE_COLOR, AI_PROVIDERS, DEFAULT_PROVIDER, - DEFAULT_MODEL, MODEL_SWITCH_MESSAGE, ) from codeaide.utils.logging_config import get_logger @@ -168,7 +161,7 @@ def on_submit(self): self.logger.info("ChatWindow: Empty input, returning") return - self.logger.info(f"ChatWindow: Processing user input") + self.logger.info("ChatWindow: Processing user input") self.input_text.clear() if self.waiting_for_api_key: @@ -350,22 +343,18 @@ def update_chat_handler(self): self.logger.info(f"In update_chat_handler, message: {message}") if not success: - self.logger.info(f"In update_chat_handler, not success") + self.logger.info("In update_chat_handler, not success") if message: # This indicates that an API key is required self.waiting_for_api_key = True self.add_to_chat("AI", message) else: - self.logger.info(f"In update_chat_handler, not success, no message") + self.logger.info("In update_chat_handler, not success, no message") self.add_to_chat( "System", f"Failed to set model {model} for provider {provider}. Please check your API key.", ) return - new_version = general_utils.increment_version( - current_version, major_or_minor="major", increment=1 - ) - # Use the constant with format switch_message = MODEL_SWITCH_MESSAGE.format( provider=provider, diff --git a/codeaide/ui/code_popup.py b/codeaide/ui/code_popup.py index 53146ae..2118e3f 100644 --- a/codeaide/ui/code_popup.py +++ b/codeaide/ui/code_popup.py @@ -15,16 +15,11 @@ QHBoxLayout, QLabel, QPushButton, - QTextEdit, QVBoxLayout, QWidget, QPlainTextEdit, - QMessageBox, QDialog, ) -from pygments import highlight -from pygments.lexers import PythonLexer -from pygments.formatters import HtmlFormatter from codeaide.utils import general_utils from codeaide.utils.constants import ( diff --git a/codeaide/ui/example_selection_dialog.py b/codeaide/ui/example_selection_dialog.py index 7966b5b..3a210f3 100644 --- a/codeaide/ui/example_selection_dialog.py +++ b/codeaide/ui/example_selection_dialog.py @@ -9,7 +9,6 @@ ) from codeaide.utils import general_utils -from codeaide.utils.constants import CHAT_WINDOW_BG, CHAT_WINDOW_FG class ExampleSelectionDialog(QDialog): diff --git a/codeaide/ui/traceback_dialog.py b/codeaide/ui/traceback_dialog.py index 1b5c135..0bb203f 100644 --- a/codeaide/ui/traceback_dialog.py +++ b/codeaide/ui/traceback_dialog.py @@ -1,6 +1,5 @@ from PyQt5.QtWidgets import QMessageBox, QTextEdit from PyQt5.QtGui import QFont -from PyQt5.QtCore import Qt import logging @@ -38,7 +37,7 @@ def __init__(self, parent, traceback_text): def exec_(self): self.logger.info("TracebackDialog: Executing dialog") - result = super().exec_() + super().exec_() user_choice = "fix" if self.clickedButton() == self.send_button else "ignore" self.logger.info(f"TracebackDialog: User chose to {user_choice} the traceback") return self.clickedButton() == self.send_button diff --git a/codeaide/utils/api_utils.py b/codeaide/utils/api_utils.py index 4166acb..35621f4 100644 --- a/codeaide/utils/api_utils.py +++ b/codeaide/utils/api_utils.py @@ -1,12 +1,8 @@ import os -import json -import re import anthropic import openai -from decouple import config, AutoConfig +from decouple import AutoConfig import hjson -from anthropic import APIError -import logging from codeaide.utils.constants import ( AI_PROVIDERS, diff --git a/codeaide/utils/cost_tracker.py b/codeaide/utils/cost_tracker.py index 851a6b9..904f088 100644 --- a/codeaide/utils/cost_tracker.py +++ b/codeaide/utils/cost_tracker.py @@ -1,6 +1,3 @@ -from datetime import datetime - - class CostTracker: def __init__(self): self.cost_log = [] diff --git a/codeaide/utils/environment_manager.py b/codeaide/utils/environment_manager.py index 420b07f..5e66e62 100644 --- a/codeaide/utils/environment_manager.py +++ b/codeaide/utils/environment_manager.py @@ -1,6 +1,5 @@ import os import subprocess -import sys import venv from codeaide.utils.logging_config import get_logger diff --git a/codeaide/utils/file_handler.py b/codeaide/utils/file_handler.py index 294cb5c..1925bc5 100644 --- a/codeaide/utils/file_handler.py +++ b/codeaide/utils/file_handler.py @@ -1,7 +1,6 @@ import os import shutil import json -import logging from codeaide.utils.logging_config import setup_logger, get_logger diff --git a/codeaide/utils/general_utils.py b/codeaide/utils/general_utils.py index e8fb107..cb2e007 100644 --- a/codeaide/utils/general_utils.py +++ b/codeaide/utils/general_utils.py @@ -3,6 +3,7 @@ from PyQt5.QtGui import QFont, QColor from datetime import datetime from codeaide.utils.logging_config import get_logger +import sys logger = get_logger() diff --git a/codeaide/utils/terminal_manager.py b/codeaide/utils/terminal_manager.py index 23fce5c..fc9c9bc 100644 --- a/codeaide/utils/terminal_manager.py +++ b/codeaide/utils/terminal_manager.py @@ -2,12 +2,10 @@ import os import subprocess import sys -import tempfile import threading import logging import queue import time -import re from codeaide.utils.environment_manager import EnvironmentManager diff --git a/requirements.txt b/requirements.txt index 5983f60..348e3f2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,7 @@ black isort pre-commit pyqt5 -pygments \ No newline at end of file +pygments +flake8 +flake8-unused-arguments +autoflake diff --git a/tests/integration/test_api_integration.py b/tests/integration/test_api_integration.py index f3953a1..8a23244 100644 --- a/tests/integration/test_api_integration.py +++ b/tests/integration/test_api_integration.py @@ -28,7 +28,6 @@ import pytest from codeaide.utils.api_utils import get_api_client, send_api_request, parse_response -from codeaide.utils.constants import SYSTEM_PROMPT ANTHROPIC_MODEL = "claude-3-haiku-20240307" OPENAI_MODEL = "gpt-3.5-turbo" diff --git a/tests/ui/test_chat_window.py b/tests/ui/test_chat_window.py index fec9255..2f3fcca 100644 --- a/tests/ui/test_chat_window.py +++ b/tests/ui/test_chat_window.py @@ -1,5 +1,4 @@ -import sys -from unittest.mock import Mock, patch, MagicMock +from unittest.mock import Mock, patch import pytest import logging import os @@ -10,7 +9,6 @@ from codeaide.ui.chat_window import ChatWindow from codeaide.logic.chat_handler import ChatHandler -from codeaide.utils import general_utils from codeaide.utils.constants import ( AI_PROVIDERS, DEFAULT_PROVIDER, diff --git a/tests/utils/test_api_utils.py b/tests/utils/test_api_utils.py index 1bbbca5..c56ed19 100644 --- a/tests/utils/test_api_utils.py +++ b/tests/utils/test_api_utils.py @@ -3,7 +3,6 @@ from unittest.mock import Mock, patch import pytest -import anthropic from anthropic import APIError from codeaide.utils.api_utils import ( @@ -11,7 +10,6 @@ parse_response, send_api_request, get_api_client, - MissingAPIKeyException, ) from codeaide.utils.constants import ( DEFAULT_MODEL, @@ -91,11 +89,8 @@ class TestGetApiClient: Various test methods to cover different scenarios for get_api_client function. """ - @patch("codeaide.utils.api_utils.config") @patch("codeaide.utils.api_utils.AutoConfig") - def test_get_api_client_missing_key( - self, mock_auto_config, mock_config, monkeypatch - ): + def test_get_api_client_missing_key(self, mock_auto_config): """ Test the behavior of get_api_client when the API key is missing. @@ -104,23 +99,22 @@ def test_get_api_client_missing_key( Args: mock_auto_config (MagicMock): A mock object for the AutoConfig class. - mock_config (MagicMock): A mock object for the config function. - monkeypatch (pytest.MonkeyPatch): Pytest fixture for modifying the test environment. The test performs the following steps: - 1. Mocks the config function to return None, simulating a missing API key. - 2. Sets up the mock AutoConfig to use the mocked config function. - 3. Removes the ANTHROPIC_API_KEY from the environment variables. - 4. Calls get_api_client with the "anthropic" provider. - 5. Asserts that the returned client is None, as expected when the API key is missing. + 1. Mocks the AutoConfig to return None, simulating a missing API key. + 2. Calls get_api_client with the "anthropic" provider. + 3. Asserts that the returned client is None, as expected when the API key is missing. """ + mock_config = Mock() mock_config.return_value = None mock_auto_config.return_value = mock_config - monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False) + client = get_api_client(provider="anthropic") assert client is None - def test_get_api_client_success(self, monkeypatch): + @patch("codeaide.utils.api_utils.AutoConfig") + @patch("anthropic.Anthropic") + def test_get_api_client_success(self, mock_anthropic, mock_auto_config): """ Test the successful creation of an API client for Anthropic. @@ -128,20 +122,29 @@ def test_get_api_client_success(self, monkeypatch): an Anthropic API client when a valid API key is provided in the environment. Args: - monkeypatch (pytest.MonkeyPatch): Pytest fixture for modifying the test environment. + mock_anthropic (MagicMock): A mock object for the Anthropic class. + mock_auto_config (MagicMock): A mock object for the AutoConfig class. The test performs the following steps: - 1. Sets the ANTHROPIC_API_KEY environment variable to a test value. - 2. Calls get_api_client with the "anthropic" provider. - 3. Asserts that the returned client is not None. - 4. Verifies that the client has a 'messages' attribute, which is expected for Anthropic clients. + 1. Mocks the AutoConfig to return a test API key. + 2. Mocks the Anthropic class to return a mock client. + 3. Calls get_api_client with the "anthropic" provider. + 4. Asserts that the returned client is not None. + 5. Verifies that the client is the same as the mock client. """ - monkeypatch.setenv("ANTHROPIC_API_KEY", "test_key") + mock_config = Mock() + mock_config.return_value = "test_key" + mock_auto_config.return_value = mock_config + + mock_client = Mock() + mock_anthropic.return_value = mock_client + client = get_api_client(provider="anthropic") assert client is not None - assert hasattr(client, "messages") + assert client == mock_client - def test_get_api_client_empty_key(self, monkeypatch): + @patch("codeaide.utils.api_utils.AutoConfig") + def test_get_api_client_empty_key(self, mock_auto_config): """ Test the behavior of get_api_client when the API key is empty. @@ -149,14 +152,17 @@ def test_get_api_client_empty_key(self, monkeypatch): ANTHROPIC_API_KEY is set to an empty string in the environment variables. Args: - monkeypatch (pytest.MonkeyPatch): Pytest fixture for modifying the test environment. + mock_auto_config (MagicMock): A mock object for the AutoConfig class. The test performs the following steps: - 1. Sets the ANTHROPIC_API_KEY environment variable to an empty string. + 1. Mocks the AutoConfig to return an empty string, simulating an empty API key. 2. Calls get_api_client with the "anthropic" provider. 3. Asserts that the returned client is None, as expected when the API key is empty. """ - monkeypatch.setenv("ANTHROPIC_API_KEY", "") + mock_config = Mock() + mock_config.return_value = "" + mock_auto_config.return_value = mock_config + client = get_api_client(provider="anthropic") assert client is None @@ -569,7 +575,7 @@ def test_check_api_connection_success(self, mock_get_api_client): result = check_api_connection() - assert result[0] == True + assert result[0] is True assert result[1] == "Yes, we are communicating." @patch("codeaide.utils.api_utils.get_api_client") @@ -586,7 +592,7 @@ def test_check_api_connection_failure(self, mock_get_api_client): result = check_api_connection() - assert result[0] == False + assert result[0] is False assert "Connection failed" in result[1] @patch("codeaide.utils.api_utils.get_api_client") @@ -601,5 +607,5 @@ def test_check_api_connection_missing_key(self, mock_get_api_client): result = check_api_connection() - assert result[0] == False + assert result[0] is False assert result[1] == "API key is missing or invalid"