From bd8b746bffd261dd5cc3a40422cf0cc01e8648a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Bj=C3=A4reholt?= Date: Wed, 23 Oct 2024 19:44:40 +0200 Subject: [PATCH] fix: better error if attempting to run on Windows, refactor readline stuff (#221) * fix: better error if attempting to run on Windows, refactored readline stuff into tabcomplete file * refactor: renamed tabcomplete file to readline * fix: removed remaining imports of readline outside of readline.py * test: fixed tests --- gptme/chat.py | 4 +- gptme/init.py | 37 +-------------- gptme/{tabcomplete.py => readline.py} | 46 ++++++++++++++++++- .../{test_tabcomplete.py => test_readline.py} | 2 +- 4 files changed, 50 insertions(+), 39 deletions(-) rename gptme/{tabcomplete.py => readline.py} (64%) rename tests/{test_tabcomplete.py => test_readline.py} (94%) diff --git a/gptme/chat.py b/gptme/chat.py index 1038a7a8..35b1be8f 100644 --- a/gptme/chat.py +++ b/gptme/chat.py @@ -2,7 +2,6 @@ import logging import os import re -import readline import sys import termios import urllib.parse @@ -18,6 +17,7 @@ from .logmanager import Log, LogManager, prepare_messages from .message import Message from .models import get_model +from .readline import add_history from .tools import ToolUse, execute_msg, has_tool from .tools.base import ConfirmFunc from .tools.browser import read_url @@ -228,7 +228,7 @@ def prompt_user(value=None) -> str: # pragma: no cover print("\nInterrupted. Press Ctrl-D to exit.") clear_interruptible() if response: - readline.add_history(response) + add_history(response) # readline history return response diff --git a/gptme/init.py b/gptme/init.py index 06a29e30..b732f890 100644 --- a/gptme/init.py +++ b/gptme/init.py @@ -1,12 +1,9 @@ -import atexit import logging -import readline from typing import cast from dotenv import load_dotenv from .config import config_path, load_config, set_config_value -from .dirs import get_readline_history_file from .llm import init_llm from .models import ( PROVIDERS, @@ -14,7 +11,7 @@ get_recommended_model, set_default_model, ) -from .tabcomplete import register_tabcomplete +from .readline import load_readline_history, register_tabcomplete from .tools import init_tools from .util import console @@ -74,7 +71,7 @@ def init(model: str | None, interactive: bool, tool_allowlist: list[str] | None) set_default_model(model) if interactive: - _load_readline_history() + load_readline_history() # for some reason it bugs out shell tests in CI register_tabcomplete() @@ -89,36 +86,6 @@ def init_logging(verbose): logging.getLogger("httpx").setLevel(logging.WARNING) -# default history if none found -# NOTE: there are also good examples in the integration tests -history_examples = [ - "What is love?", - "Have you heard about an open-source app called ActivityWatch?", - "Explain 'Attention is All You Need' in the style of Andrej Karpathy.", - "Explain how public-key cryptography works as if I'm five.", - "Write a Python script that prints the first 100 prime numbers.", - "Find all TODOs in the current git project", -] - - -def _load_readline_history() -> None: # pragma: no cover - logger.debug("Loading history") - # enabled by default in CPython, make it explicit - readline.set_auto_history(True) - # had some bugs where it grew to gigs, which should be fixed, but still good precaution - readline.set_history_length(100) - history_file = get_readline_history_file() - try: - readline.read_history_file(history_file) - except FileNotFoundError: - for line in history_examples: - readline.add_history(line) - except Exception: - logger.exception("Failed to load history file") - - atexit.register(readline.write_history_file, history_file) - - def _prompt_api_key() -> tuple[str, str, str]: # pragma: no cover api_key = input("Your OpenAI, Anthropic, or OpenRouter API key: ").strip() if api_key.startswith("sk-ant-"): diff --git a/gptme/tabcomplete.py b/gptme/readline.py similarity index 64% rename from gptme/tabcomplete.py rename to gptme/readline.py index 41624342..9202ad78 100644 --- a/gptme/tabcomplete.py +++ b/gptme/readline.py @@ -1,13 +1,57 @@ +import atexit import logging -import readline from functools import lru_cache from pathlib import Path from .commands import COMMANDS +from .dirs import get_readline_history_file + +# noreorder +try: + import readline # fmt: skip +except ImportError: # pragma: no cover + raise Exception( + "Unsupported platform: readline not available.\nIf you are on Windows, use WSL or Docker to run gptme." + ) from None + logger = logging.getLogger(__name__) +# default history if none found +# NOTE: there are also good examples in the integration tests +history_examples = [ + "What is love?", + "Have you heard about an open-source app called ActivityWatch?", + "Explain 'Attention is All You Need' in the style of Andrej Karpathy.", + "Explain how public-key cryptography works as if I'm five.", + "Write a Python script that prints the first 100 prime numbers.", + "Find all TODOs in the current git project", +] + + +def add_history(line: str) -> None: # pragma: no cover + readline.add_history(line) + + +def load_readline_history() -> None: # pragma: no cover + logger.debug("Loading history") + # enabled by default in CPython, make it explicit + readline.set_auto_history(True) + # had some bugs where it grew to gigs, which should be fixed, but still good precaution + readline.set_history_length(100) + history_file = get_readline_history_file() + try: + readline.read_history_file(history_file) + except FileNotFoundError: + for line in history_examples: + readline.add_history(line) + except Exception: + logger.exception("Failed to load history file") + + atexit.register(readline.write_history_file, history_file) + + def register_tabcomplete() -> None: # pragma: no cover """Register tab completion for readline.""" diff --git a/tests/test_tabcomplete.py b/tests/test_readline.py similarity index 94% rename from tests/test_tabcomplete.py rename to tests/test_readline.py index cc4b729d..fc38e996 100644 --- a/tests/test_tabcomplete.py +++ b/tests/test_readline.py @@ -1,7 +1,7 @@ import os import sys -from gptme.tabcomplete import _matches +from gptme.readline import _matches def test_matches():