chore: use primitives instead of typing imports and fixes completion … (#149)

Signed-off-by: Adrian Cole <adrian.cole@elastic.co>
This commit is contained in:
Adrian Cole
2024-10-16 09:41:37 +11:00
committed by GitHub
parent e687b0b3bc
commit c247c8eb30
53 changed files with 235 additions and 257 deletions
+6 -6
View File
@@ -1,6 +1,6 @@
from functools import cache
from pathlib import Path
from typing import Callable, Dict, Mapping, Optional, Tuple
from typing import Mapping, Optional
from rich import print
from rich.panel import Panel
@@ -20,7 +20,7 @@ RECOMMENDED_DEFAULT_PROVIDER = "openai"
@cache
def default_profiles() -> Mapping[str, Callable]:
def default_profiles() -> Mapping[str, callable]:
return load_plugins(group="goose.profile")
@@ -29,7 +29,7 @@ def session_path(name: str) -> Path:
return SESSIONS_PATH.joinpath(f"{name}{SESSION_FILE_SUFFIX}")
def write_config(profiles: Dict[str, Profile]) -> None:
def write_config(profiles: dict[str, Profile]) -> None:
"""Overwrite the config with the passed profiles"""
PROFILES_CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
converted = {name: profile.to_dict() for name, profile in profiles.items()}
@@ -38,7 +38,7 @@ def write_config(profiles: Dict[str, Profile]) -> None:
yaml.dump(converted, f)
def ensure_config(name: Optional[str]) -> Tuple[str, Profile]:
def ensure_config(name: Optional[str]) -> tuple[str, Profile]:
"""Ensure that the config exists and has the default section"""
# TODO we should copy a templated default config in to better document
# but this is complicated a bit by autodetecting the provider
@@ -70,7 +70,7 @@ def ensure_config(name: Optional[str]) -> Tuple[str, Profile]:
return (name, default_profile)
def read_config() -> Dict[str, Profile]:
def read_config() -> dict[str, Profile]:
"""Return config from the configuration file and validates its contents"""
yaml = YAML()
@@ -80,7 +80,7 @@ def read_config() -> Dict[str, Profile]:
return {name: Profile(**profile) for name, profile in data.items()}
def default_model_configuration() -> Tuple[str, str, str]:
def default_model_configuration() -> tuple[str, str, str]:
providers = load_plugins(group="exchange.provider")
for provider, cls in providers.items():
try:
+4 -5
View File
@@ -1,5 +1,4 @@
import re
from typing import List
from prompt_toolkit.completion import CompleteEvent, Completer, Completion
from prompt_toolkit.document import Document
@@ -8,10 +7,10 @@ from goose.command.base import Command
class GoosePromptCompleter(Completer):
def __init__(self, commands: List[Command]) -> None:
def __init__(self, commands: list[Command]) -> None:
self.commands = commands
def get_command_completions(self, document: Document) -> List[Completion]:
def get_command_completions(self, document: Document) -> list[Completion]:
all_completions = []
for command_name, command_instance in self.commands.items():
pattern = rf"(?<!\S)\/{command_name}:([\S]*)$"
@@ -25,7 +24,7 @@ class GoosePromptCompleter(Completer):
all_completions.extend(completions)
return all_completions
def get_command_name_completions(self, document: Document) -> List[Completion]:
def get_command_name_completions(self, document: Document) -> list[Completion]:
pattern = r"(?<!\S)\/([\S]*)$"
text = document.text_before_cursor
match = re.search(pattern=pattern, string=text)
@@ -40,7 +39,7 @@ class GoosePromptCompleter(Completer):
completions.append(Completion(command_name, start_position=-len(query), display=command_name))
return completions
def get_completions(self, document: Document, _: CompleteEvent) -> List[Completion]:
def get_completions(self, document: Document, _: CompleteEvent) -> list[Completion]:
command_completions = self.get_command_completions(document)
command_name_completions = self.get_command_name_completions(document)
return command_name_completions + command_completions
+3 -3
View File
@@ -1,5 +1,5 @@
import re
from typing import Callable, List, Tuple
from typing import Callable
from prompt_toolkit.document import Document
from prompt_toolkit.lexers import Lexer
@@ -27,7 +27,7 @@ def value_for_command(command_string: str) -> re.Pattern[str]:
class PromptLexer(Lexer):
def __init__(self, command_names: List[str]) -> None:
def __init__(self, command_names: list[str]) -> None:
self.patterns = []
for command_name in command_names:
self.patterns.append((completion_for_command(command_name), "class:command"))
@@ -35,7 +35,7 @@ class PromptLexer(Lexer):
self.patterns.append((command_itself(command_name), "class:command"))
def lex_document(self, document: Document) -> Callable[[int], list]:
def get_line_tokens(line_number: int) -> Tuple[str, str]:
def get_line_tokens(line_number: int) -> tuple[str, str]:
line = document.lines[line_number]
tokens = []
@@ -1,10 +1,8 @@
from typing import Any
from rich.prompt import Prompt
class OverwriteSessionPrompt(Prompt):
def __init__(self, *args: tuple[Any], **kwargs: dict[str, Any]) -> None:
def __init__(self, *args: tuple[any], **kwargs: dict[str, any]) -> None:
super().__init__(*args, **kwargs)
self.choices = {
"yes": "Overwrite the existing session",
+2 -2
View File
@@ -1,6 +1,6 @@
import traceback
from pathlib import Path
from typing import Any, Optional
from typing import Optional
from exchange import Message, Text, ToolResult, ToolUse
from rich import print
@@ -62,7 +62,7 @@ class Session:
profile: Optional[str] = None,
plan: Optional[dict] = None,
log_level: Optional[str] = "INFO",
**kwargs: dict[str, Any],
**kwargs: dict[str, any],
) -> None:
if name is None:
self.name = droid()
+1 -2
View File
@@ -1,5 +1,4 @@
from functools import cache
from typing import Dict
from goose.command.base import Command
from goose.utils import load_plugins
@@ -11,5 +10,5 @@ def get_command(name: str) -> type[Command]:
@cache
def get_commands() -> Dict[str, type[Command]]:
def get_commands() -> dict[str, type[Command]]:
return load_plugins(group="goose.command")
+2 -2
View File
@@ -1,5 +1,5 @@
from abc import ABC
from typing import List, Optional
from typing import Optional
from prompt_toolkit.completion import Completion
@@ -7,7 +7,7 @@ from prompt_toolkit.completion import Completion
class Command(ABC):
"""A command that can be executed by the CLI."""
def get_completions(self, query: str) -> List[Completion]:
def get_completions(self, query: str) -> list[Completion]:
"""
Get completions for the command.
+1 -2
View File
@@ -1,5 +1,4 @@
import os
from typing import List
from prompt_toolkit.completion import Completion
@@ -7,7 +6,7 @@ from goose.command.base import Command
class FileCommand(Command):
def get_completions(self, query: str) -> List[Completion]:
def get_completions(self, query: str) -> list[Completion]:
if query.startswith("/"):
directory = os.path.dirname(query)
search_term = os.path.basename(query)
+5 -5
View File
@@ -1,4 +1,4 @@
from typing import Any, Dict, List, Mapping, Type
from typing import Mapping
from attrs import asdict, define, field
@@ -21,10 +21,10 @@ class Profile:
processor: str
accelerator: str
moderator: str
toolkits: List[ToolkitSpec] = field(factory=list, converter=ensure_list(ToolkitSpec))
toolkits: list[ToolkitSpec] = field(factory=list, converter=ensure_list(ToolkitSpec))
@toolkits.validator
def check_toolkit_requirements(self, _: Type["ToolkitSpec"], toolkits: List[ToolkitSpec]) -> None:
def check_toolkit_requirements(self, _: type["ToolkitSpec"], toolkits: list[ToolkitSpec]) -> None:
# checks that the list of toolkits in the profile have their requirements
installed_toolkits = set([toolkit.name for toolkit in toolkits])
@@ -36,7 +36,7 @@ class Profile:
msg = f"Toolkit {toolkit_name} requires {req} but it is not present"
raise ValueError(msg)
def to_dict(self) -> Dict[str, Any]:
def to_dict(self) -> dict[str, any]:
return asdict(self)
def profile_info(self) -> str:
@@ -44,7 +44,7 @@ class Profile:
return f"provider:{self.provider}, processor:{self.processor} toolkits: {', '.join(tookit_names)}"
def default_profile(provider: str, processor: str, accelerator: str, **kwargs: Dict[str, Any]) -> Profile:
def default_profile(provider: str, processor: str, accelerator: str, **kwargs: dict[str, any]) -> Profile:
"""Get the default profile"""
# TODO consider if the providers should have recommended models
+3 -3
View File
@@ -1,6 +1,6 @@
import inspect
from abc import ABC
from typing import Callable, Mapping, Optional, Tuple, TypeVar
from typing import Mapping, Optional, TypeVar
from attrs import define, field
from exchange import Tool
@@ -8,7 +8,7 @@ from exchange import Tool
from goose.notifier import Notifier
# Create a type variable that can represent any function signature
F = TypeVar("F", bound=Callable)
F = TypeVar("F", bound=callable)
def tool(func: F) -> F:
@@ -55,7 +55,7 @@ class Toolkit(ABC):
"""Get the addition to the system prompt for this toolkit."""
return ""
def tools(self) -> Tuple[Tool, ...]:
def tools(self) -> tuple[Tool, ...]:
"""Get the tools for this toolkit
This default method looks for functions on the toolkit annotated
+4 -5
View File
@@ -3,7 +3,6 @@ import re
import subprocess
import time
from pathlib import Path
from typing import Dict, List
from exchange import Message
from goose.toolkit.base import Toolkit, tool
@@ -35,9 +34,9 @@ class Developer(Toolkit):
We also include some default shell strategies in the prompt, such as using ripgrep
"""
def __init__(self, *args: object, **kwargs: Dict[str, object]) -> None:
def __init__(self, *args: object, **kwargs: dict[str, object]) -> None:
super().__init__(*args, **kwargs)
self.timestamps: Dict[str, float] = {}
self.timestamps: dict[str, float] = {}
def system(self) -> str:
"""Retrieve system configuration details for developer"""
@@ -55,7 +54,7 @@ class Developer(Toolkit):
return system_prompt
@tool
def update_plan(self, tasks: List[dict]) -> List[dict]:
def update_plan(self, tasks: list[dict]) -> list[dict]:
"""
Update the plan by overwriting all current tasks
@@ -63,7 +62,7 @@ class Developer(Toolkit):
shown to the user directly, you do not need to reiterate it
Args:
tasks (List(dict)): The list of tasks, where each task is a dictionary
tasks (list(dict)): The list of tasks, where each task is a dictionary
with a key for the task "description" and the task "status". The status
MUST be one of "planned", "complete", "failed", "in-progress".
@@ -1,7 +1,6 @@
import os
from functools import cache
from subprocess import CompletedProcess, run
from typing import Dict, Tuple
from exchange import Message
@@ -21,7 +20,7 @@ class RepoContext(Toolkit):
self.repo_project_root, self.is_git_repo, self.goose_session_root = self.determine_git_proj()
def determine_git_proj(self) -> Tuple[str, bool, str]:
def determine_git_proj(self) -> tuple[str, bool, str]:
"""Determines the root as well as where Goose is currently running
If the project is not part of a Github repo, the root of the project will be defined as the current working
@@ -72,11 +71,11 @@ class RepoContext(Toolkit):
return self.repo_size > 2000
@tool
def summarize_current_project(self) -> Dict[str, str]:
def summarize_current_project(self) -> dict[str, str]:
"""Summarizes the current project based on repo root (if git repo) or current project_directory (if not)
Returns:
summary (Dict[str, str]): Keys are file paths and values are the summaries
summary (dict[str, str]): Keys are file paths and values are the summaries
"""
self.notifier.log("Summarizing the most relevant files in the current project. This may take a while...")
+3 -4
View File
@@ -2,7 +2,6 @@ import ast
import concurrent.futures
import os
from collections import deque
from typing import Dict, List, Tuple
from exchange import Exchange
@@ -26,7 +25,7 @@ def get_repo_size(repo_path: str) -> int:
return get_directory_size(git_dir) / (1024**2)
def get_files_and_directories(root_dir: str) -> Dict[str, list]:
def get_files_and_directories(root_dir: str) -> dict[str, list]:
"""Gets file names and directory names. Checks that goose has correctly typed the file and directory names and that
the files actually exist (to avoid downstream file read errors).
@@ -61,7 +60,7 @@ def get_files_and_directories(root_dir: str) -> Dict[str, list]:
return {"files": files, "directories": dirs}
def goose_picks_files(root: str, exchange: Exchange, max_workers: int = 4) -> List[str]:
def goose_picks_files(root: str, exchange: Exchange, max_workers: int = 4) -> list[str]:
"""Lets goose pick files in a BFS manner"""
queue = deque([root])
@@ -80,7 +79,7 @@ def goose_picks_files(root: str, exchange: Exchange, max_workers: int = 4) -> Li
return all_files
def process_directory(current_dir: str, exchange: Exchange) -> Tuple[List[str], List[str]]:
def process_directory(current_dir: str, exchange: Exchange) -> tuple[list[str], list[str]]:
"""Allows goose to pick files and subdirectories contained in a given directory (current_dir). Get the list of file
and directory names in the current folder, then ask Goose to pick which ones to keep.
@@ -1,5 +1,5 @@
import os
from typing import List, Optional
from typing import Optional
from goose.toolkit import Toolkit
from goose.toolkit.base import tool
@@ -11,7 +11,7 @@ class SummarizeProject(Toolkit):
def get_project_summary(
self,
project_dir_path: Optional[str] = os.getcwd(),
extensions: Optional[List[str]] = None,
extensions: Optional[list[str]] = None,
summary_instructions_prompt: Optional[str] = None,
) -> dict:
"""Generates or retrieves a project summary based on specified file extensions.
@@ -19,7 +19,7 @@ class SummarizeProject(Toolkit):
Args:
project_dir_path (Optional[Path]): Path to the project directory. Defaults to the current working directory
if None
extensions (Optional[List[str]]): Specific file extensions to summarize.
extensions (Optional[list[str]]): Specific file extensions to summarize.
summary_instructions_prompt (Optional[str]): Instructions to give to the LLM about how to summarize each file. E.g.
"Summarize the file in two sentences.". The default instruction is "Please summarize this file."
@@ -1,4 +1,4 @@
from typing import List, Optional
from typing import Optional
from goose.toolkit import Toolkit
from goose.toolkit.base import tool
@@ -10,7 +10,7 @@ class SummarizeRepo(Toolkit):
def summarize_repo(
self,
repo_url: str,
specified_extensions: Optional[List[str]] = None,
specified_extensions: Optional[list[str]] = None,
summary_instructions_prompt: Optional[str] = None,
) -> dict:
"""
@@ -19,7 +19,7 @@ class SummarizeRepo(Toolkit):
Args:
repo_url (str): The URL of the repository to summarize.
specified_extensions (Optional[List[str]]): List of file extensions to summarize, e.g., ["tf", "md"]. If
specified_extensions (Optional[list[str]]): list of file extensions to summarize, e.g., ["tf", "md"]. If
this list is empty, then all files in the repo are summarized
summary_instructions_prompt (Optional[str]): Instructions to give to the LLM about how to summarize each file. E.g.
"Summarize the file in two sentences.". The default instruction is "Please summarize this file."
+15 -15
View File
@@ -2,7 +2,7 @@ import json
import subprocess
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from typing import Optional
from exchange import Exchange
from exchange.providers.utils import InitialMessageTooLargeError
@@ -15,7 +15,7 @@ CLONED_REPOS_FOLDER = ".goose/cloned_repos"
# TODO: move git stuff
def run_git_command(command: List[str]) -> subprocess.CompletedProcess[str]:
def run_git_command(command: list[str]) -> subprocess.CompletedProcess[str]:
result = subprocess.run(["git"] + command, capture_output=True, text=True, check=False)
if result.returncode != 0:
@@ -28,7 +28,7 @@ def clone_repo(repo_url: str, target_directory: str) -> None:
run_git_command(["clone", repo_url, target_directory])
def load_summary_file_if_exists(project_name: str) -> Optional[Dict]:
def load_summary_file_if_exists(project_name: str) -> Optional[dict]:
"""Checks if a summary file exists at '.goose/summaries/projectname-summary.json. Returns contents of the file if
it exists, otherwise returns None
@@ -36,7 +36,7 @@ def load_summary_file_if_exists(project_name: str) -> Optional[Dict]:
project_name (str): name of the project or repo
Returns:
Optional[Dict]: File contents, else None
Optional[dict]: File contents, else None
"""
summary_file_path = f"{SUMMARIES_FOLDER}/{project_name}-summary.json"
if Path(summary_file_path).exists():
@@ -44,7 +44,7 @@ def load_summary_file_if_exists(project_name: str) -> Optional[Dict]:
return json.load(f)
def summarize_file(filepath: str, exchange: Exchange, prompt: Optional[str] = None) -> Tuple[str, str]:
def summarize_file(filepath: str, exchange: Exchange, prompt: Optional[str] = None) -> tuple[str, str]:
"""Summarizes a single file
Args:
@@ -74,15 +74,15 @@ def summarize_file(filepath: str, exchange: Exchange, prompt: Optional[str] = No
def summarize_repo(
repo_url: str,
exchange: Exchange,
extensions: List[str],
extensions: list[str],
summary_instructions_prompt: Optional[str] = None,
) -> Dict[str, str]:
) -> dict[str, str]:
"""Clones (if needed) and summarizes a repo
Args:
repo_url (str): Repository url
exchange (Exchange): Exchange for summarizing the repo.
extensions (List[str]): List of file-types to summarize.
extensions (list[str]): list of file-types to summarize.
summary_instructions_prompt (Optional[str]): Optional parameter to customize summarization results. Defaults to
"Please summarize this file"
"""
@@ -110,15 +110,15 @@ def summarize_repo(
def summarize_directory(
directory: str, exchange: Exchange, extensions: List[str], summary_instructions_prompt: Optional[str] = None
) -> Dict[str, str]:
directory: str, exchange: Exchange, extensions: list[str], summary_instructions_prompt: Optional[str] = None
) -> dict[str, str]:
"""Summarize files in a given directory based on extensions. Will also recursively find files in subdirectories and
summarize them.
Args:
directory (str): path to the top-level directory to summarize
exchange (Exchange): Exchange to use to summarize
extensions (List[str]): List of file-type extensions to summarize (and ignore all other extensions).
extensions (list[str]): list of file-type extensions to summarize (and ignore all other extensions).
summary_instructions_prompt (Optional[str]): Optional instructions to give to the exchange regarding summarization.
Returns:
@@ -158,19 +158,19 @@ def summarize_directory(
def summarize_files_concurrent(
exchange: Exchange, file_list: List[str], project_name: str, summary_instructions_prompt: Optional[str] = None
) -> Dict[str, str]:
exchange: Exchange, file_list: list[str], project_name: str, summary_instructions_prompt: Optional[str] = None
) -> dict[str, str]:
"""Takes in a list of files and summarizes them. Exchange does not keep history of the summarized files.
Args:
exchange (Exchange): Underlying exchange
file_list (List[str]): List of paths to files to summarize
file_list (list[str]): list of paths to files to summarize
project_name (str): Used to save the summary of the files to .goose/summaries/<project_name>-summary.json
summary_instructions_prompt (Optional[str]): Summary instructions for the LLM. Defaults to "Please summarize
this file."
Returns:
file_summaries (Dict[str, str]): Keys are file paths and values are the summaries returned by the Exchange
file_summaries (dict[str, str]): Keys are file paths and values are the summaries returned by the Exchange
"""
summary_file = load_summary_file_if_exists(project_name)
if summary_file:
+2 -2
View File
@@ -1,5 +1,5 @@
from pathlib import Path
from typing import Optional, Dict
from typing import Optional
from pygments.lexers import get_lexer_for_filename
from pygments.util import ClassNotFound
@@ -67,7 +67,7 @@ def find_last_task_group_index(input_str: str) -> int:
return last_group_start_index
def parse_plan(input_plan_str: str) -> Dict:
def parse_plan(input_plan_str: str) -> dict:
last_group_start_index = find_last_task_group_index(input_plan_str)
if last_group_start_index == -1:
return {"kickoff_message": input_plan_str, "tasks": []}
+5 -5
View File
@@ -1,7 +1,7 @@
import random
import string
from importlib.metadata import entry_points
from typing import Any, Callable, Dict, List, Type, TypeVar
from typing import TypeVar, Callable
T = TypeVar("T")
@@ -31,10 +31,10 @@ def load_plugins(group: str) -> dict:
return plugins
def ensure(cls: Type[T]) -> Callable[[Any], T]:
def ensure(cls: type[T]) -> Callable[[any], T]:
"""Convert dictionary to a class instance"""
def converter(val: Any) -> T: # noqa: ANN401
def converter(val: any) -> T: # noqa: ANN401
if isinstance(val, cls):
return val
elif isinstance(val, dict):
@@ -47,10 +47,10 @@ def ensure(cls: Type[T]) -> Callable[[Any], T]:
return converter
def ensure_list(cls: Type[T]) -> Callable[[List[Dict[str, Any]]], Type[T]]:
def ensure_list(cls: type[T]) -> Callable[[list[dict[str, any]]], type[T]]:
"""Convert a list of dictionaries to class instances"""
def converter(val: List[Dict[str, Any]]) -> List[T]:
def converter(val: list[dict[str, any]]) -> list[T]:
output = []
for entry in val:
output.append(ensure(cls)(entry))
+10 -10
View File
@@ -2,7 +2,7 @@ import glob
import os
from collections import Counter
from pathlib import Path
from typing import Dict, List, Optional
from typing import Optional
def create_extensions_list(project_root: str, max_n: int) -> list:
@@ -11,7 +11,7 @@ def create_extensions_list(project_root: str, max_n: int) -> list:
project_root (str): Root of the project to analyze
max_n (int): The number of file extensions to return
Returns:
extensions (List[str]): A list of the top N file extensions
extensions (list[str]): A list of the top N file extensions
"""
if max_n == 0:
raise (ValueError("Number of file extensions must be greater than 0"))
@@ -31,14 +31,14 @@ def create_extensions_list(project_root: str, max_n: int) -> list:
return extensions
def create_language_weighting(files_in_directory: List[str]) -> Dict[str, float]:
def create_language_weighting(files_in_directory: list[str]) -> dict[str, float]:
"""Calculate language weighting by file size to match GitHub's methodology.
Args:
files_in_directory (List[str]): Paths to files in the project directory
files_in_directory (list[str]): Paths to files in the project directory
Returns:
Dict[str, float]: A dictionary with languages as keys and their percentage of the total codebase as values
dict[str, float]: A dictionary with languages as keys and their percentage of the total codebase as values
"""
# Initialize counters for sizes
@@ -59,7 +59,7 @@ def create_language_weighting(files_in_directory: List[str]) -> Dict[str, float]
return dict(sorted(language_percentages.items(), key=lambda item: item[1], reverse=True))
def list_files_with_extension(dir_path: str, extension: Optional[str] = "") -> List[str]:
def list_files_with_extension(dir_path: str, extension: Optional[str] = "") -> list[str]:
"""List all files in a directory with a given extension. Set extension to '' to return all files.
Args:
@@ -67,7 +67,7 @@ def list_files_with_extension(dir_path: str, extension: Optional[str] = "") -> L
extension (Optional[str]): extension to lookup. Defaults to '' which will return all files.
Returns:
files (List[str]): List of file paths
files (list[str]): list of file paths
"""
# add a leading '.' to extension if needed
if extension and not extension.startswith("."):
@@ -77,15 +77,15 @@ def list_files_with_extension(dir_path: str, extension: Optional[str] = "") -> L
return files
def create_file_list(dir_path: str, extensions: List[str]) -> List[str]:
def create_file_list(dir_path: str, extensions: list[str]) -> list[str]:
"""Creates a list of files with certain extensions
Args:
dir_path (str): Directory to list files of. Will include files recursively in sub-directories.
extensions (List[str]): List of file extensions to select for. If empty list, return all files
extensions (list[str]): list of file extensions to select for. If empty list, return all files
Returns:
final_file_list (List[str]): List of file paths with specified extensions.
final_file_list (list[str]): list of file paths with specified extensions.
"""
# if extensions is empty list, return all files
if not extensions:
+7 -7
View File
@@ -2,7 +2,7 @@ import json
import os
import tempfile
from pathlib import Path
from typing import Dict, Iterator, List
from typing import Iterator
from exchange import Message
@@ -17,12 +17,12 @@ def is_empty_session(path: Path) -> bool:
return path.is_file() and path.stat().st_size == 0
def write_to_file(file_path: Path, messages: List[Message]) -> None:
def write_to_file(file_path: Path, messages: list[Message]) -> None:
with open(file_path, "w") as f:
_write_messages_to_file(f, messages)
def read_or_create_file(file_path: Path) -> List[Message]:
def read_or_create_file(file_path: Path) -> list[Message]:
if file_path.exists():
return read_from_file(file_path)
with open(file_path, "w"):
@@ -30,7 +30,7 @@ def read_or_create_file(file_path: Path) -> List[Message]:
return []
def read_from_file(file_path: Path) -> List[Message]:
def read_from_file(file_path: Path) -> list[Message]:
try:
with open(file_path, "r") as f:
messages = [json.loads(m) for m in list(f) if m.strip()]
@@ -40,7 +40,7 @@ def read_from_file(file_path: Path) -> List[Message]:
return [Message(**m) for m in messages]
def list_sorted_session_files(session_files_directory: Path) -> Dict[str, Path]:
def list_sorted_session_files(session_files_directory: Path) -> dict[str, Path]:
logs = list_session_files(session_files_directory)
return {log.stem: log for log in sorted(logs, key=lambda x: x.stat().st_mtime, reverse=True)}
@@ -55,7 +55,7 @@ def session_file_exists(session_files_directory: Path) -> bool:
return any(list_session_files(session_files_directory))
def save_latest_session(file_path: Path, messages: List[Message]) -> None:
def save_latest_session(file_path: Path, messages: list[Message]) -> None:
with tempfile.NamedTemporaryFile("w", delete=False) as temp_file:
_write_messages_to_file(temp_file, messages)
temp_file_path = temp_file.name
@@ -63,7 +63,7 @@ def save_latest_session(file_path: Path, messages: List[Message]) -> None:
os.replace(temp_file_path, file_path)
def _write_messages_to_file(file: any, messages: List[Message]) -> None:
def _write_messages_to_file(file: any, messages: list[Message]) -> None:
for m in messages:
json.dump(m.to_dict(), file)
file.write("\n")