"
- header = f"Relevant files"
+ header = "Relevant files"
delta = 75
# header += " " * delta
pr_body += f""" | {header} |
"""
@@ -690,7 +697,7 @@ def process_pr_files_prediction(self, pr_body, value):
if use_collapsible_file_list:
pr_body += f"""{len(list_tuples)} files"""
else:
- pr_body += f""""""
+ pr_body += """"""
for filename, file_changes_title, file_change_description in list_tuples:
filename = filename.replace("'", "`").rstrip()
filename_publish = filename.split("/")[-1]
diff --git a/pr_agent/tools/pr_generate_labels.py b/pr_agent/tools/pr_generate_labels.py
index dae25d914e..94a4bc32f6 100644
--- a/pr_agent/tools/pr_generate_labels.py
+++ b/pr_agent/tools/pr_generate_labels.py
@@ -1,7 +1,6 @@
import copy
-import re
from functools import partial
-from typing import List, Tuple
+from typing import List
from jinja2 import Environment, StrictUndefined
diff --git a/pr_agent/tools/pr_help_docs.py b/pr_agent/tools/pr_help_docs.py
index aa7002189c..ffcdb72cc0 100644
--- a/pr_agent/tools/pr_help_docs.py
+++ b/pr_agent/tools/pr_help_docs.py
@@ -1,18 +1,18 @@
import copy
-from functools import partial
-
-from jinja2 import Environment, StrictUndefined
import math
import os
import re
+from functools import partial
from tempfile import TemporaryDirectory
+from jinja2 import Environment, StrictUndefined
+
from pr_agent.algo import MAX_TOKENS
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
from pr_agent.algo.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
from pr_agent.algo.pr_processing import retry_with_fallback_models
from pr_agent.algo.token_handler import TokenHandler
-from pr_agent.algo.utils import clip_tokens, get_max_tokens, load_yaml, ModelType
+from pr_agent.algo.utils import ModelType, clip_tokens, get_max_tokens, load_yaml
from pr_agent.config_loader import get_settings
from pr_agent.git_providers import get_git_provider_with_context
from pr_agent.log import get_logger
@@ -113,7 +113,7 @@ def return_document_headings(text: str, ext: str) -> str:
return '\n'.join(headings)
except Exception as e:
- get_logger().exception(f"Unexpected exception thrown. Returning empty result.")
+ get_logger().exception("Unexpected exception thrown. Returning empty result.")
return ""
# Load documentation files to memory: full file path (as will be given as prompt) -> doc contents
@@ -139,7 +139,7 @@ def map_documentation_files_to_contents(base_path: str, doc_files: list[str], ma
get_logger().error("Couldn't find any usable documentation files. Returning empty dict.")
return returned_dict
except Exception as e:
- get_logger().exception(f"Unexpected exception thrown. Returning empty dict.")
+ get_logger().exception("Unexpected exception thrown. Returning empty dict.")
return {}
# Goes over files' contents, generating payload for prompt while decorating them with a header to mark where each file begins,
@@ -163,7 +163,7 @@ def aggregate_documentation_files_for_prompt_contents(file_path_to_contents: dic
docs_prompt += f"\n==file name==\n\n{file_path}\n\n==file content==\n\n{file_contents}\n=========\n\n"
return docs_prompt
except Exception as e:
- get_logger().exception(f"Unexpected exception thrown. Returning empty result.")
+ get_logger().exception("Unexpected exception thrown. Returning empty result.")
return ""
def format_markdown_q_and_a_response(question_str: str, response_str: str, relevant_sections: list[dict[str, str]],
@@ -173,7 +173,7 @@ def format_markdown_q_and_a_response(question_str: str, response_str: str, relev
answer_str = ""
answer_str += f"### Question: \n{question_str}\n\n"
answer_str += f"### Answer:\n{response_str.strip()}\n\n"
- answer_str += f"#### Relevant Sources:\n\n"
+ answer_str += "#### Relevant Sources:\n\n"
for section in relevant_sections:
file = section.get('file_name').lstrip('/').strip() #Remove any '/' in the beginning, since some models do it anyway
ext = [suffix for suffix in supported_suffixes if file.endswith(suffix)]
@@ -188,7 +188,7 @@ def format_markdown_q_and_a_response(question_str: str, response_str: str, relev
answer_str += f"> - {base_url_prefix}/{file}{base_url_suffix}\n"
return answer_str
except Exception as e:
- get_logger().exception(f"Unexpected exception thrown. Returning empty result.")
+ get_logger().exception("Unexpected exception thrown. Returning empty result.")
return ""
def format_markdown_header(header: str) -> str:
@@ -215,7 +215,7 @@ def format_markdown_header(header: str) -> str:
# Perform replacements in a single pass and convert to lowercase
return pattern.sub(lambda m: replacements[m.group()], cleaned).lower()
except Exception:
- get_logger().exception(f"Error while formatting markdown header", artifacts={'header': header})
+ get_logger().exception("Error while formatting markdown header", artifacts={'header': header})
return ""
def clean_markdown_content(content: str) -> str:
@@ -253,7 +253,7 @@ def clean_markdown_content(content: str) -> str:
r'\2', content, flags=re.DOTALL)
return content.strip()
except Exception as e:
- get_logger().exception(f"Unexpected exception thrown. Returning empty result.")
+ get_logger().exception("Unexpected exception thrown. Returning empty result.")
return ""
class PredictionPreparator:
@@ -265,7 +265,7 @@ def __init__(self, ai_handler, vars, system_prompt, user_prompt):
self.system_prompt = environment.from_string(system_prompt).render(variables)
self.user_prompt = environment.from_string(user_prompt).render(variables)
except Exception as e:
- get_logger().exception(f"Caught exception during init. Setting ai_handler to None to prevent __call__.")
+ get_logger().exception("Caught exception during init. Setting ai_handler to None to prevent __call__.")
self.ai_handler = None
#Called by retry_with_fallback_models and therefore, on any failure must throw an exception:
@@ -323,7 +323,7 @@ def __init__(self, ctx_url, ai_handler:partial[BaseAiHandler,] = LiteLLMAIHandle
get_settings().pr_help_docs_prompts.system,
get_settings().pr_help_docs_prompts.user)
except Exception as e:
- get_logger().exception(f"Caught exception during init. Setting self.question to None to prevent run() to do anything.")
+ get_logger().exception("Caught exception during init. Setting self.question to None to prevent run() to do anything.")
self.question = None
async def run(self):
@@ -338,7 +338,7 @@ async def run(self):
#Generate prompt for the AI model. This will be the full text of all the documentation files combined.
docs_prompt = aggregate_documentation_files_for_prompt_contents(docs_filepath_to_contents)
if not docs_filepath_to_contents or not docs_prompt:
- get_logger().warning(f"Could not find any usable documentation. Returning with no result...")
+ get_logger().warning("Could not find any usable documentation. Returning with no result...")
return None
docs_prompt_to_send_to_model = docs_prompt
@@ -376,7 +376,7 @@ async def run(self):
artifacts={'raw_response': response, 'response_str': response_str, 'relevant_sections': relevant_sections})
return
if int(response_yaml.get('question_is_relevant', '1')) == 0:
- get_logger().warning(f"Question is not relevant. Returning without an answer...",
+ get_logger().warning("Question is not relevant. Returning without an answer...",
artifacts={'raw_response': response})
return
@@ -415,7 +415,7 @@ def _find_all_document_files_matching_exts(self, abs_docs_path: str, ignore_read
return matching_files
return matching_files
except Exception as e:
- get_logger().exception(f"Unexpected exception thrown. Returning empty list.")
+ get_logger().exception("Unexpected exception thrown. Returning empty list.")
return []
def _gen_filenames_to_contents_map_from_repo(self) -> dict[str, str]:
@@ -426,7 +426,7 @@ def _gen_filenames_to_contents_map_from_repo(self) -> dict[str, str]:
if not returned_cloned_repo_root:
raise Exception(f"Failed to clone {self.repo_url} to {tmp_dir}")
- get_logger().debug(f"About to gather relevant documentation files...")
+ get_logger().debug("About to gather relevant documentation files...")
doc_files = []
if self.include_root_readme_file:
for root, _, files in os.walk(returned_cloned_repo_root.path):
@@ -451,7 +451,7 @@ def _gen_filenames_to_contents_map_from_repo(self) -> dict[str, str]:
return map_documentation_files_to_contents(returned_cloned_repo_root.path, doc_files)
except Exception as e:
- get_logger().exception(f"Unexpected exception thrown. Returning empty dict.")
+ get_logger().exception("Unexpected exception thrown. Returning empty dict.")
return {}
def _trim_docs_input(self, docs_input: str, max_allowed_txt_input: int, only_return_if_trim_needed=False) -> bool|str:
@@ -488,7 +488,7 @@ def _trim_docs_input(self, docs_input: str, max_allowed_txt_input: int, only_ret
# Unexpected exception. Rethrowing it since:
# 1. This is an internal function.
# 2. An empty str/False result is a valid one - would require now checking also for None.
- get_logger().exception(f"Unexpected exception thrown. Rethrowing it...")
+ get_logger().exception("Unexpected exception thrown. Rethrowing it...")
raise e
async def _rank_docs_and_return_them_as_prompt(self, docs_filepath_to_contents: dict[str, str], max_allowed_txt_input: int) -> str:
@@ -530,7 +530,7 @@ async def _rank_docs_and_return_them_as_prompt(self, docs_filepath_to_contents:
return ""
return docs_prompt_to_send_to_model
except Exception as e:
- get_logger().exception(f"Unexpected exception thrown. Returning empty result.")
+ get_logger().exception("Unexpected exception thrown. Returning empty result.")
return ""
def _format_model_answer(self, response_str: str, relevant_sections: list[dict[str, str]]) -> str:
@@ -545,10 +545,10 @@ def _format_model_answer(self, response_str: str, relevant_sections: list[dict[s
answer_str = modify_answer_section(answer_str)
#In case the response should not be published and returned as string, stop here:
if answer_str and self.return_as_string:
- get_logger().info(f"Chat help docs answer", artifacts={'answer_str': answer_str})
+ get_logger().info("Chat help docs answer", artifacts={'answer_str': answer_str})
return answer_str
if not answer_str:
- get_logger().info(f"No answer found")
+ get_logger().info("No answer found")
return ""
if self.git_provider.is_supported("gfm_markdown") and get_settings().pr_help_docs.enable_help_text:
answer_str += " \n\n 💡 Tool usage guide: \n\n"
@@ -556,5 +556,5 @@ def _format_model_answer(self, response_str: str, relevant_sections: list[dict[s
answer_str += "\n \n"
return answer_str
except Exception as e:
- get_logger().exception(f"Unexpected exception thrown. Returning empty result.")
+ get_logger().exception("Unexpected exception thrown. Returning empty result.")
return ""
diff --git a/pr_agent/tools/pr_help_message.py b/pr_agent/tools/pr_help_message.py
index a83e03e0c7..a33f0eac64 100644
--- a/pr_agent/tools/pr_help_message.py
+++ b/pr_agent/tools/pr_help_message.py
@@ -10,7 +10,7 @@
from pr_agent.algo.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
from pr_agent.algo.pr_processing import retry_with_fallback_models
from pr_agent.algo.token_handler import TokenHandler
-from pr_agent.algo.utils import ModelType, clip_tokens, load_yaml, get_max_tokens
+from pr_agent.algo.utils import ModelType, clip_tokens, get_max_tokens, load_yaml
from pr_agent.config_loader import get_settings
from pr_agent.git_providers import BitbucketServerProvider, GithubProvider, get_git_provider_with_context
from pr_agent.log import get_logger
@@ -89,7 +89,7 @@ def format_markdown_header(self, header: str) -> str:
# Perform replacements in a single pass and convert to lowercase
return pattern.sub(lambda m: replacements[m.group()], cleaned).lower()
except Exception:
- get_logger().exception(f"Error while formatting markdown header", artifacts={'header': header})
+ get_logger().exception("Error while formatting markdown header", artifacts={'header': header})
return ""
@@ -151,7 +151,7 @@ async def run(self):
get_logger().warning(f"failing to parse response: {response_yaml}, publishing the response as is")
if get_settings().config.publish_output:
answer_str = f"### Question: \n{self.question_str}\n\n"
- answer_str += f"### Answer:\n\n"
+ answer_str += "### Answer:\n\n"
answer_str += response_yaml
self.git_provider.publish_comment(answer_str)
return ""
@@ -162,8 +162,8 @@ async def run(self):
get_logger().info(f"Could not find relevant answer for the question: {self.question_str}")
if get_settings().config.publish_output:
answer_str = f"### Question: \n{self.question_str}\n\n"
- answer_str += f"### Answer:\n\n"
- answer_str += f"Could not find relevant information to answer the question. Please provide more details and try again."
+ answer_str += "### Answer:\n\n"
+ answer_str += "Could not find relevant information to answer the question. Please provide more details and try again."
self.git_provider.publish_comment(answer_str)
return ""
@@ -172,7 +172,7 @@ async def run(self):
if response_str:
answer_str += f"### Question: \n{self.question_str}\n\n"
answer_str += f"### Answer:\n{response_str.strip()}\n\n"
- answer_str += f"#### Relevant Sources:\n\n"
+ answer_str += "#### Relevant Sources:\n\n"
base_path = "https://qodo-merge-docs.qodo.ai/"
for section in relevant_sections:
file = section.get('file_name').strip().removesuffix('.md')
@@ -247,21 +247,21 @@ async def run(self):
checkbox_list.append("[*]")
if isinstance(self.git_provider, GithubProvider) and not get_settings().config.get('disable_checkboxes', False):
- pr_comment += f"| Tool | Description | Trigger Interactively :gem: | "
+ pr_comment += "| Tool | Description | Trigger Interactively :gem: | "
for i in range(len(tool_names)):
pr_comment += f"\n| \n\n{tool_names[i]} | \n{descriptions[i]} | \n\n\n{checkbox_list[i]}\n | "
pr_comment += " \n\n"
- pr_comment += f"""\n\n(1) Note that each tool can be [triggered automatically](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#github-app-automatic-tools-when-a-new-pr-is-opened) when a new PR is opened, or called manually by [commenting on a PR](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#online-usage)."""
- pr_comment += f"""\n\n(2) Tools marked with [*] require additional parameters to be passed. For example, to invoke the `/ask` tool, you need to comment on a PR: `/ask ""`. See the relevant documentation for each tool for more details."""
+ pr_comment += """\n\n(1) Note that each tool can be [triggered automatically](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#github-app-automatic-tools-when-a-new-pr-is-opened) when a new PR is opened, or called manually by [commenting on a PR](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#online-usage)."""
+ pr_comment += """\n\n(2) Tools marked with [*] require additional parameters to be passed. For example, to invoke the `/ask` tool, you need to comment on a PR: `/ask ""`. See the relevant documentation for each tool for more details."""
elif isinstance(self.git_provider, BitbucketServerProvider):
# only support basic commands in BBDC
pr_comment = generate_bbdc_table(tool_names[:4], descriptions[:4])
else:
- pr_comment += f"| Tool | Command | Description | "
+ pr_comment += "| Tool | Command | Description | "
for i in range(len(tool_names)):
pr_comment += f"\n| \n\n{tool_names[i]} | {commands[i]} | {descriptions[i]} | "
pr_comment += " \n\n"
- pr_comment += f"""\n\nNote that each tool can be [invoked automatically](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/) when a new PR is opened, or called manually by [commenting on a PR](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#online-usage)."""
+ pr_comment += """\n\nNote that each tool can be [invoked automatically](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/) when a new PR is opened, or called manually by [commenting on a PR](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#online-usage)."""
if get_settings().config.publish_output:
self.git_provider.publish_comment(pr_comment)
diff --git a/pr_agent/tools/pr_line_questions.py b/pr_agent/tools/pr_line_questions.py
index 3ae5d0040b..025c5296ca 100644
--- a/pr_agent/tools/pr_line_questions.py
+++ b/pr_agent/tools/pr_line_questions.py
@@ -1,4 +1,3 @@
-import argparse
import copy
from functools import partial
@@ -6,9 +5,8 @@
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
from pr_agent.algo.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
-from pr_agent.algo.git_patch_processing import (
- decouple_and_convert_to_hunks_with_lines_numbers, extract_hunk_lines_from_patch)
-from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models
+from pr_agent.algo.git_patch_processing import extract_hunk_lines_from_patch
+from pr_agent.algo.pr_processing import retry_with_fallback_models
from pr_agent.algo.token_handler import TokenHandler
from pr_agent.algo.utils import ModelType
from pr_agent.config_loader import get_settings
@@ -16,7 +14,7 @@
from pr_agent.git_providers.git_provider import get_main_pr_language
from pr_agent.git_providers.github_provider import GithubProvider
from pr_agent.log import get_logger
-from pr_agent.servers.help import HelpMessage
+
class PR_LineQuestions:
def __init__(self, pr_url: str, args=None, ai_handler: partial[BaseAiHandler,] = LiteLLMAIHandler):
diff --git a/pr_agent/tools/pr_questions.py b/pr_agent/tools/pr_questions.py
index e03c707a3a..aad8e41e04 100644
--- a/pr_agent/tools/pr_questions.py
+++ b/pr_agent/tools/pr_questions.py
@@ -9,7 +9,7 @@
from pr_agent.algo.token_handler import TokenHandler
from pr_agent.algo.utils import ModelType
from pr_agent.config_loader import get_settings
-from pr_agent.git_providers import get_git_provider, GitLabProvider
+from pr_agent.git_providers import GitLabProvider, get_git_provider
from pr_agent.git_providers.git_provider import get_main_pr_language
from pr_agent.log import get_logger
from pr_agent.servers.help import HelpMessage
@@ -62,12 +62,12 @@ async def run(self):
# identify image
img_path = self.identify_image_in_comment()
if img_path:
- get_logger().debug(f"Image path identified", artifact=img_path)
+ get_logger().debug("Image path identified", artifact=img_path)
await retry_with_fallback_models(self._prepare_prediction, model_type=ModelType.WEAK)
pr_comment = self._prepare_pr_answer()
- get_logger().debug(f"PR output", artifact=pr_comment)
+ get_logger().debug("PR output", artifact=pr_comment)
if self.git_provider.is_supported("gfm_markdown") and get_settings().pr_questions.enable_help_text:
pr_comment += " \n\n 💡 Tool usage guide: \n\n"
@@ -95,10 +95,10 @@ def identify_image_in_comment(self):
async def _prepare_prediction(self, model: str):
self.patches_diff = get_pr_diff(self.git_provider, self.token_handler, model)
if self.patches_diff:
- get_logger().debug(f"PR diff", artifact=self.patches_diff)
+ get_logger().debug("PR diff", artifact=self.patches_diff)
self.prediction = await self._get_prediction(model)
else:
- get_logger().error(f"Error getting PR diff")
+ get_logger().error("Error getting PR diff")
self.prediction = ""
async def _get_prediction(self, model: str):
@@ -136,7 +136,7 @@ def _prepare_pr_answer(self) -> str:
if model_answer_sanitized.startswith("/"):
model_answer_sanitized = " " + model_answer_sanitized
if model_answer_sanitized != model_answer:
- get_logger().debug(f"Sanitized model answer",
+ get_logger().debug("Sanitized model answer",
artifact={"model_answer": model_answer, "sanitized_answer": model_answer_sanitized})
diff --git a/pr_agent/tools/pr_reviewer.py b/pr_agent/tools/pr_reviewer.py
index 3ace41dc16..bfb52afa87 100644
--- a/pr_agent/tools/pr_reviewer.py
+++ b/pr_agent/tools/pr_reviewer.py
@@ -1,7 +1,5 @@
import copy
import datetime
-import traceback
-from collections import OrderedDict
from functools import partial
from typing import List, Tuple
@@ -9,24 +7,24 @@
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
from pr_agent.algo.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
-from pr_agent.algo.pr_processing import (add_ai_metadata_to_diff_files,
- get_pr_diff,
- retry_with_fallback_models)
-from pr_agent.algo.skills_loader import get_skills_context
+from pr_agent.algo.pr_processing import add_ai_metadata_to_diff_files, get_pr_diff, retry_with_fallback_models
from pr_agent.algo.repo_context import build_repo_context
+from pr_agent.algo.skills_loader import get_skills_context
from pr_agent.algo.token_handler import TokenHandler
-from pr_agent.algo.utils import (ModelType, PRReviewHeader,
- convert_to_markdown_v2, github_action_output,
- load_yaml, show_relevant_configurations)
+from pr_agent.algo.utils import (
+ ModelType,
+ PRReviewHeader,
+ convert_to_markdown_v2,
+ github_action_output,
+ load_yaml,
+ show_relevant_configurations,
+)
from pr_agent.config_loader import get_settings
-from pr_agent.git_providers import (get_git_provider,
- get_git_provider_with_context)
-from pr_agent.git_providers.git_provider import (IncrementalPR,
- get_main_pr_language)
+from pr_agent.git_providers import get_git_provider_with_context
+from pr_agent.git_providers.git_provider import IncrementalPR, get_main_pr_language
from pr_agent.log import get_logger
from pr_agent.servers.help import HelpMessage
-from pr_agent.tools.ticket_pr_compliance_check import (
- extract_and_cache_pr_tickets, extract_tickets)
+from pr_agent.tools.ticket_pr_compliance_check import extract_and_cache_pr_tickets
class PRReviewer:
@@ -71,10 +69,10 @@ def __init__(self, pr_url: str, is_answer: bool = False, is_auto: bool = False,
if (self.pr_description_files and get_settings().get("config.is_auto_command", False) and
get_settings().get("config.enable_ai_metadata", False)):
add_ai_metadata_to_diff_files(self.git_provider, self.pr_description_files)
- get_logger().debug(f"AI metadata added to the this command")
+ get_logger().debug("AI metadata added to the this command")
else:
get_settings().set("config.enable_ai_metadata", False)
- get_logger().debug(f"AI metadata is disabled for this command")
+ get_logger().debug("AI metadata is disabled for this command")
self.vars = {
"title": self.git_provider.pr.title,
@@ -169,7 +167,7 @@ async def run(self) -> None:
return None
pr_review = self._prepare_pr_review()
- get_logger().debug(f"PR output", artifact=pr_review)
+ get_logger().debug("PR output", artifact=pr_review)
should_publish = get_settings().config.publish_output and self._should_publish_review_no_suggestions(pr_review)
if not should_publish:
@@ -205,7 +203,7 @@ async def _prepare_prediction(self, model: str) -> None:
disable_extra_lines=False,)
if self.patches_diff:
- get_logger().debug(f"PR diff", diff=self.patches_diff)
+ get_logger().debug("PR diff", diff=self.patches_diff)
self.prediction = await self._get_prediction(model)
else:
get_logger().warning(f"Empty diff for PR: {self.pr_url}")
diff --git a/pr_agent/tools/pr_similar_issue.py b/pr_agent/tools/pr_similar_issue.py
index f48756d793..26273ba212 100644
--- a/pr_agent/tools/pr_similar_issue.py
+++ b/pr_agent/tools/pr_similar_issue.py
@@ -35,9 +35,7 @@ def __init__(self, issue_url: str, ai_handler, args: list = None):
if get_settings().pr_similar_issue.vectordb == "pinecone":
try:
- import pandas as pd
import pinecone
- from pinecone_datasets import Dataset, DatasetMetadata
except:
raise Exception("Please install 'pinecone' and 'pinecone_datasets' to use pinecone as vectordb")
# assuming pinecone api key and environment are set in secrets file
@@ -178,9 +176,7 @@ def __init__(self, issue_url: str, ai_handler, args: list = None):
elif get_settings().pr_similar_issue.vectordb == "qdrant":
try:
import qdrant_client
- from qdrant_client.models import (Distance, FieldCondition,
- Filter, MatchValue,
- PointStruct, VectorParams)
+ from qdrant_client.models import Distance, FieldCondition, Filter, MatchValue, VectorParams
except Exception:
raise Exception("Please install qdrant-client to use qdrant as vectordb")
diff --git a/pr_agent/tools/pr_update_changelog.py b/pr_agent/tools/pr_update_changelog.py
index 28f96128b5..39cca3e3b9 100644
--- a/pr_agent/tools/pr_update_changelog.py
+++ b/pr_agent/tools/pr_update_changelog.py
@@ -12,7 +12,7 @@
from pr_agent.algo.token_handler import TokenHandler
from pr_agent.algo.utils import ModelType, show_relevant_configurations
from pr_agent.config_loader import get_settings
-from pr_agent.git_providers import GithubProvider, get_git_provider
+from pr_agent.git_providers import get_git_provider
from pr_agent.git_providers.git_provider import get_main_pr_language
from pr_agent.log import get_logger
@@ -91,7 +91,7 @@ async def run(self):
if get_settings().get('config', {}).get('output_relevant_configurations', False):
answer += show_relevant_configurations(relevant_section='pr_update_changelog')
- get_logger().debug(f"PR output", artifact=answer)
+ get_logger().debug("PR output", artifact=answer)
if get_settings().config.publish_output:
self.git_provider.remove_initial_comment()
@@ -109,10 +109,10 @@ async def run(self):
async def _prepare_prediction(self, model: str):
self.patches_diff = get_pr_diff(self.git_provider, self.token_handler, model)
if self.patches_diff:
- get_logger().debug(f"PR diff", artifact=self.patches_diff)
+ get_logger().debug("PR diff", artifact=self.patches_diff)
self.prediction = await self._get_prediction(model)
else:
- get_logger().error(f"Error getting PR diff")
+ get_logger().error("Error getting PR diff")
self.prediction = ""
async def _get_prediction(self, model: str):
diff --git a/pr_agent/tools/ticket_pr_compliance_check.py b/pr_agent/tools/ticket_pr_compliance_check.py
index f2ae1dca78..12160cbde3 100644
--- a/pr_agent/tools/ticket_pr_compliance_check.py
+++ b/pr_agent/tools/ticket_pr_compliance_check.py
@@ -2,8 +2,7 @@
import traceback
from pr_agent.config_loader import get_settings
-from pr_agent.git_providers import GithubProvider
-from pr_agent.git_providers import AzureDevopsProvider
+from pr_agent.git_providers import AzureDevopsProvider, GithubProvider
from pr_agent.log import get_logger
# Compile the regex pattern once, outside the function
diff --git a/pyproject.toml b/pyproject.toml
index fdfec8acfb..77954e8d9c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -112,7 +112,7 @@ dev = [
"pytest-cov==7.0.0",
"httpx==0.28.1",
"pre-commit>=4,<5",
- "flake8==7.3.0",
+ "ruff==0.15.20",
]
[tool.ruff]
@@ -126,9 +126,10 @@ lint.select = [
"I002", # isort missing-required-import
]
-# First commit - only fixing isort
lint.fixable = [
- "I001", # isort basic checks
+ "I001", # import sorting
+ "F401", # unused imports
+ "F541", # f-string without placeholders
]
lint.unfixable = [
@@ -137,12 +138,39 @@ lint.unfixable = [
lint.exclude = ["api/code_completions"]
-lint.ignore = ["E999", "B008"]
+# B008 is a long-standing ignore. Everything below it is a pre-existing
+# violation deferred so ruff runs green on adoption — fix the code and drop
+# the entry. TODO: burn this list down.
+lint.ignore = [
+ "B008", # function call in argument defaults
+ "E402", # import not at top of file
+ "E501", # line too long
+ "E711", # comparison to None
+ "E713", # test for membership with `not ... in`
+ "E714", # test for object identity with `not ... is`
+ "E721", # type comparison with `==`
+ "E722", # bare `except`
+ "F811", # redefinition of unused name
+ "F841", # unused local variable
+ "B005", # strip() with multi-character string
+ "B006", # mutable argument default
+ "B007", # unused loop control variable
+ "B009", # getattr with constant attribute
+ "B011", # `assert False`
+ "B012", # break/continue/return inside finally
+ "B017", # assertRaises(Exception)
+ "B023", # function definition uses loop variable
+ "B027", # empty method without @abstractmethod
+ "B904", # raise without `from` inside `except`
+ "B905", # zip() without explicit strict=
+]
[tool.ruff.lint.per-file-ignores]
-"__init__.py" = [
- "E402",
-] # Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`.
+# Ignore import-position and unused-import in __init__.py (re-export modules).
+"__init__.py" = ["E402", "F401"]
+# The 'similar issue' tool imports its optional deps (pinecone, pandas,
+# pinecone-datasets) lazily at runtime; ruff can't see them at module scope.
+"pr_agent/tools/pr_similar_issue.py" = ["F821"]
[tool.bandit]
exclude_dirs = ["tests"]
diff --git a/tests/e2e_tests/langchain_ai_handler.py b/tests/e2e_tests/langchain_ai_handler.py
index 38b73ad08c..ddf9e61a78 100644
--- a/tests/e2e_tests/langchain_ai_handler.py
+++ b/tests/e2e_tests/langchain_ai_handler.py
@@ -43,7 +43,7 @@ async def measure_performance(handler, num_requests=3):
total_time = end_time - start_time
avg_time = total_time / num_requests
- print(f'Performance results:')
+ print('Performance results:')
print(f'Total time: {total_time:.2f} seconds')
print(f'Average time per request: {avg_time:.2f} seconds')
print(f'Requests per second: {num_requests/total_time:.2f}')
diff --git a/tests/e2e_tests/test_bitbucket_app.py b/tests/e2e_tests/test_bitbucket_app.py
index 7a070e7d4f..26b5fa3f50 100644
--- a/tests/e2e_tests/test_bitbucket_app.py
+++ b/tests/e2e_tests/test_bitbucket_app.py
@@ -1,10 +1,7 @@
-import hashlib
import os
-import re
import time
from datetime import datetime
-import jwt
import requests
from atlassian.bitbucket import Cloud
from requests.auth import HTTPBasicAuth
@@ -13,11 +10,8 @@
from pr_agent.log import get_logger, setup_logger
from tests.e2e_tests.e2e_utils import (
FILE_PATH,
- IMPROVE_START_WITH_REGEX_PATTERN,
NEW_FILE_CONTENT,
NUM_MINUTES,
- PR_HEADER_START_WITH,
- REVIEW_START_WITH,
)
log_level = os.environ.get("LOG_LEVEL", "INFO")
@@ -66,7 +60,7 @@ def test_e2e_run_bitbucket_app():
# check every 1 minute, for 5 minutes if the PR has all the tool results
for i in range(NUM_MINUTES):
- logger.info(f"Waiting for the PR to get all the tool results...")
+ logger.info("Waiting for the PR to get all the tool results...")
time.sleep(60)
comments = list(pr.comments())
comments_raw = [c.raw for c in comments]
@@ -79,7 +73,7 @@ def test_e2e_run_bitbucket_app():
if valid_review:
break
else:
- logger.error(f"REVIEW feedback is invalid")
+ logger.error("REVIEW feedback is invalid")
raise Exception("REVIEW feedback is invalid")
else:
logger.info(f"Waiting for the PR to get all the tool results. {i + 1} minute(s) passed")
@@ -91,7 +85,7 @@ def test_e2e_run_bitbucket_app():
repo.branches.delete(new_branch)
# If we reach here, the test is successful
- logger.info(f"Succeeded in running e2e test for Bitbucket app on the PR")
+ logger.info("Succeeded in running e2e test for Bitbucket app on the PR")
except Exception as e:
logger.error(f"Failed to run e2e test for Bitbucket app: {e}")
# delete the branch
diff --git a/tests/e2e_tests/test_gitea_app.py b/tests/e2e_tests/test_gitea_app.py
index e2c83ed284..5956dbc0ff 100644
--- a/tests/e2e_tests/test_gitea_app.py
+++ b/tests/e2e_tests/test_gitea_app.py
@@ -8,11 +8,8 @@
from pr_agent.log import get_logger, setup_logger
from tests.e2e_tests.e2e_utils import (
FILE_PATH,
- IMPROVE_START_WITH_REGEX_PATTERN,
NEW_FILE_CONTENT,
NUM_MINUTES,
- PR_HEADER_START_WITH,
- REVIEW_START_WITH,
)
log_level = os.environ.get("LOG_LEVEL", "INFO")
@@ -121,7 +118,7 @@ def test_e2e_run_gitea_app():
pr_number = pr['number']
for i in range(NUM_MINUTES):
- logger.info(f"Waiting for the PR to get all the tool results...")
+ logger.info("Waiting for the PR to get all the tool results...")
time.sleep(60)
response = requests.get(
@@ -163,7 +160,7 @@ def test_e2e_run_gitea_app():
)
response.raise_for_status()
- logger.info(f"Succeeded in running e2e test for Gitea app on the PR")
+ logger.info("Succeeded in running e2e test for Gitea app on the PR")
except Exception as e:
logger.error(f"Failed to run e2e test for Gitea app: {e}")
raise
diff --git a/tests/e2e_tests/test_github_app.py b/tests/e2e_tests/test_github_app.py
index 17e08627c1..691456b74f 100644
--- a/tests/e2e_tests/test_github_app.py
+++ b/tests/e2e_tests/test_github_app.py
@@ -67,7 +67,7 @@ def test_e2e_run_github_app():
# check every 1 minute, for 5, minutes if the PR has all the tool results
for i in range(NUM_MINUTES):
- logger.info(f"Waiting for the PR to get all the tool results...")
+ logger.info("Waiting for the PR to get all the tool results...")
time.sleep(60)
logger.info(f"Checking the PR {pr.html_url} after {i + 1} minute(s)")
pr.update()
diff --git a/tests/e2e_tests/test_gitlab_webhook.py b/tests/e2e_tests/test_gitlab_webhook.py
index e304660e38..d775b6cbde 100644
--- a/tests/e2e_tests/test_gitlab_webhook.py
+++ b/tests/e2e_tests/test_gitlab_webhook.py
@@ -6,7 +6,6 @@
import gitlab
from pr_agent.config_loader import get_settings
-from pr_agent.git_providers import get_git_provider
from pr_agent.log import get_logger, setup_logger
from tests.e2e_tests.e2e_utils import (
FILE_PATH,
@@ -59,7 +58,7 @@ def test_e2e_run_github_app():
# check every 1 minute, for 5, minutes if the PR has all the tool results
for i in range(NUM_MINUTES):
- logger.info(f"Waiting for the MR to get all the tool results...")
+ logger.info("Waiting for the MR to get all the tool results...")
time.sleep(60)
logger.info(f"Checking the MR {mr.web_url} after {i + 1} minute(s)")
mr = project.mergerequests.get(mr.iid)
diff --git a/tests/health_test/main.py b/tests/health_test/main.py
index 11279f49b6..ed753bf576 100644
--- a/tests/health_test/main.py
+++ b/tests/health_test/main.py
@@ -1,16 +1,12 @@
-import argparse
import asyncio
import copy
import os
-from pathlib import Path
from starlette_context import context, request_cycle_context
-from pr_agent.agent.pr_agent import PRAgent, commands
-from pr_agent.cli import run_command
+from pr_agent.agent.pr_agent import PRAgent
from pr_agent.config_loader import get_settings, global_settings
from pr_agent.log import get_logger, setup_logger
-from tests.e2e_tests import e2e_utils
log_level = os.environ.get("LOG_LEVEL", "INFO")
setup_logger(log_level)
@@ -26,7 +22,7 @@ async def run_async() -> None:
agent = PRAgent()
try:
# Run the 'describe' command
- get_logger().info(f"\nSanity check for the 'describe' command...")
+ get_logger().info("\nSanity check for the 'describe' command...")
original_settings = copy.deepcopy(get_settings())
await agent.handle_request(pr_url, ['describe'])
pr_header_body = dict(get_settings().data)['artifact']
@@ -37,7 +33,7 @@ async def run_async() -> None:
get_logger().info("PR description generated successfully\n")
# Run the 'review' command
- get_logger().info(f"\nSanity check for the 'review' command...")
+ get_logger().info("\nSanity check for the 'review' command...")
original_settings = copy.deepcopy(get_settings())
await agent.handle_request(pr_url, ['review'])
pr_review_body = dict(get_settings().data)['artifact']
@@ -48,7 +44,7 @@ async def run_async() -> None:
get_logger().info("PR review generated successfully\n")
# Run the 'improve' command
- get_logger().info(f"\nSanity check for the 'improve' command...")
+ get_logger().info("\nSanity check for the 'improve' command...")
original_settings = copy.deepcopy(get_settings())
await agent.handle_request(pr_url, ['improve'])
pr_improve_body = dict(get_settings().data)['artifact']
@@ -58,10 +54,10 @@ async def run_async() -> None:
context['settings'] = copy.deepcopy(original_settings) # Restore settings state after each test to prevent test interference
get_logger().info("PR improvements generated successfully\n")
- get_logger().info(f"\n\n========\nHealth test passed successfully\n========")
+ get_logger().info("\n\n========\nHealth test passed successfully\n========")
except Exception as e:
- get_logger().exception(f"\n\n========\nHealth test failed\n========")
+ get_logger().exception("\n\n========\nHealth test failed\n========")
raise e
diff --git a/tests/unittest/test_aws_secrets_manager_provider.py b/tests/unittest/test_aws_secrets_manager_provider.py
index 4ca33b8b01..8eca25af78 100644
--- a/tests/unittest/test_aws_secrets_manager_provider.py
+++ b/tests/unittest/test_aws_secrets_manager_provider.py
@@ -2,7 +2,6 @@
from unittest.mock import MagicMock, patch
import pytest
-from botocore.exceptions import ClientError
from pr_agent.secret_providers.aws_secrets_manager_provider import AWSSecretsManagerProvider
diff --git a/tests/unittest/test_clip_tokens.py b/tests/unittest/test_clip_tokens.py
index 45b264cdba..d469871efe 100644
--- a/tests/unittest/test_clip_tokens.py
+++ b/tests/unittest/test_clip_tokens.py
@@ -1,7 +1,5 @@
from unittest.mock import MagicMock, patch
-import pytest
-
from pr_agent.algo.token_handler import TokenEncoder
from pr_agent.algo.utils import clip_tokens
diff --git a/tests/unittest/test_codecommit_provider.py b/tests/unittest/test_codecommit_provider.py
index c4f021256f..ab823a7b1c 100644
--- a/tests/unittest/test_codecommit_provider.py
+++ b/tests/unittest/test_codecommit_provider.py
@@ -2,7 +2,7 @@
import pytest
-from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
+from pr_agent.algo.types import EDIT_TYPE
from pr_agent.git_providers.codecommit_provider import CodeCommitFile, CodeCommitProvider, PullRequestCCMimic
diff --git a/tests/unittest/test_extract_issue_from_branch.py b/tests/unittest/test_extract_issue_from_branch.py
index e8a240448c..064aeb6aa4 100644
--- a/tests/unittest/test_extract_issue_from_branch.py
+++ b/tests/unittest/test_extract_issue_from_branch.py
@@ -1,4 +1,3 @@
-import pytest
from pr_agent.tools.ticket_pr_compliance_check import (
extract_ticket_links_from_branch_name,
diff --git a/tests/unittest/test_gitlab_provider.py b/tests/unittest/test_gitlab_provider.py
index dc61a49525..4612dfa7fd 100644
--- a/tests/unittest/test_gitlab_provider.py
+++ b/tests/unittest/test_gitlab_provider.py
@@ -1,9 +1,8 @@
from unittest.mock import MagicMock, patch
import pytest
-from gitlab import Gitlab
from gitlab.exceptions import GitlabGetError
-from gitlab.v4.objects import Project, ProjectFile
+from gitlab.v4.objects import ProjectFile
from pr_agent.git_providers.gitlab_provider import GitLabProvider
diff --git a/tests/unittest/test_handle_patch_deletions.py b/tests/unittest/test_handle_patch_deletions.py
index c216f189d7..ec88baa713 100644
--- a/tests/unittest/test_handle_patch_deletions.py
+++ b/tests/unittest/test_handle_patch_deletions.py
@@ -1,8 +1,6 @@
# Generated by CodiumAI
-import logging
from pr_agent.algo.git_patch_processing import handle_patch_deletions
-from pr_agent.config_loader import get_settings
"""
Code Analysis
diff --git a/tests/unittest/test_litellm_reasoning_effort.py b/tests/unittest/test_litellm_reasoning_effort.py
index 3c4905ef01..031c51ed74 100644
--- a/tests/unittest/test_litellm_reasoning_effort.py
+++ b/tests/unittest/test_litellm_reasoning_effort.py
@@ -1,4 +1,4 @@
-from unittest.mock import AsyncMock, MagicMock, call, patch
+from unittest.mock import AsyncMock, MagicMock, patch
import pytest
diff --git a/tests/unittest/test_mosaico_a2a_roundtrip.py b/tests/unittest/test_mosaico_a2a_roundtrip.py
index 5fd0a6a361..4ac85b230c 100644
--- a/tests/unittest/test_mosaico_a2a_roundtrip.py
+++ b/tests/unittest/test_mosaico_a2a_roundtrip.py
@@ -22,14 +22,13 @@
custom_merge_loader circular import (mirrors server.py)."""
import os
-import pr_agent.config_loader # noqa: F401 (import-order load; see module docstring)
-
import httpx
import pytest
+from a2a.types import Message, Part, Role, SendMessageRequest
from google.protobuf.json_format import MessageToDict
from httpx import ASGITransport
-from a2a.types import Message, Part, Role, SendMessageRequest
+import pr_agent.config_loader # noqa: F401 (import-order load; see module docstring)
# A small, valid unified diff wrapped in a ```diff fence -> the supplied-diff (path b)
# of the router: no PR URL, no network, parsed by the mosaico_diff provider.
diff --git a/tests/unittest/test_mosaico_card.py b/tests/unittest/test_mosaico_card.py
index 4507c961ec..656b17ce82 100644
--- a/tests/unittest/test_mosaico_card.py
+++ b/tests/unittest/test_mosaico_card.py
@@ -4,8 +4,7 @@
from google.protobuf.json_format import MessageToDict
-from pr_agent.mosaico.card import (OBSERVABILITY_EXTENSION_URI,
- build_agent_card)
+from pr_agent.mosaico.card import OBSERVABILITY_EXTENSION_URI, build_agent_card
_REGISTRATION_JSON = os.path.join(
os.path.dirname(__file__), "..", "..", "docker", "mosaico",
diff --git a/tests/unittest/test_mosaico_env_bridge.py b/tests/unittest/test_mosaico_env_bridge.py
index 8fd1263d6f..2e00899bee 100644
--- a/tests/unittest/test_mosaico_env_bridge.py
+++ b/tests/unittest/test_mosaico_env_bridge.py
@@ -9,10 +9,8 @@
import pytest
from pr_agent.config_loader import get_settings, global_settings
-from pr_agent.mosaico.env_bridge import (apply_mosaico_env,
- langfuse_env_present)
-from pr_agent.mosaico.observability import (mosaico_log_context,
- parse_observability_metadata)
+from pr_agent.mosaico.env_bridge import apply_mosaico_env, langfuse_env_present
+from pr_agent.mosaico.observability import mosaico_log_context, parse_observability_metadata
_SNAPSHOT_KEYS = [
"OPENAI.API_BASE",
diff --git a/tests/unittest/test_mosaico_metadata.py b/tests/unittest/test_mosaico_metadata.py
index 4a9c2a2d64..9edcdcf5ff 100644
--- a/tests/unittest/test_mosaico_metadata.py
+++ b/tests/unittest/test_mosaico_metadata.py
@@ -11,9 +11,7 @@
import pytest
-from pr_agent.mosaico.observability import (langfuse_span,
- mosaico_log_context,
- parse_observability_metadata)
+from pr_agent.mosaico.observability import langfuse_span, mosaico_log_context, parse_observability_metadata
class TestParseObservabilityMetadata:
diff --git a/tests/unittest/test_mosaico_provider.py b/tests/unittest/test_mosaico_provider.py
index 5db9ac9664..11760bd27f 100644
--- a/tests/unittest/test_mosaico_provider.py
+++ b/tests/unittest/test_mosaico_provider.py
@@ -252,8 +252,8 @@ def isolated_settings():
async def _run_tool_via_spy(monkeypatch, isolated_settings, verb, canned_yaml, second_yaml=""):
"""Patch the provider registry to a spy subclass, mock the LLM, run the tool with
publish_output=False, and return (captured_artifact, incremental_accessed_list)."""
- from pr_agent.git_providers import _GIT_PROVIDERS
import pr_agent.algo.ai_handlers.litellm_ai_handler as litellm_mod
+ from pr_agent.git_providers import _GIT_PROVIDERS
files = parse_unified_diff(TWO_FILE_DIFF)
provider_input = {"files": files, "languages": {"Python": 100}, "title": "spike PR"}
diff --git a/tests/unittest/test_mosaico_router.py b/tests/unittest/test_mosaico_router.py
index b194a2526e..ded6ba36b1 100644
--- a/tests/unittest/test_mosaico_router.py
+++ b/tests/unittest/test_mosaico_router.py
@@ -5,15 +5,18 @@
with no exception escaping.
asyncio_mode=auto."""
-import pytest
-
import aiohttp
+import pytest
from pr_agent.config_loader import global_settings
from pr_agent.mosaico import dispatch
-from pr_agent.mosaico.dispatch import (_detect_verb, _empty_fallback,
- _error_fallback, route_and_run,
- route_and_run_result, RouteResult)
+from pr_agent.mosaico.dispatch import (
+ _detect_verb,
+ _empty_fallback,
+ _error_fallback,
+ route_and_run,
+ route_and_run_result,
+)
PR_URL = "https://github.com/org/repo/pull/123"
diff --git a/tests/unittest/test_mosaico_smoke.py b/tests/unittest/test_mosaico_smoke.py
index ae9392b876..6b888fc8b8 100644
--- a/tests/unittest/test_mosaico_smoke.py
+++ b/tests/unittest/test_mosaico_smoke.py
@@ -13,11 +13,11 @@
provider->render chain works through the wire."""
import uuid
+from a2a.types import Message, Part, Role, SendMessageRequest
from google.protobuf.json_format import MessageToDict
from starlette.testclient import TestClient
import pr_agent.algo.ai_handlers.litellm_ai_handler as litellm_mod
-from a2a.types import Message, Part, Role, SendMessageRequest
from pr_agent.mosaico.server import build_app
REVIEW_DIFF = """```diff
diff --git a/tests/unittest/test_progress_comment.py b/tests/unittest/test_progress_comment.py
index eb21224c21..e4be03a337 100644
--- a/tests/unittest/test_progress_comment.py
+++ b/tests/unittest/test_progress_comment.py
@@ -1,10 +1,12 @@
from unittest.mock import MagicMock, patch
-from pr_agent.tools.progress_comment import (DEFAULT_PROGRESS_GIF_URL,
- DEFAULT_PROGRESS_GIF_WIDTH,
- build_progress_comment,
- get_progress_gif_url,
- get_progress_gif_width)
+from pr_agent.tools.progress_comment import (
+ DEFAULT_PROGRESS_GIF_URL,
+ DEFAULT_PROGRESS_GIF_WIDTH,
+ build_progress_comment,
+ get_progress_gif_url,
+ get_progress_gif_width,
+)
def _mock_settings(mock_get_settings, values):
diff --git a/tests/unittest/test_skills_loader.py b/tests/unittest/test_skills_loader.py
index 6aa9d492d3..cd74a6715a 100644
--- a/tests/unittest/test_skills_loader.py
+++ b/tests/unittest/test_skills_loader.py
@@ -5,10 +5,13 @@
from jinja2 import Environment, StrictUndefined
-from pr_agent.algo.skills_loader import (Skill, _parse_skill_file,
- discover_skills,
- format_skills_context,
- get_skills_context)
+from pr_agent.algo.skills_loader import (
+ Skill,
+ _parse_skill_file,
+ discover_skills,
+ format_skills_context,
+ get_skills_context,
+)
def _write_skill(directory: Path, name: str, body: str = "Body content."):
diff --git a/uv.lock b/uv.lock
index aa597a197c..e39dc2570e 100644
--- a/uv.lock
+++ b/uv.lock
@@ -762,20 +762,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/4a/e3/f1fae3647d170919c2cf2a898e77e7d1a4e5c7cae0aed7bb4bd3f5ebff6f/filelock-3.29.5-py3-none-any.whl", hash = "sha256:8af830889ba3a0ffcefbd6c7d2af8a54012058103771f2e10848222f476a1693", size = 45073, upload-time = "2026-07-03T03:50:30.445Z" },
]
-[[package]]
-name = "flake8"
-version = "7.3.0"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "mccabe" },
- { name = "pycodestyle" },
- { name = "pyflakes" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/9b/af/fbfe3c4b5a657d79e5c47a2827a362f9e1b763336a52f926126aa6dc7123/flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872", size = 48326, upload-time = "2025-06-20T19:31:35.838Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", size = 57922, upload-time = "2025-06-20T19:31:34.425Z" },
-]
-
[[package]]
name = "frozenlist"
version = "1.8.0"
@@ -1963,15 +1949,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
]
-[[package]]
-name = "mccabe"
-version = "0.7.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" },
-]
-
[[package]]
name = "msal"
version = "1.37.0"
@@ -2577,12 +2554,12 @@ similar-issue = [
[package.dev-dependencies]
dev = [
- { name = "flake8" },
{ name = "httpx" },
{ name = "pre-commit" },
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "pytest-cov" },
+ { name = "ruff" },
]
[package.metadata]
@@ -2635,12 +2612,12 @@ provides-extras = ["lambda", "similar-issue", "langchain"]
[package.metadata.requires-dev]
dev = [
- { name = "flake8", specifier = "==7.3.0" },
{ name = "httpx", specifier = "==0.28.1" },
{ name = "pre-commit", specifier = ">=4,<5" },
{ name = "pytest", specifier = "==9.0.2" },
{ name = "pytest-asyncio", specifier = ">=1.3.0" },
{ name = "pytest-cov", specifier = "==7.0.0" },
+ { name = "ruff", specifier = "==0.15.20" },
]
[[package]]
@@ -2853,15 +2830,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" },
]
-[[package]]
-name = "pycodestyle"
-version = "2.14.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/11/e0/abfd2a0d2efe47670df87f3e3a0e2edda42f055053c85361f19c0e2c1ca8/pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783", size = 39472, upload-time = "2025-06-20T18:49:48.75Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d", size = 31594, upload-time = "2025-06-20T18:49:47.491Z" },
-]
-
[[package]]
name = "pycparser"
version = "3.0"
@@ -2961,15 +2929,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/17/eb/9d89ad2d9b0ba8cd65393d434471621b98912abb10fbe1df08e480ba57b5/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd35aa21299def8db7ef4fe5c4ff862941a9a158ca7b63d61e66fe67d30416b4", size = 2137657, upload-time = "2026-04-20T14:42:45.149Z" },
]
-[[package]]
-name = "pyflakes"
-version = "3.4.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/45/dc/fd034dc20b4b264b3d015808458391acbf9df40b1e54750ef175d39180b1/pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58", size = 64669, upload-time = "2025-06-20T18:45:27.834Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f", size = 63551, upload-time = "2025-06-20T18:45:26.937Z" },
-]
-
[[package]]
name = "pygithub"
version = "1.59.1"
@@ -3474,6 +3433,31 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/8c/97/d855d6b3c322d1f27e26f5241c42016b56cf01377ea8ed348285f54652f0/rpds_py-2026.6.3-cp315-cp315t-win_amd64.whl", hash = "sha256:ae3d4fe8c0b9213624fdce7279d70e3b148b682ca20719ebd193a23ebfa47324", size = 220719, upload-time = "2026-06-30T07:17:31.788Z" },
]
+[[package]]
+name = "ruff"
+version = "0.15.20"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/43/dc/35b341fc554ba02f217fc10da57d1a75168cfbcf75b0ef2202176d4c4f2d/ruff-0.15.20.tar.gz", hash = "sha256:1416eb04349192646b54de98f146c4f59afe37d0decfc02c3cbbf396f3a28566", size = 4755489, upload-time = "2026-06-25T17:20:37.578Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/94/d9/2d5014f0253ba541d2061d9fa7193f48e941c8b21bb88a7ff9bbe0bd0596/ruff-0.15.20-py3-none-linux_armv6l.whl", hash = "sha256:00e188c53e499c3c1637f73c91dcf2fb56d576cab76ce1be50a27c4e80e37078", size = 10839665, upload-time = "2026-06-25T17:19:44.702Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/d3/ac1798ba64f670698867fcfc591d50e7e421bef137db564858f619a30fcf/ruff-0.15.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9ebd1fd9b9c95fc0bd7b2761aebec1f030013d2e193a2901b224af68fe47251b", size = 11208649, upload-time = "2026-06-25T17:19:48.787Z" },
+ { url = "https://files.pythonhosted.org/packages/47/47/d3ac899991202095dfcf3d5176be4272642be3cf981a2f1a30f72a2afb95/ruff-0.15.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5b16cdd67ca108185cd36dce98c576350c03b1660a751de725fb049193a0632", size = 10622638, upload-time = "2026-06-25T17:19:51.354Z" },
+ { url = "https://files.pythonhosted.org/packages/33/13/4e043fe30aa94d4ff5213a9881fc296d12960f5971b234a5263fdc225312/ruff-0.15.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3413bb3c3d2ca6a8208f1f4809cd2dca3c6de6d0b491c0e70847672bde6e6efd", size = 10984227, upload-time = "2026-06-25T17:19:54.044Z" },
+ { url = "https://files.pythonhosted.org/packages/76/e6/92e7bf40388bc5800073b96564f56264f7e48bfd1a498f5ced6ae6d5a769/ruff-0.15.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd7ec42b3bb3da066488db093308a69c4ac5ee6d2af333a86ba6e2eb2e7dd44b", size = 10622882, upload-time = "2026-06-25T17:19:57.037Z" },
+ { url = "https://files.pythonhosted.org/packages/13/7a/43460be3f24495a3aa46d4b16873e2c4941b3b5f0b00cf88c03b7b94b339/ruff-0.15.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1a36ad0eb77fba9aabfb69ede54de6f376d04ac18ebea022847046d340a8267", size = 11474808, upload-time = "2026-06-25T17:20:00.357Z" },
+ { url = "https://files.pythonhosted.org/packages/27/a0/f37077884873221c6b33b4ab49eb18f9f88e54a16a25a5bca59bef46dd66/ruff-0.15.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6df3b1e4610432f0386dba04d853b5f08cbbc903410c6fcc02f620f05aff53c", size = 12293094, upload-time = "2026-06-25T17:20:03.446Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/74/165545b60256a9704c21ac0ec4a0d07933b320812f9584836c9f4aca4292/ruff-0.15.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e89f198a1ea6ef0d727c1cf16088bc91a6cb0ab947dedc966715691647186eae", size = 11526176, upload-time = "2026-06-25T17:20:06.301Z" },
+ { url = "https://files.pythonhosted.org/packages/86/b1/a976a136d40ade83ce743578399865f57001003a409acadc0ecbb3051082/ruff-0.15.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309809086c2acb67624950a3c8133e80f32d0d3e27106c0cd60ff26657c9f24b", size = 11520767, upload-time = "2026-06-25T17:20:09.191Z" },
+ { url = "https://files.pythonhosted.org/packages/19/0f/f032696cb01c9b54c0263fa393474d7758f1cdc021a01b04e3cbc2500999/ruff-0.15.20-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2d2374caa2f2c2f9e2b7da0a50802cfb8b79f55a9b5e49379f564544fbf56487", size = 11500132, upload-time = "2026-06-25T17:20:13.602Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/f4/51b1a14bc69e8c224b15dab9cce8e99b425e0455d462caa2b3c9be2b6a8e/ruff-0.15.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a1ed17b65293e0c2f22fc387bc13198a5de94bf4429589b0ff6946b0feaf21a3", size = 10943828, upload-time = "2026-06-25T17:20:16.635Z" },
+ { url = "https://files.pythonhosted.org/packages/71/4b/fe267640783cd02bf6c5cc290b1df1051be2ec294c678b5c15fe19e52343/ruff-0.15.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f701305e66b38ea6c91882490eb73459796808e4c6362a1b765255e0cdcd4053", size = 10645418, upload-time = "2026-06-25T17:20:19.4Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/c0/a65aa4ec2f5e87a1df32dc3ec1fede434fe3dfd5cbcf3b503cafc676ab54/ruff-0.15.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b9c0c367ad8e5d0d5b5b8537864c469a0a0e55417aadfbeca41fa61333be9f4", size = 11211770, upload-time = "2026-06-25T17:20:22.033Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/a4/0caa331d954ae2723d729d351c989cb4ca8b6077d5c6c2cb6de75e98c041/ruff-0.15.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:01cc00dd58f0df339d0e902219dd53990ea99996a0344e5d9cc8d45d5307e460", size = 11618698, upload-time = "2026-06-25T17:20:25.259Z" },
+ { url = "https://files.pythonhosted.org/packages/10/9b/5f14927848d2fd4aa891fd88d883788c5a7baba561c7874732364045708c/ruff-0.15.20-py3-none-win32.whl", hash = "sha256:ed65ef510e43a137207e0f01cfcf998aeddb1aeeda5c9d35023e910284d7cf21", size = 10857322, upload-time = "2026-06-25T17:20:28.612Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/f0/fe47c501f9dea92a26d788ff98bb5d92ed4cb4c88792c5c88af6b697dc8e/ruff-0.15.20-py3-none-win_amd64.whl", hash = "sha256:a525c81c70fb0380344dd1d8745d8cc1c890b7fc94a58d5a07bd8eb9557b8415", size = 11993274, upload-time = "2026-06-25T17:20:31.871Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/2b/9555445e1201d92b3195f45cdb153a0b68f24e0a4273f6e3d5ab46e212bb/ruff-0.15.20-py3-none-win_arm64.whl", hash = "sha256:2f5b2a6d614e8700388806a14996c40fab2c47b819ef57d790a34878858ed9ca", size = 11343498, upload-time = "2026-06-25T17:20:35.03Z" },
+]
+
[[package]]
name = "s3fs"
version = "2025.12.0"
| | |