Compare commits

..

1 Commits

Author SHA1 Message Date
Tal
5430e9ab5a Update setup.py 2024-11-06 21:46:31 +02:00
128 changed files with 439 additions and 619 deletions

View File

@ -37,3 +37,5 @@ jobs:
name: Test dev docker name: Test dev docker
run: | run: |
docker run --rm codiumai/pr-agent:test pytest -v tests/unittest docker run --rm codiumai/pr-agent:test pytest -v tests/unittest

View File

@ -30,3 +30,6 @@ jobs:
GITHUB_ACTION_CONFIG.AUTO_DESCRIBE: true GITHUB_ACTION_CONFIG.AUTO_DESCRIBE: true
GITHUB_ACTION_CONFIG.AUTO_REVIEW: true GITHUB_ACTION_CONFIG.AUTO_REVIEW: true
GITHUB_ACTION_CONFIG.AUTO_IMPROVE: true GITHUB_ACTION_CONFIG.AUTO_IMPROVE: true

View File

@ -1,17 +0,0 @@
# disabled. We might run it manually if needed.
name: pre-commit
on:
workflow_dispatch:
# pull_request:
# push:
# branches: [main]
jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v5
# SEE https://github.com/pre-commit/action
- uses: pre-commit/action@v3.0.1

View File

@ -1,46 +0,0 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
default_language_version:
python: python3
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: check-added-large-files
- id: check-toml
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
# - repo: https://github.com/rhysd/actionlint
# rev: v1.7.3
# hooks:
# - id: actionlint
- repo: https://github.com/pycqa/isort
# rev must match what's in dev-requirements.txt
rev: 5.13.2
hooks:
- id: isort
# - repo: https://github.com/PyCQA/bandit
# rev: 1.7.10
# hooks:
# - id: bandit
# args: [
# "-c", "pyproject.toml",
# ]
# - repo: https://github.com/astral-sh/ruff-pre-commit
# rev: v0.7.1
# hooks:
# - id: ruff
# args:
# - --fix
# - id: ruff-format
# - repo: https://github.com/PyCQA/autoflake
# rev: v2.3.1
# hooks:
# - id: autoflake
# args:
# - --in-place
# - --remove-all-unused-imports
# - --remove-unused-variables

View File

@ -43,26 +43,6 @@ Qode Merge PR-Agent aims to help efficiently review and handle pull requests, by
## News and Updates ## News and Updates
### November 7, 2024
Added new option: `--pr_code_suggestions.focus_only_on_problems=true`
When enabled, this option reduces the number of code suggestions and categorizes them into just two groups: "Possible Issues" and "General". The suggestions will focus primarily on identifying and fixing code problems, rather than style considerations like best practices, maintainability, or readability.
This mode is ideal for developers who want to concentrate specifically on finding and fixing potential bugs in their pull request code.
**Example results:**
Original mode
<kbd><img src="https://qodo.ai/images/pr_agent/code_suggestions_original_mode.png" width="512"></kbd>
Focused mode
<kbd><img src="https://qodo.ai/images/pr_agent/code_suggestions_focused_mode.png" width="512"></kbd>
### November 4, 2024 ### November 4, 2024
Qodo Merge PR Agent will now leverage context from Jira or GitHub tickets to enhance the PR Feedback. Read more about this feature Qodo Merge PR Agent will now leverage context from Jira or GitHub tickets to enhance the PR Feedback. Read more about this feature

View File

@ -2,3 +2,4 @@ We take your code's security and privacy seriously:
- The Chrome extension will not send your code to any external servers. - The Chrome extension will not send your code to any external servers.
- For private repositories, we will first validate the user's identity and permissions. After authentication, we generate responses using the existing Qodo Merge Pro integration. - For private repositories, we will first validate the user's identity and permissions. After authentication, we generate responses using the existing Qodo Merge Pro integration.

View File

@ -38,7 +38,6 @@ You can also modify the `script` section to run different Qodo Merge commands, o
Note that if your base branches are not protected, don't set the variables as `protected`, since the pipeline will not have access to them. Note that if your base branches are not protected, don't set the variables as `protected`, since the pipeline will not have access to them.
> **Note**: The `$CI_SERVER_FQDN` variable is available starting from GitLab version 16.10. If you're using an earlier version, this variable will not be available. However, you can combine `$CI_SERVER_HOST` and `$CI_SERVER_PORT` to achieve the same result. Please ensure you're using a compatible version or adjust your configuration.
## Run a GitLab webhook server ## Run a GitLab webhook server

View File

@ -275,10 +275,6 @@ Using a combination of both can help the AI model to provide relevant and tailor
<td><b>dual_publishing_score_threshold</b></td> <td><b>dual_publishing_score_threshold</b></td>
<td>Minimum score threshold for suggestions to be presented as commitable PR comments in addition to the table. Default is -1 (disabled).</td> <td>Minimum score threshold for suggestions to be presented as commitable PR comments in addition to the table. Default is -1 (disabled).</td>
</tr> </tr>
<tr>
<td><b>focus_only_on_problems</b></td>
<td>If set to true, suggestions will focus primarily on identifying and fixing code problems, and less on style considerations like best practices, maintainability, or readability. Default is false.</td>
</tr>
<tr> <tr>
<td><b>persistent_comment</b></td> <td><b>persistent_comment</b></td>
<td>If set to true, the improve comment will be persistent, meaning that every new improve request will edit the previous one. Default is false.</td> <td>If set to true, the improve comment will be persistent, meaning that every new improve request will edit the previous one. Default is false.</td>

View File

@ -258,3 +258,4 @@ If enabled, the `review` tool can approve a PR when a specific comment, `/review
[//]: # ( Notice If you are interested **only** in the code suggestions, it is recommended to use the [`improve`]&#40;./improve.md&#41; feature instead, since it is a dedicated only to code suggestions, and usually gives better results.) [//]: # ( Notice If you are interested **only** in the code suggestions, it is recommended to use the [`improve`]&#40;./improve.md&#41; feature instead, since it is a dedicated only to code suggestions, and usually gives better results.)
[//]: # ( Use the `review` tool if you want to get more comprehensive feedback, which includes code suggestions as well.) [//]: # ( Use the `review` tool if you want to get more comprehensive feedback, which includes code suggestions as well.)

View File

@ -77,7 +77,6 @@ pr_commands = [
"/improve --pr_code_suggestions.suggestions_score_threshold=5", "/improve --pr_code_suggestions.suggestions_score_threshold=5",
] ]
``` ```
This means that when a new PR is opened/reopened or marked as ready for review, Qodo Merge will run the `describe`, `review` and `improve` tools. This means that when a new PR is opened/reopened or marked as ready for review, Qodo Merge will run the `describe`, `review` and `improve` tools.
For the `improve` tool, for example, the `suggestions_score_threshold` parameter will be set to 5 (suggestions below a score of 5 won't be presented) For the `improve` tool, for example, the `suggestions_score_threshold` parameter will be set to 5 (suggestions below a score of 5 won't be presented)
@ -154,7 +153,7 @@ pr_commands = [
] ]
``` ```
the GitLab webhook can also respond to new code that is pushed to an open MR. The GitLab webhook can also respond to new code that is pushed to an open MR.
The configuration toggle `handle_push_trigger` can be used to enable this feature. The configuration toggle `handle_push_trigger` can be used to enable this feature.
The configuration parameter `push_commands` defines the list of tools that will be **run automatically** when new code is pushed to the MR. The configuration parameter `push_commands` defines the list of tools that will be **run automatically** when new code is pushed to the MR.
``` ```
@ -221,7 +220,7 @@ git_provider="azure"
``` ```
Azure DevOps provider supports [PAT token](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows) or [DefaultAzureCredential](https://learn.microsoft.com/en-us/azure/developer/python/sdk/authentication-overview#authentication-in-server-environments) authentication. Azure DevOps provider supports [PAT token](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows) or [DefaultAzureCredential](https://learn.microsoft.com/en-us/azure/developer/python/sdk/authentication-overview#authentication-in-server-environments) authentication.
PAT is faster to create, but has build in expiration date, and will use the user identity for API calls. PAT is faster to create, but has a build in expiration date, and will use the user identity for API calls.
Using DefaultAzureCredential you can use managed identity or Service principle, which are more secure and will create separate ADO user identity (via AAD) to the agent. Using DefaultAzureCredential you can use managed identity or Service principle, which are more secure and will create separate ADO user identity (via AAD) to the agent.
If PAT was chosen, you can assign the value in .secrets.toml. If PAT was chosen, you can assign the value in .secrets.toml.

View File

@ -10,3 +10,4 @@ Specifically, CLI commands can be issued by invoking a pre-built [docker image](
For online usage, you will need to setup either a [GitHub App](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-app) or a [GitHub Action](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-action) (GitHub), a [GitLab webhook](https://qodo-merge-docs.qodo.ai/installation/gitlab/#run-a-gitlab-webhook-server) (GitLab), or a [BitBucket App](https://qodo-merge-docs.qodo.ai/installation/bitbucket/#run-using-codiumai-hosted-bitbucket-app) (BitBucket). For online usage, you will need to setup either a [GitHub App](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-app) or a [GitHub Action](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-action) (GitHub), a [GitLab webhook](https://qodo-merge-docs.qodo.ai/installation/gitlab/#run-a-gitlab-webhook-server) (GitLab), or a [BitBucket App](https://qodo-merge-docs.qodo.ai/installation/bitbucket/#run-using-codiumai-hosted-bitbucket-app) (BitBucket).
These platforms also enable to run Qodo Merge specific tools automatically when a new PR is opened, or on each push to a branch. These platforms also enable to run Qodo Merge specific tools automatically when a new PR is opened, or on each push to a branch.

View File

@ -0,0 +1 @@

View File

@ -3,6 +3,7 @@ from functools import partial
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler 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.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
from pr_agent.algo.utils import update_settings_from_args from pr_agent.algo.utils import update_settings_from_args
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.git_providers.utils import apply_repo_settings from pr_agent.git_providers.utils import apply_repo_settings

View File

@ -31,7 +31,6 @@ MAX_TOKENS = {
'vertex_ai/codechat-bison': 6144, 'vertex_ai/codechat-bison': 6144,
'vertex_ai/codechat-bison-32k': 32000, 'vertex_ai/codechat-bison-32k': 32000,
'vertex_ai/claude-3-haiku@20240307': 100000, 'vertex_ai/claude-3-haiku@20240307': 100000,
'vertex_ai/claude-3-5-haiku@20241022': 100000,
'vertex_ai/claude-3-sonnet@20240229': 100000, 'vertex_ai/claude-3-sonnet@20240229': 100000,
'vertex_ai/claude-3-opus@20240229': 100000, 'vertex_ai/claude-3-opus@20240229': 100000,
'vertex_ai/claude-3-5-sonnet@20240620': 100000, 'vertex_ai/claude-3-5-sonnet@20240620': 100000,
@ -49,13 +48,11 @@ MAX_TOKENS = {
'anthropic/claude-3-opus-20240229': 100000, 'anthropic/claude-3-opus-20240229': 100000,
'anthropic/claude-3-5-sonnet-20240620': 100000, 'anthropic/claude-3-5-sonnet-20240620': 100000,
'anthropic/claude-3-5-sonnet-20241022': 100000, 'anthropic/claude-3-5-sonnet-20241022': 100000,
'anthropic/claude-3-5-haiku-20241022': 100000,
'bedrock/anthropic.claude-instant-v1': 100000, 'bedrock/anthropic.claude-instant-v1': 100000,
'bedrock/anthropic.claude-v2': 100000, 'bedrock/anthropic.claude-v2': 100000,
'bedrock/anthropic.claude-v2:1': 100000, 'bedrock/anthropic.claude-v2:1': 100000,
'bedrock/anthropic.claude-3-sonnet-20240229-v1:0': 100000, 'bedrock/anthropic.claude-3-sonnet-20240229-v1:0': 100000,
'bedrock/anthropic.claude-3-haiku-20240307-v1:0': 100000, 'bedrock/anthropic.claude-3-haiku-20240307-v1:0': 100000,
'bedrock/anthropic.claude-3-5-haiku-20241022-v1:0': 100000,
'bedrock/anthropic.claude-3-5-sonnet-20240620-v1:0': 100000, 'bedrock/anthropic.claude-3-5-sonnet-20240620-v1:0': 100000,
'bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0': 100000, 'bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0': 100000,
'claude-3-5-sonnet': 100000, 'claude-3-5-sonnet': 100000,

View File

@ -1,18 +1,17 @@
try: try:
from langchain_core.messages import HumanMessage, SystemMessage from langchain_openai import ChatOpenAI, AzureChatOpenAI
from langchain_openai import AzureChatOpenAI, ChatOpenAI from langchain_core.messages import SystemMessage, HumanMessage
except: # we don't enforce langchain as a dependency, so if it's not installed, just move on except: # we don't enforce langchain as a dependency, so if it's not installed, just move on
pass pass
import functools
from openai import APIError, RateLimitError, Timeout
from retry import retry
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.log import get_logger from pr_agent.log import get_logger
from openai import APIError, RateLimitError, Timeout
from retry import retry
import functools
OPENAI_RETRIES = 5 OPENAI_RETRIES = 5
@ -74,3 +73,4 @@ class LangChainOpenAIHandler(BaseAiHandler):
raise ValueError(f"OpenAI {e.name} is required") from e raise ValueError(f"OpenAI {e.name} is required") from e
else: else:
raise e raise e

View File

@ -1,8 +1,7 @@
import os import os
import requests
import litellm import litellm
import openai import openai
import requests
from litellm import acompletion from litellm import acompletion
from tenacity import retry, retry_if_exception_type, stop_after_attempt from tenacity import retry, retry_if_exception_type, stop_after_attempt

View File

@ -4,7 +4,6 @@ import openai
from openai import APIError, AsyncOpenAI, RateLimitError, Timeout from openai import APIError, AsyncOpenAI, RateLimitError, Timeout
from retry import retry from retry import retry
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.log import get_logger from pr_agent.log import get_logger
@ -42,6 +41,7 @@ class OpenAIHandler(BaseAiHandler):
tries=OPENAI_RETRIES, delay=2, backoff=2, jitter=(1, 3)) tries=OPENAI_RETRIES, delay=2, backoff=2, jitter=(1, 3))
async def chat_completion(self, model: str, system: str, user: str, temperature: float = 0.2): async def chat_completion(self, model: str, system: str, user: str, temperature: float = 0.2):
try: try:
deployment_id = self.deployment_id
get_logger().info("System: ", system) get_logger().info("System: ", system)
get_logger().info("User: ", user) get_logger().info("User: ", user)
messages = [{"role": "system", "content": system}, {"role": "user", "content": user}] messages = [{"role": "system", "content": system}, {"role": "user", "content": user}]

View File

@ -3,8 +3,8 @@ from __future__ import annotations
import re import re
import traceback import traceback
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
from pr_agent.log import get_logger from pr_agent.log import get_logger
@ -31,7 +31,7 @@ def extend_patch(original_file_str, patch_str, patch_extra_lines_before=0,
def decode_if_bytes(original_file_str): def decode_if_bytes(original_file_str):
if isinstance(original_file_str, (bytes, bytearray)): if isinstance(original_file_str, bytes):
try: try:
return original_file_str.decode('utf-8') return original_file_str.decode('utf-8')
except UnicodeDecodeError: except UnicodeDecodeError:
@ -61,26 +61,23 @@ def process_patch_lines(patch_str, original_file_str, patch_extra_lines_before,
patch_lines = patch_str.splitlines() patch_lines = patch_str.splitlines()
extended_patch_lines = [] extended_patch_lines = []
is_valid_hunk = True
start1, size1, start2, size2 = -1, -1, -1, -1 start1, size1, start2, size2 = -1, -1, -1, -1
RE_HUNK_HEADER = re.compile( RE_HUNK_HEADER = re.compile(
r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@[ ]?(.*)") r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@[ ]?(.*)")
try: try:
for i,line in enumerate(patch_lines): for line in patch_lines:
if line.startswith('@@'): if line.startswith('@@'):
match = RE_HUNK_HEADER.match(line) match = RE_HUNK_HEADER.match(line)
# identify hunk header # identify hunk header
if match: if match:
# finish processing previous hunk # finish processing previous hunk
if is_valid_hunk and (start1 != -1 and patch_extra_lines_after > 0): if start1 != -1 and patch_extra_lines_after > 0:
delta_lines = [f' {line}' for line in original_lines[start1 + size1 - 1:start1 + size1 - 1 + patch_extra_lines_after]] delta_lines = [f' {line}' for line in original_lines[start1 + size1 - 1:start1 + size1 - 1 + patch_extra_lines_after]]
extended_patch_lines.extend(delta_lines) extended_patch_lines.extend(delta_lines)
section_header, size1, size2, start1, start2 = extract_hunk_headers(match) section_header, size1, size2, start1, start2 = extract_hunk_headers(match)
is_valid_hunk = check_if_hunk_lines_matches_to_file(i, original_lines, patch_lines, start1) if patch_extra_lines_before > 0 or patch_extra_lines_after > 0:
if is_valid_hunk and (patch_extra_lines_before > 0 or patch_extra_lines_after > 0):
def _calc_context_limits(patch_lines_before): def _calc_context_limits(patch_lines_before):
extended_start1 = max(1, start1 - patch_lines_before) extended_start1 = max(1, start1 - patch_lines_before)
extended_size1 = size1 + (start1 - extended_start1) + patch_extra_lines_after extended_size1 = size1 + (start1 - extended_start1) + patch_extra_lines_after
@ -141,7 +138,7 @@ def process_patch_lines(patch_str, original_file_str, patch_extra_lines_before,
return patch_str return patch_str
# finish processing last hunk # finish processing last hunk
if start1 != -1 and patch_extra_lines_after > 0 and is_valid_hunk: if start1 != -1 and patch_extra_lines_after > 0:
delta_lines = original_lines[start1 + size1 - 1:start1 + size1 - 1 + patch_extra_lines_after] delta_lines = original_lines[start1 + size1 - 1:start1 + size1 - 1 + patch_extra_lines_after]
# add space at the beginning of each extra line # add space at the beginning of each extra line
delta_lines = [f' {line}' for line in delta_lines] delta_lines = [f' {line}' for line in delta_lines]
@ -151,23 +148,6 @@ def process_patch_lines(patch_str, original_file_str, patch_extra_lines_before,
return extended_patch_str return extended_patch_str
def check_if_hunk_lines_matches_to_file(i, original_lines, patch_lines, start1):
"""
Check if the hunk lines match the original file content. We saw cases where the hunk header line doesn't match the original file content, and then
extending the hunk with extra lines before the hunk header can cause the hunk to be invalid.
"""
is_valid_hunk = True
try:
if i + 1 < len(patch_lines) and patch_lines[i + 1][0] == ' ': # an existing line in the file
if patch_lines[i + 1].strip() != original_lines[start1 - 1].strip():
is_valid_hunk = False
get_logger().error(
f"Invalid hunk in PR, line {start1} in hunk header doesn't match the original file content")
except:
pass
return is_valid_hunk
def extract_hunk_headers(match): def extract_hunk_headers(match):
res = list(match.groups()) res = list(match.groups())
for i in range(len(res)): for i in range(len(res)):

View File

@ -4,6 +4,8 @@ from typing import Dict
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
def filter_bad_extensions(files): def filter_bad_extensions(files):
# Bad Extensions, source: https://github.com/EleutherAI/github-downloader/blob/345e7c4cbb9e0dc8a0615fd995a08bf9d73b3fe6/download_repo_text.py # noqa: E501 # Bad Extensions, source: https://github.com/EleutherAI/github-downloader/blob/345e7c4cbb9e0dc8a0615fd995a08bf9d73b3fe6/download_repo_text.py # noqa: E501
bad_extensions = get_settings().bad_extensions.default bad_extensions = get_settings().bad_extensions.default

View File

@ -5,15 +5,14 @@ from typing import Callable, List, Tuple
from github import RateLimitExceededException from github import RateLimitExceededException
from pr_agent.algo.file_filter import filter_ignored from pr_agent.algo.git_patch_processing import convert_to_hunks_with_lines_numbers, extend_patch, handle_patch_deletions
from pr_agent.algo.git_patch_processing import (
convert_to_hunks_with_lines_numbers, extend_patch, handle_patch_deletions)
from pr_agent.algo.language_handler import sort_files_by_main_languages from pr_agent.algo.language_handler import sort_files_by_main_languages
from pr_agent.algo.file_filter import filter_ignored
from pr_agent.algo.token_handler import TokenHandler from pr_agent.algo.token_handler import TokenHandler
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo from pr_agent.algo.utils import get_max_tokens, clip_tokens, ModelType
from pr_agent.algo.utils import ModelType, clip_tokens, get_max_tokens
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.git_providers.git_provider import GitProvider from pr_agent.git_providers.git_provider import GitProvider
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
from pr_agent.log import get_logger from pr_agent.log import get_logger
DELETED_FILES_ = "Deleted files:\n" DELETED_FILES_ = "Deleted files:\n"

View File

@ -1,9 +1,8 @@
from threading import Lock
from jinja2 import Environment, StrictUndefined from jinja2 import Environment, StrictUndefined
from tiktoken import encoding_for_model, get_encoding from tiktoken import encoding_for_model, get_encoding
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from threading import Lock
from pr_agent.log import get_logger from pr_agent.log import get_logger

View File

@ -14,6 +14,7 @@ from datetime import datetime
from enum import Enum from enum import Enum
from typing import Any, List, Tuple from typing import Any, List, Tuple
import html2text import html2text
import requests import requests
import yaml import yaml
@ -22,11 +23,10 @@ from starlette_context import context
from pr_agent.algo import MAX_TOKENS from pr_agent.algo import MAX_TOKENS
from pr_agent.algo.token_handler import TokenEncoder from pr_agent.algo.token_handler import TokenEncoder
from pr_agent.algo.types import FilePatchInfo
from pr_agent.config_loader import get_settings, global_settings from pr_agent.config_loader import get_settings, global_settings
from pr_agent.algo.types import FilePatchInfo
from pr_agent.log import get_logger from pr_agent.log import get_logger
class Range(BaseModel): class Range(BaseModel):
line_start: int # should be 0-indexed line_start: int # should be 0-indexed
line_end: int line_end: int

View File

@ -4,7 +4,7 @@ import os
from pr_agent.agent.pr_agent import PRAgent, commands from pr_agent.agent.pr_agent import PRAgent, commands
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.log import get_logger, setup_logger from pr_agent.log import setup_logger, get_logger
log_level = os.environ.get("LOG_LEVEL", "INFO") log_level = os.environ.get("LOG_LEVEL", "INFO")
setup_logger(log_level) setup_logger(log_level)
@ -92,4 +92,3 @@ def run(inargs=None, args=None):
if __name__ == '__main__': if __name__ == '__main__':
run() run()
aa= "rr"

View File

@ -1,16 +1,14 @@
from starlette_context import context
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.git_providers.azuredevops_provider import AzureDevopsProvider
from pr_agent.git_providers.bitbucket_provider import BitbucketProvider from pr_agent.git_providers.bitbucket_provider import BitbucketProvider
from pr_agent.git_providers.bitbucket_server_provider import \ from pr_agent.git_providers.bitbucket_server_provider import BitbucketServerProvider
BitbucketServerProvider
from pr_agent.git_providers.codecommit_provider import CodeCommitProvider from pr_agent.git_providers.codecommit_provider import CodeCommitProvider
from pr_agent.git_providers.gerrit_provider import GerritProvider
from pr_agent.git_providers.git_provider import GitProvider from pr_agent.git_providers.git_provider import GitProvider
from pr_agent.git_providers.github_provider import GithubProvider from pr_agent.git_providers.github_provider import GithubProvider
from pr_agent.git_providers.gitlab_provider import GitLabProvider from pr_agent.git_providers.gitlab_provider import GitLabProvider
from pr_agent.git_providers.local_git_provider import LocalGitProvider from pr_agent.git_providers.local_git_provider import LocalGitProvider
from pr_agent.git_providers.azuredevops_provider import AzureDevopsProvider
from pr_agent.git_providers.gerrit_provider import GerritProvider
from starlette_context import context
_GIT_PROVIDERS = { _GIT_PROVIDERS = {
'github': GithubProvider, 'github': GithubProvider,

View File

@ -2,16 +2,13 @@ import os
from typing import Optional, Tuple from typing import Optional, Tuple
from urllib.parse import urlparse from urllib.parse import urlparse
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
from ..algo.file_filter import filter_ignored from ..algo.file_filter import filter_ignored
from ..algo.language_handler import is_valid_file
from ..algo.utils import (PRDescriptionHeader, clip_tokens,
find_line_number_of_relevant_line_in_file,
load_large_diff)
from ..config_loader import get_settings
from ..log import get_logger from ..log import get_logger
from ..algo.language_handler import is_valid_file
from ..algo.utils import clip_tokens, find_line_number_of_relevant_line_in_file, load_large_diff, PRDescriptionHeader
from ..config_loader import get_settings
from .git_provider import GitProvider from .git_provider import GitProvider
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
AZURE_DEVOPS_AVAILABLE = True AZURE_DEVOPS_AVAILABLE = True
ADO_APP_CLIENT_DEFAULT_ID = "499b84ac-1321-427f-aa17-267ca6975798/.default" ADO_APP_CLIENT_DEFAULT_ID = "499b84ac-1321-427f-aa17-267ca6975798/.default"
@ -19,16 +16,19 @@ MAX_PR_DESCRIPTION_AZURE_LENGTH = 4000-1
try: try:
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
from msrest.authentication import BasicAuthentication
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
from azure.devops.connection import Connection from azure.devops.connection import Connection
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
from azure.devops.v7_1.git.models import (Comment, CommentThread, from azure.identity import DefaultAzureCredential
# noinspection PyUnresolvedReferences
from azure.devops.v7_1.git.models import (
Comment,
CommentThread,
GitVersionDescriptor,
GitPullRequest, GitPullRequest,
GitPullRequestIterationChanges, GitPullRequestIterationChanges,
GitVersionDescriptor) )
# noinspection PyUnresolvedReferences
from azure.identity import DefaultAzureCredential
from msrest.authentication import BasicAuthentication
except ImportError: except ImportError:
AZURE_DEVOPS_AVAILABLE = False AZURE_DEVOPS_AVAILABLE = False
@ -620,3 +620,4 @@ class AzureDevopsProvider(GitProvider):
def publish_file_comments(self, file_comments: list) -> bool: def publish_file_comments(self, file_comments: list) -> bool:
pass pass

View File

@ -6,14 +6,13 @@ import requests
from atlassian.bitbucket import Cloud from atlassian.bitbucket import Cloud
from starlette_context import context from starlette_context import context
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo from pr_agent.algo.types import FilePatchInfo, EDIT_TYPE
from ..algo.file_filter import filter_ignored from ..algo.file_filter import filter_ignored
from ..algo.language_handler import is_valid_file from ..algo.language_handler import is_valid_file
from ..algo.utils import find_line_number_of_relevant_line_in_file from ..algo.utils import find_line_number_of_relevant_line_in_file
from ..config_loader import get_settings from ..config_loader import get_settings
from ..log import get_logger from ..log import get_logger
from .git_provider import MAX_FILES_ALLOWED_FULL, GitProvider from .git_provider import GitProvider, MAX_FILES_ALLOWED_FULL
def _gef_filename(diff): def _gef_filename(diff):

View File

@ -1,17 +1,16 @@
from distutils.version import LooseVersion from distutils.version import LooseVersion
from requests.exceptions import HTTPError
from typing import Optional, Tuple from typing import Optional, Tuple
from urllib.parse import quote_plus, urlparse from urllib.parse import quote_plus, urlparse
from atlassian.bitbucket import Bitbucket from atlassian.bitbucket import Bitbucket
from requests.exceptions import HTTPError
from ..algo.language_handler import is_valid_file from .git_provider import GitProvider
from ..algo.types import EDIT_TYPE, FilePatchInfo from ..algo.types import EDIT_TYPE, FilePatchInfo
from ..algo.utils import (find_line_number_of_relevant_line_in_file, from ..algo.language_handler import is_valid_file
load_large_diff) from ..algo.utils import load_large_diff, find_line_number_of_relevant_line_in_file
from ..config_loader import get_settings from ..config_loader import get_settings
from ..log import get_logger from ..log import get_logger
from .git_provider import GitProvider
class BitbucketServerProvider(GitProvider): class BitbucketServerProvider(GitProvider):

View File

@ -4,15 +4,13 @@ from collections import Counter
from typing import List, Optional, Tuple from typing import List, Optional, Tuple
from urllib.parse import urlparse from urllib.parse import urlparse
from pr_agent.algo.language_handler import is_valid_file
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
from pr_agent.git_providers.codecommit_client import CodeCommitClient from pr_agent.git_providers.codecommit_client import CodeCommitClient
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
from ..algo.utils import load_large_diff from ..algo.utils import load_large_diff
from .git_provider import GitProvider
from ..config_loader import get_settings from ..config_loader import get_settings
from ..log import get_logger from ..log import get_logger
from .git_provider import GitProvider from pr_agent.algo.language_handler import is_valid_file
class PullRequestCCMimic: class PullRequestCCMimic:
""" """

View File

@ -12,9 +12,9 @@ import requests
import urllib3.util import urllib3.util
from git import Repo from git import Repo
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.git_providers.git_provider import GitProvider from pr_agent.git_providers.git_provider import GitProvider
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
from pr_agent.git_providers.local_git_provider import PullRequestMimic from pr_agent.git_providers.local_git_provider import PullRequestMimic
from pr_agent.log import get_logger from pr_agent.log import get_logger

View File

@ -1,12 +1,12 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
# enum EDIT_TYPE (ADDED, DELETED, MODIFIED, RENAMED) # enum EDIT_TYPE (ADDED, DELETED, MODIFIED, RENAMED)
from typing import Optional from typing import Optional
from pr_agent.algo.types import FilePatchInfo
from pr_agent.algo.utils import Range, process_description from pr_agent.algo.utils import Range, process_description
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.algo.types import FilePatchInfo
from pr_agent.log import get_logger from pr_agent.log import get_logger
MAX_FILES_ALLOWED_FULL = 50 MAX_FILES_ALLOWED_FULL = 50
class GitProvider(ABC): class GitProvider(ABC):
@ -62,8 +62,8 @@ class GitProvider(ABC):
pass pass
def get_pr_description(self, full: bool = True, split_changes_walkthrough=False) -> str or tuple: def get_pr_description(self, full: bool = True, split_changes_walkthrough=False) -> str or tuple:
from pr_agent.algo.utils import clip_tokens
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.algo.utils import clip_tokens
max_tokens_description = get_settings().get("CONFIG.MAX_DESCRIPTION_TOKENS", None) max_tokens_description = get_settings().get("CONFIG.MAX_DESCRIPTION_TOKENS", None)
description = self.get_pr_description_full() if full else self.get_user_description() description = self.get_pr_description_full() if full else self.get_user_description()
if split_changes_walkthrough: if split_changes_walkthrough:

View File

@ -1,11 +1,10 @@
import hashlib
import itertools import itertools
import time import time
import hashlib
import traceback import traceback
from datetime import datetime from datetime import datetime
from typing import Optional, Tuple from typing import Optional, Tuple
from urllib.parse import urlparse from urllib.parse import urlparse
from github import AppAuthentication, Auth, Github from github import AppAuthentication, Auth, Github
from retry import retry from retry import retry
from starlette_context import context from starlette_context import context
@ -13,14 +12,11 @@ from starlette_context import context
from ..algo.file_filter import filter_ignored from ..algo.file_filter import filter_ignored
from ..algo.language_handler import is_valid_file from ..algo.language_handler import is_valid_file
from ..algo.types import EDIT_TYPE from ..algo.types import EDIT_TYPE
from ..algo.utils import (PRReviewHeader, Range, clip_tokens, from ..algo.utils import PRReviewHeader, load_large_diff, clip_tokens, find_line_number_of_relevant_line_in_file, Range
find_line_number_of_relevant_line_in_file,
load_large_diff)
from ..config_loader import get_settings from ..config_loader import get_settings
from ..log import get_logger from ..log import get_logger
from ..servers.utils import RateLimitExceeded from ..servers.utils import RateLimitExceeded
from .git_provider import (MAX_FILES_ALLOWED_FULL, FilePatchInfo, GitProvider, from .git_provider import FilePatchInfo, GitProvider, IncrementalPR, MAX_FILES_ALLOWED_FULL
IncrementalPR)
class GithubProvider(GitProvider): class GithubProvider(GitProvider):
@ -199,24 +195,7 @@ class GithubProvider(GitProvider):
if avoid_load: if avoid_load:
original_file_content_str = "" original_file_content_str = ""
else: else:
# The base.sha will point to the current state of the base branch (including parallel merges), not the original base commit when the PR was created original_file_content_str = self._get_pr_file_content(file, self.pr.base.sha)
# We can fix this by finding the merge base commit between the PR head and base branches
# Note that The pr.head.sha is actually correct as is - it points to the latest commit in your PR branch.
# This SHA isn't affected by parallel merges to the base branch since it's specific to your PR's branch.
repo = self.repo_obj
pr = self.pr
try:
compare = repo.compare(pr.base.sha, pr.head.sha)
merge_base_commit = compare.merge_base_commit
except Exception as e:
get_logger().error(f"Failed to get merge base commit: {e}")
merge_base_commit = pr.base
if merge_base_commit.sha != pr.base.sha:
get_logger().info(
f"Using merge base commit {merge_base_commit.sha} instead of base commit "
f"{pr.base.sha} for {file.filename}")
original_file_content_str = self._get_pr_file_content(file, merge_base_commit.sha)
if not patch: if not patch:
patch = load_large_diff(file.filename, new_file_content_str, original_file_content_str) patch = load_large_diff(file.filename, new_file_content_str, original_file_content_str)
@ -300,6 +279,7 @@ class GithubProvider(GitProvider):
relevant_line_in_file, relevant_line_in_file,
absolute_position) absolute_position)
if position == -1: if position == -1:
if get_settings().config.verbosity_level >= 2:
get_logger().info(f"Could not find position for {relevant_file} {relevant_line_in_file}") get_logger().info(f"Could not find position for {relevant_file} {relevant_line_in_file}")
subject_type = "FILE" subject_type = "FILE"
else: else:
@ -312,9 +292,11 @@ class GithubProvider(GitProvider):
# publish all comments in a single message # publish all comments in a single message
self.pr.create_review(commit=self.last_commit_id, comments=comments) self.pr.create_review(commit=self.last_commit_id, comments=comments)
except Exception as e: except Exception as e:
get_logger().info(f"Initially failed to publish inline comments as committable") if get_settings().config.verbosity_level >= 2:
get_logger().error(f"Failed to publish inline comments")
if (getattr(e, "status", None) == 422 and not disable_fallback): if (getattr(e, "status", None) == 422
and get_settings().github.publish_inline_comments_fallback_with_verification and not disable_fallback):
pass # continue to try _publish_inline_comments_fallback_with_verification pass # continue to try _publish_inline_comments_fallback_with_verification
else: else:
raise e # will end up with publishing the comments one by one raise e # will end up with publishing the comments one by one
@ -322,6 +304,7 @@ class GithubProvider(GitProvider):
try: try:
self._publish_inline_comments_fallback_with_verification(comments) self._publish_inline_comments_fallback_with_verification(comments)
except Exception as e: except Exception as e:
if get_settings().config.verbosity_level >= 2:
get_logger().error(f"Failed to publish inline code comments fallback, error: {e}") get_logger().error(f"Failed to publish inline code comments fallback, error: {e}")
raise e raise e
@ -347,8 +330,10 @@ class GithubProvider(GitProvider):
for comment in fixed_comments_as_one_liner: for comment in fixed_comments_as_one_liner:
try: try:
self.publish_inline_comments([comment], disable_fallback=True) self.publish_inline_comments([comment], disable_fallback=True)
if get_settings().config.verbosity_level >= 2:
get_logger().info(f"Published invalid comment as a single line comment: {comment}") get_logger().info(f"Published invalid comment as a single line comment: {comment}")
except: except:
if get_settings().config.verbosity_level >= 2:
get_logger().error(f"Failed to publish invalid comment as a single line comment: {comment}") get_logger().error(f"Failed to publish invalid comment as a single line comment: {comment}")
def _verify_code_comment(self, comment: dict): def _verify_code_comment(self, comment: dict):
@ -407,6 +392,7 @@ class GithubProvider(GitProvider):
if fixed_comment != comment: if fixed_comment != comment:
fixed_comments.append(fixed_comment) fixed_comments.append(fixed_comment)
except Exception as e: except Exception as e:
if get_settings().config.verbosity_level >= 2:
get_logger().error(f"Failed to fix inline comment, error: {e}") get_logger().error(f"Failed to fix inline comment, error: {e}")
return fixed_comments return fixed_comments
@ -422,11 +408,13 @@ class GithubProvider(GitProvider):
relevant_lines_end = suggestion['relevant_lines_end'] relevant_lines_end = suggestion['relevant_lines_end']
if not relevant_lines_start or relevant_lines_start == -1: if not relevant_lines_start or relevant_lines_start == -1:
if get_settings().config.verbosity_level >= 2:
get_logger().exception( get_logger().exception(
f"Failed to publish code suggestion, relevant_lines_start is {relevant_lines_start}") f"Failed to publish code suggestion, relevant_lines_start is {relevant_lines_start}")
continue continue
if relevant_lines_end < relevant_lines_start: if relevant_lines_end < relevant_lines_start:
if get_settings().config.verbosity_level >= 2:
get_logger().exception(f"Failed to publish code suggestion, " get_logger().exception(f"Failed to publish code suggestion, "
f"relevant_lines_end is {relevant_lines_end} and " f"relevant_lines_end is {relevant_lines_end} and "
f"relevant_lines_start is {relevant_lines_start}") f"relevant_lines_start is {relevant_lines_start}")
@ -453,6 +441,7 @@ class GithubProvider(GitProvider):
self.publish_inline_comments(post_parameters_list) self.publish_inline_comments(post_parameters_list)
return True return True
except Exception as e: except Exception as e:
if get_settings().config.verbosity_level >= 2:
get_logger().error(f"Failed to publish code suggestion, error: {e}") get_logger().error(f"Failed to publish code suggestion, error: {e}")
return False return False
@ -512,7 +501,6 @@ class GithubProvider(GitProvider):
elif self.deployment_type == 'user': elif self.deployment_type == 'user':
same_comment_creator = self.github_user_id == existing_comment['user']['login'] same_comment_creator = self.github_user_id == existing_comment['user']['login']
if existing_comment['subject_type'] == 'file' and comment['path'] == existing_comment['path'] and same_comment_creator: if existing_comment['subject_type'] == 'file' and comment['path'] == existing_comment['path'] and same_comment_creator:
headers, data_patch = self.pr._requester.requestJsonAndCheck( headers, data_patch = self.pr._requester.requestJsonAndCheck(
"PATCH", f"{self.base_url}/repos/{self.repo}/pulls/comments/{existing_comment['id']}", input={"body":comment['body']} "PATCH", f"{self.base_url}/repos/{self.repo}/pulls/comments/{existing_comment['id']}", input={"body":comment['body']}
) )
@ -524,6 +512,7 @@ class GithubProvider(GitProvider):
) )
return True return True
except Exception as e: except Exception as e:
if get_settings().config.verbosity_level >= 2:
get_logger().error(f"Failed to publish diffview file summary, error: {e}") get_logger().error(f"Failed to publish diffview file summary, error: {e}")
return False return False
@ -812,6 +801,7 @@ class GithubProvider(GitProvider):
link = f"{self.base_url_html}/{self.repo}/pull/{self.pr_num}/files#diff-{sha_file}R{absolute_position}" link = f"{self.base_url_html}/{self.repo}/pull/{self.pr_num}/files#diff-{sha_file}R{absolute_position}"
return link return link
except Exception as e: except Exception as e:
if get_settings().config.verbosity_level >= 2:
get_logger().info(f"Failed adding line link, error: {e}") get_logger().info(f"Failed adding line link, error: {e}")
return "" return ""

View File

@ -7,16 +7,13 @@ import gitlab
import requests import requests
from gitlab import GitlabGetError from gitlab import GitlabGetError
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
from ..algo.file_filter import filter_ignored from ..algo.file_filter import filter_ignored
from ..algo.language_handler import is_valid_file from ..algo.language_handler import is_valid_file
from ..algo.utils import (clip_tokens, from ..algo.utils import load_large_diff, clip_tokens, find_line_number_of_relevant_line_in_file
find_line_number_of_relevant_line_in_file,
load_large_diff)
from ..config_loader import get_settings from ..config_loader import get_settings
from .git_provider import GitProvider, MAX_FILES_ALLOWED_FULL
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
from ..log import get_logger from ..log import get_logger
from .git_provider import MAX_FILES_ALLOWED_FULL, GitProvider
class DiffNotFoundError(Exception): class DiffNotFoundError(Exception):

View File

@ -4,9 +4,9 @@ from typing import List
from git import Repo from git import Repo
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
from pr_agent.config_loader import _find_repository_root, get_settings from pr_agent.config_loader import _find_repository_root, get_settings
from pr_agent.git_providers.git_provider import GitProvider from pr_agent.git_providers.git_provider import GitProvider
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
from pr_agent.log import get_logger from pr_agent.log import get_logger

View File

@ -3,12 +3,11 @@ import os
import tempfile import tempfile
from dynaconf import Dynaconf from dynaconf import Dynaconf
from starlette_context import context
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.git_providers import (get_git_provider, from pr_agent.git_providers import get_git_provider, get_git_provider_with_context
get_git_provider_with_context)
from pr_agent.log import get_logger from pr_agent.log import get_logger
from starlette_context import context
def apply_repo_settings(pr_url): def apply_repo_settings(pr_url):

View File

@ -1,6 +1,5 @@
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.identity_providers.default_identity_provider import \ from pr_agent.identity_providers.default_identity_provider import DefaultIdentityProvider
DefaultIdentityProvider
_IDENTITY_PROVIDERS = { _IDENTITY_PROVIDERS = {
'default': DefaultIdentityProvider 'default': DefaultIdentityProvider

View File

@ -1,5 +1,4 @@
from pr_agent.identity_providers.identity_provider import (Eligibility, from pr_agent.identity_providers.identity_provider import Eligibility, IdentityProvider
IdentityProvider)
class DefaultIdentityProvider(IdentityProvider): class DefaultIdentityProvider(IdentityProvider):

View File

@ -8,10 +8,12 @@ def get_secret_provider():
provider_id = get_settings().config.secret_provider provider_id = get_settings().config.secret_provider
if provider_id == 'google_cloud_storage': if provider_id == 'google_cloud_storage':
try: try:
from pr_agent.secret_providers.google_cloud_storage_secret_provider import \ from pr_agent.secret_providers.google_cloud_storage_secret_provider import GoogleCloudStorageSecretProvider
GoogleCloudStorageSecretProvider
return GoogleCloudStorageSecretProvider() return GoogleCloudStorageSecretProvider()
except Exception as e: except Exception as e:
raise ValueError(f"Failed to initialize google_cloud_storage secret provider {provider_id}") from e raise ValueError(f"Failed to initialize google_cloud_storage secret provider {provider_id}") from e
else: else:
raise ValueError("Unknown SECRET_PROVIDER") raise ValueError("Unknown SECRET_PROVIDER")

View File

@ -9,9 +9,9 @@ import secrets
from urllib.parse import unquote from urllib.parse import unquote
import uvicorn import uvicorn
from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request from fastapi import APIRouter, Depends, FastAPI, HTTPException
from fastapi.encoders import jsonable_encoder
from fastapi.security import HTTPBasic, HTTPBasicCredentials from fastapi.security import HTTPBasic, HTTPBasicCredentials
from fastapi.encoders import jsonable_encoder
from starlette import status from starlette import status
from starlette.background import BackgroundTasks from starlette.background import BackgroundTasks
from starlette.middleware import Middleware from starlette.middleware import Middleware
@ -23,6 +23,9 @@ from pr_agent.agent.pr_agent import PRAgent, command2class
from pr_agent.algo.utils import update_settings_from_args from pr_agent.algo.utils import update_settings_from_args
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.git_providers.utils import apply_repo_settings from pr_agent.git_providers.utils import apply_repo_settings
from pr_agent.log import get_logger
from fastapi import Request, Depends
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from pr_agent.log import LoggingFormat, get_logger, setup_logger from pr_agent.log import LoggingFormat, get_logger, setup_logger
setup_logger(fmt=LoggingFormat.JSON, level="DEBUG") setup_logger(fmt=LoggingFormat.JSON, level="DEBUG")

View File

@ -98,14 +98,11 @@ async def _perform_commands_bitbucket(commands_conf: str, agent: PRAgent, api_ur
def is_bot_user(data) -> bool: def is_bot_user(data) -> bool:
try: try:
actor = data.get("data", {}).get("actor", {}) if data["data"]["actor"]["type"] != "user":
# allow actor type: user . if it's "AppUser" or "team" then it is a bot user get_logger().info(f"BitBucket actor type is not 'user': {data['data']['actor']['type']}")
allowed_actor_types = {"user"}
if actor and actor["type"].lower() not in allowed_actor_types:
get_logger().info(f"BitBucket actor type is not 'user', skipping: {actor}")
return True return True
except Exception as e: except Exception as e:
get_logger().error(f"Failed 'is_bot_user' logic: {e}") get_logger().error("Failed 'is_bot_user' logic: {e}")
return False return False
@ -164,18 +161,16 @@ async def handle_github_webhooks(background_tasks: BackgroundTasks, request: Req
return "OK" return "OK"
# Get the username of the sender # Get the username of the sender
actor = data.get("data", {}).get("actor", {})
if actor:
try: try:
username = actor["username"] username = data["data"]["actor"]["username"]
except KeyError: except KeyError:
try: try:
username = actor["display_name"] username = data["data"]["actor"]["display_name"]
except KeyError: except KeyError:
username = actor["nickname"] username = data["data"]["actor"]["nickname"]
log_context["sender"] = username log_context["sender"] = username
sender_id = data.get("data", {}).get("actor", {}).get("account_id", "") sender_id = data["data"]["actor"]["account_id"]
log_context["sender_id"] = sender_id log_context["sender_id"] = sender_id
jwt_parts = input_jwt.split(".") jwt_parts = input_jwt.split(".")
claim_part = jwt_parts[1] claim_part = jwt_parts[1]

View File

@ -6,20 +6,20 @@ from typing import List
import uvicorn import uvicorn
from fastapi import APIRouter, FastAPI from fastapi import APIRouter, FastAPI
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
from fastapi.responses import RedirectResponse
from starlette import status from starlette import status
from starlette.background import BackgroundTasks from starlette.background import BackgroundTasks
from starlette.middleware import Middleware from starlette.middleware import Middleware
from starlette.requests import Request from starlette.requests import Request
from starlette.responses import JSONResponse from starlette.responses import JSONResponse
from starlette_context.middleware import RawContextMiddleware from starlette_context.middleware import RawContextMiddleware
from pr_agent.agent.pr_agent import PRAgent from pr_agent.agent.pr_agent import PRAgent
from pr_agent.algo.utils import update_settings_from_args from pr_agent.algo.utils import update_settings_from_args
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.git_providers.utils import apply_repo_settings from pr_agent.git_providers.utils import apply_repo_settings
from pr_agent.log import LoggingFormat, get_logger, setup_logger from pr_agent.log import LoggingFormat, get_logger, setup_logger
from pr_agent.servers.utils import verify_signature from pr_agent.servers.utils import verify_signature
from fastapi.responses import RedirectResponse
setup_logger(fmt=LoggingFormat.JSON, level="DEBUG") setup_logger(fmt=LoggingFormat.JSON, level="DEBUG")
router = APIRouter() router = APIRouter()

View File

@ -15,8 +15,7 @@ from starlette_context.middleware import RawContextMiddleware
from pr_agent.agent.pr_agent import PRAgent from pr_agent.agent.pr_agent import PRAgent
from pr_agent.algo.utils import update_settings_from_args from pr_agent.algo.utils import update_settings_from_args
from pr_agent.config_loader import get_settings, global_settings from pr_agent.config_loader import get_settings, global_settings
from pr_agent.git_providers import (get_git_provider, from pr_agent.git_providers import get_git_provider, get_git_provider_with_context
get_git_provider_with_context)
from pr_agent.git_providers.git_provider import IncrementalPR from pr_agent.git_providers.git_provider import IncrementalPR
from pr_agent.git_providers.utils import apply_repo_settings from pr_agent.git_providers.utils import apply_repo_settings
from pr_agent.identity_providers import get_identity_provider from pr_agent.identity_providers import get_identity_provider

View File

@ -1,12 +1,11 @@
import asyncio import asyncio
import multiprocessing import multiprocessing
import time
import traceback
from collections import deque from collections import deque
import traceback
from datetime import datetime, timezone from datetime import datetime, timezone
import time
import aiohttp
import requests import requests
import aiohttp
from pr_agent.agent.pr_agent import PRAgent from pr_agent.agent.pr_agent import PRAgent
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings

View File

@ -1,6 +1,6 @@
import copy import copy
import json
import re import re
import json
from datetime import datetime from datetime import datetime
import uvicorn import uvicorn

View File

@ -5,6 +5,7 @@ from starlette_context.middleware import RawContextMiddleware
from pr_agent.servers.github_app import router from pr_agent.servers.github_app import router
middleware = [Middleware(RawContextMiddleware)] middleware = [Middleware(RawContextMiddleware)]
app = FastAPI(middleware=middleware) app = FastAPI(middleware=middleware)
app.include_router(router) app.include_router(router)

View File

@ -2,7 +2,7 @@ import hashlib
import hmac import hmac
import time import time
from collections import defaultdict from collections import defaultdict
from typing import Any, Callable from typing import Callable, Any
from fastapi import HTTPException from fastapi import HTTPException

View File

@ -107,11 +107,10 @@ enable_help_text=false
[pr_code_suggestions] # /improve # [pr_code_suggestions] # /improve #
max_context_tokens=16000 max_context_tokens=14000
# #
commitable_code_suggestions = false commitable_code_suggestions = false
dual_publishing_score_threshold=-1 # -1 to disable, [0-10] to set the threshold (>=) for publishing a code suggestion both in a table and as commitable dual_publishing_score_threshold=-1 # -1 to disable, [0-10] to set the threshold (>=) for publishing a code suggestion both in a table and as commitable
focus_only_on_problems=false
# #
extra_instructions = "" extra_instructions = ""
rank_suggestions = false rank_suggestions = false

View File

@ -1,10 +1,7 @@
[pr_code_suggestions_prompt] [pr_code_suggestions_prompt]
system="""You are PR-Reviewer, an AI specializing in Pull Request (PR) code analysis and suggestions. system="""You are PR-Reviewer, an AI specializing in Pull Request (PR) code analysis and suggestions.
{%- if not focus_only_on_problems %} Your task is to examine the provided code diff, focusing on new code (lines prefixed with '+'), and offer concise, actionable suggestions to fix possible bugs and problems, and enhance code quality, readability, and performance.
Your task is to examine the provided code diff, focusing on new code (lines prefixed with '+'), and offer concise, actionable suggestions to fix possible bugs and problems, and enhance code quality and performance.
{%- else %}
Your task is to examine the provided code diff, focusing on new code (lines prefixed with '+'), and offer concise, actionable suggestions to fix critical bugs and problems.
{%- endif %}
The PR code diff will be in the following structured format: The PR code diff will be in the following structured format:
====== ======
@ -45,17 +42,9 @@ __new hunk__
Specific guidelines for generating code suggestions: Specific guidelines for generating code suggestions:
{%- if not focus_only_on_problems %}
- Provide up to {{ num_code_suggestions }} distinct and insightful code suggestions. - Provide up to {{ num_code_suggestions }} distinct and insightful code suggestions.
{%- else %}
- Provide up to {{ num_code_suggestions }} distinct and insightful code suggestions. Return less suggestions if no pertinent ones are applicable.
{%- endif %}
- Focus solely on enhancing new code introduced in the PR, identified by '+' prefixes in '__new hunk__' sections. - Focus solely on enhancing new code introduced in the PR, identified by '+' prefixes in '__new hunk__' sections.
{%- if not focus_only_on_problems %}
- Prioritize suggestions that address potential issues, critical problems, and bugs in the PR code. Avoid repeating changes already implemented in the PR. If no pertinent suggestions are applicable, return an empty list. - Prioritize suggestions that address potential issues, critical problems, and bugs in the PR code. Avoid repeating changes already implemented in the PR. If no pertinent suggestions are applicable, return an empty list.
{%- else %}
- Only give suggestions that address critical problems and bugs in the PR code. If no relevant suggestions are applicable, return an empty list.
{%- endif %}
- Don't suggest to add docstring, type hints, or comments, to remove unused imports, or to use more specific exception types. - Don't suggest to add docstring, type hints, or comments, to remove unused imports, or to use more specific exception types.
- When referencing variables or names from the code, enclose them in backticks (`). Example: "ensure that `variable_name` is..." - When referencing variables or names from the code, enclose them in backticks (`). Example: "ensure that `variable_name` is..."
- Be mindful you are viewing a partial PR code diff, not the full codebase. Avoid suggestions that might conflict with unseen code or alerting variables not declared in the visible scope, as the context is incomplete. - Be mindful you are viewing a partial PR code diff, not the full codebase. Avoid suggestions that might conflict with unseen code or alerting variables not declared in the visible scope, as the context is incomplete.
@ -80,11 +69,7 @@ class CodeSuggestion(BaseModel):
existing_code: str = Field(description="A short code snippet from a '__new hunk__' section that the suggestion aims to enhance or fix. Include only complete code lines. Use ellipsis (...) for brevity if needed. This snippet should represent the specific PR code targeted for improvement.") existing_code: str = Field(description="A short code snippet from a '__new hunk__' section that the suggestion aims to enhance or fix. Include only complete code lines. Use ellipsis (...) for brevity if needed. This snippet should represent the specific PR code targeted for improvement.")
improved_code: str = Field(description="A refined code snippet that replaces the 'existing_code' snippet after implementing the suggestion.") improved_code: str = Field(description="A refined code snippet that replaces the 'existing_code' snippet after implementing the suggestion.")
one_sentence_summary: str = Field(description="A concise, single-sentence overview of the suggested improvement. Focus on the 'what'. Be general, and avoid method or variable names.") one_sentence_summary: str = Field(description="A concise, single-sentence overview of the suggested improvement. Focus on the 'what'. Be general, and avoid method or variable names.")
{%- if not focus_only_on_problems %}
label: str = Field(description="A single, descriptive label that best characterizes the suggestion type. Possible labels include 'security', 'possible bug', 'possible issue', 'performance', 'enhancement', 'best practice', 'maintainability', 'typo'. Other relevant labels are also acceptable.") label: str = Field(description="A single, descriptive label that best characterizes the suggestion type. Possible labels include 'security', 'possible bug', 'possible issue', 'performance', 'enhancement', 'best practice', 'maintainability', 'typo'. Other relevant labels are also acceptable.")
{%- else %}
label: str = Field(description="A single, descriptive label that best characterizes the suggestion type. Possible labels include 'security', 'critical bug', 'general'. The 'general' section should be used for suggestions that address a major issue, but are necessarily on a critical level.")
{%- endif %}
class PRCodeSuggestions(BaseModel): class PRCodeSuggestions(BaseModel):

View File

@ -1,30 +1,26 @@
import asyncio import asyncio
import copy import copy
import difflib
import re
import textwrap import textwrap
import traceback import traceback
from functools import partial from functools import partial
from typing import Dict, List from typing import Dict, List
from jinja2 import Environment, StrictUndefined from jinja2 import Environment, StrictUndefined
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler 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.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
from pr_agent.algo.pr_processing import (add_ai_metadata_to_diff_files, from pr_agent.algo.pr_processing import get_pr_diff, get_pr_multi_diffs, retry_with_fallback_models, \
get_pr_diff, get_pr_multi_diffs, add_ai_metadata_to_diff_files
retry_with_fallback_models)
from pr_agent.algo.token_handler import TokenHandler from pr_agent.algo.token_handler import TokenHandler
from pr_agent.algo.utils import (ModelType, load_yaml, replace_code_tags, from pr_agent.algo.utils import load_yaml, replace_code_tags, ModelType, show_relevant_configurations
show_relevant_configurations)
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.git_providers import (AzureDevopsProvider, GithubProvider, from pr_agent.git_providers import get_git_provider, get_git_provider_with_context, GithubProvider, GitLabProvider, \
GitLabProvider, get_git_provider, AzureDevopsProvider
get_git_provider_with_context)
from pr_agent.git_providers.git_provider import get_main_pr_language from pr_agent.git_providers.git_provider import get_main_pr_language
from pr_agent.log import get_logger from pr_agent.log import get_logger
from pr_agent.servers.help import HelpMessage from pr_agent.servers.help import HelpMessage
from pr_agent.tools.pr_description import insert_br_after_x_chars from pr_agent.tools.pr_description import insert_br_after_x_chars
import difflib
import re
class PRCodeSuggestions: class PRCodeSuggestions:
@ -80,7 +76,6 @@ class PRCodeSuggestions:
"commit_messages_str": self.git_provider.get_commit_messages(), "commit_messages_str": self.git_provider.get_commit_messages(),
"relevant_best_practices": "", "relevant_best_practices": "",
"is_ai_metadata": get_settings().get("config.enable_ai_metadata", False), "is_ai_metadata": get_settings().get("config.enable_ai_metadata", False),
"focus_only_on_problems": get_settings().get("pr_code_suggestions.focus_only_on_problems", False),
} }
self.pr_code_suggestions_prompt_system = get_settings().pr_code_suggestions_prompt.system self.pr_code_suggestions_prompt_system = get_settings().pr_code_suggestions_prompt.system
@ -337,8 +332,6 @@ class PRCodeSuggestions:
model, model,
add_line_numbers_to_hunks=True, add_line_numbers_to_hunks=True,
disable_extra_lines=False) disable_extra_lines=False)
self.patches_diff_list = [self.patches_diff]
self.patches_diff_no_line_number = self.remove_line_numbers([self.patches_diff])[0]
if self.patches_diff: if self.patches_diff:
get_logger().debug(f"PR diff", artifact=self.patches_diff) get_logger().debug(f"PR diff", artifact=self.patches_diff)
@ -458,11 +451,6 @@ class PRCodeSuggestions:
if not is_valid_keys: if not is_valid_keys:
continue continue
if get_settings().get("pr_code_suggestions.focus_only_on_problems", False):
CRITICAL_LABEL = 'critical'
if CRITICAL_LABEL in suggestion['label'].lower(): # we want the published labels to be less declarative
suggestion['label'] = 'possible issue'
if suggestion['one_sentence_summary'] in one_sentence_summary_list: if suggestion['one_sentence_summary'] in one_sentence_summary_list:
get_logger().debug(f"Skipping suggestion {i + 1}, because it is a duplicate: {suggestion}") get_logger().debug(f"Skipping suggestion {i + 1}, because it is a duplicate: {suggestion}")
continue continue
@ -841,3 +829,4 @@ class PRCodeSuggestions:
get_logger().info(f"Could not reflect on suggestions, error: {e}") get_logger().info(f"Could not reflect on suggestions, error: {e}")
return "" return ""
return response_reflect return response_reflect

View File

@ -9,24 +9,19 @@ from jinja2 import Environment, StrictUndefined
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler 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.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
from pr_agent.algo.pr_processing import (OUTPUT_BUFFER_TOKENS_HARD_THRESHOLD, from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models, get_pr_diff_multiple_patchs, \
get_pr_diff, OUTPUT_BUFFER_TOKENS_HARD_THRESHOLD
get_pr_diff_multiple_patchs,
retry_with_fallback_models)
from pr_agent.algo.token_handler import TokenHandler from pr_agent.algo.token_handler import TokenHandler
from pr_agent.algo.utils import (ModelType, PRDescriptionHeader, clip_tokens, from pr_agent.algo.utils import set_custom_labels, PRDescriptionHeader
get_max_tokens, get_user_labels, load_yaml, from pr_agent.algo.utils import load_yaml, get_user_labels, ModelType, show_relevant_configurations, get_max_tokens, \
set_custom_labels, clip_tokens
show_relevant_configurations)
from pr_agent.config_loader import get_settings 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, GithubProvider, get_git_provider_with_context
get_git_provider_with_context)
from pr_agent.git_providers.git_provider import get_main_pr_language from pr_agent.git_providers.git_provider import get_main_pr_language
from pr_agent.log import get_logger from pr_agent.log import get_logger
from pr_agent.servers.help import HelpMessage from pr_agent.servers.help import HelpMessage
from pr_agent.tools.ticket_pr_compliance_check import ( from pr_agent.tools.ticket_pr_compliance_check import extract_ticket_links_from_pr_description, extract_tickets, \
extract_and_cache_pr_tickets, extract_ticket_links_from_pr_description, extract_and_cache_pr_tickets
extract_tickets)
class PRDescription: class PRDescription:

View File

@ -9,7 +9,7 @@ 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.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models
from pr_agent.algo.token_handler import TokenHandler from pr_agent.algo.token_handler import TokenHandler
from pr_agent.algo.utils import get_user_labels, load_yaml, set_custom_labels from pr_agent.algo.utils import load_yaml, set_custom_labels, get_user_labels
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.git_providers import 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.git_providers.git_provider import get_main_pr_language

View File

@ -9,10 +9,10 @@ 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.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
from pr_agent.algo.pr_processing import retry_with_fallback_models from pr_agent.algo.pr_processing import retry_with_fallback_models
from pr_agent.algo.token_handler import TokenHandler from pr_agent.algo.token_handler import TokenHandler
from pr_agent.algo.utils import ModelType, clip_tokens, load_yaml from pr_agent.algo.utils import ModelType, load_yaml, clip_tokens
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.git_providers import (BitbucketServerProvider, GithubProvider, from pr_agent.git_providers import GithubProvider, BitbucketServerProvider, \
get_git_provider_with_context) get_git_provider_with_context
from pr_agent.log import get_logger from pr_agent.log import get_logger

View File

@ -6,8 +6,8 @@ from jinja2 import Environment, StrictUndefined
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler 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.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
from pr_agent.algo.git_patch_processing import ( from pr_agent.algo.git_patch_processing import convert_to_hunks_with_lines_numbers, \
convert_to_hunks_with_lines_numbers, extract_hunk_lines_from_patch) extract_hunk_lines_from_patch
from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models
from pr_agent.algo.token_handler import TokenHandler from pr_agent.algo.token_handler import TokenHandler
from pr_agent.algo.utils import ModelType from pr_agent.algo.utils import ModelType

View File

@ -4,27 +4,19 @@ import traceback
from collections import OrderedDict from collections import OrderedDict
from functools import partial from functools import partial
from typing import List, Tuple from typing import List, Tuple
from jinja2 import Environment, StrictUndefined from jinja2 import Environment, StrictUndefined
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler 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.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
from pr_agent.algo.pr_processing import (add_ai_metadata_to_diff_files, from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models, add_ai_metadata_to_diff_files
get_pr_diff,
retry_with_fallback_models)
from pr_agent.algo.token_handler import TokenHandler from pr_agent.algo.token_handler import TokenHandler
from pr_agent.algo.utils import (ModelType, PRReviewHeader, from pr_agent.algo.utils import github_action_output, load_yaml, ModelType, \
convert_to_markdown_v2, github_action_output, show_relevant_configurations, convert_to_markdown_v2, PRReviewHeader
load_yaml, show_relevant_configurations)
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.git_providers import (get_git_provider, from pr_agent.git_providers import get_git_provider, get_git_provider_with_context
get_git_provider_with_context) from pr_agent.git_providers.git_provider import IncrementalPR, get_main_pr_language
from pr_agent.git_providers.git_provider import (IncrementalPR,
get_main_pr_language)
from pr_agent.log import get_logger from pr_agent.log import get_logger
from pr_agent.servers.help import HelpMessage from pr_agent.servers.help import HelpMessage
from pr_agent.tools.ticket_pr_compliance_check import ( from pr_agent.tools.ticket_pr_compliance_check import extract_tickets, extract_and_cache_pr_tickets
extract_and_cache_pr_tickets, extract_tickets)
class PRReviewer: class PRReviewer:

View File

@ -34,9 +34,9 @@ class PRSimilarIssue:
if get_settings().pr_similar_issue.vectordb == "pinecone": if get_settings().pr_similar_issue.vectordb == "pinecone":
try: try:
import pandas as pd
import pinecone import pinecone
from pinecone_datasets import Dataset, DatasetMetadata from pinecone_datasets import Dataset, DatasetMetadata
import pandas as pd
except: except:
raise Exception("Please install 'pinecone' and 'pinecone_datasets' to use pinecone as vectordb") raise Exception("Please install 'pinecone' and 'pinecone_datasets' to use pinecone as vectordb")
# assuming pinecone api key and environment are set in secrets file # assuming pinecone api key and environment are set in secrets file

View File

@ -3,16 +3,14 @@ from datetime import date
from functools import partial from functools import partial
from time import sleep from time import sleep
from typing import Tuple from typing import Tuple
from jinja2 import Environment, StrictUndefined from jinja2 import Environment, StrictUndefined
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler 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.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models
from pr_agent.algo.token_handler import TokenHandler from pr_agent.algo.token_handler import TokenHandler
from pr_agent.algo.utils import ModelType, show_relevant_configurations from pr_agent.algo.utils import ModelType, show_relevant_configurations
from pr_agent.config_loader import get_settings 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, GithubProvider
from pr_agent.git_providers.git_provider import get_main_pr_language from pr_agent.git_providers.git_provider import get_main_pr_language
from pr_agent.log import get_logger from pr_agent.log import get_logger

View File

@ -6,19 +6,19 @@ build-backend = "setuptools.build_meta"
name = "pr-agent" name = "pr-agent"
version = "0.2.4" version = "0.2.4"
authors = [{ name = "CodiumAI", email = "tal.r@codium.ai" }] authors = [{name= "CodiumAI", email = "tal.r@codium.ai"}]
maintainers = [ maintainers = [
{ name = "Tal Ridnik", email = "tal.r@codium.ai" }, {name = "Tal Ridnik", email = "tal.r@codium.ai"},
{ name = "Ori Kotek", email = "ori.k@codium.ai" }, {name = "Ori Kotek", email = "ori.k@codium.ai"},
{ name = "Hussam Lawen", email = "hussam.l@codium.ai" }, {name = "Hussam Lawen", email = "hussam.l@codium.ai"},
] ]
description = "CodiumAI PR-Agent aims to help efficiently review and handle pull requests, by providing AI feedbacks and suggestions." description = "CodiumAI PR-Agent aims to help efficiently review and handle pull requests, by providing AI feedbacks and suggestions."
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"
keywords = ["AI", "Agents", "Pull Request", "Automation", "Code Review"] keywords = ["AI", "Agents", "Pull Request", "Automation", "Code Review"]
license = { name = "Apache 2.0", file = "LICENSE" } license = {name = "Apache 2.0", file = "LICENSE"}
classifiers = [ classifiers = [
"Intended Audience :: Developers", "Intended Audience :: Developers",
@ -28,7 +28,7 @@ dynamic = ["dependencies"]
[tool.setuptools.dynamic] [tool.setuptools.dynamic]
dependencies = { file = ["requirements.txt"] } dependencies = {file = ["requirements.txt"]}
[project.urls] [project.urls]
"Homepage" = "https://github.com/Codium-ai/pr-agent" "Homepage" = "https://github.com/Codium-ai/pr-agent"
@ -40,43 +40,41 @@ license-files = ["LICENSE"]
[tool.setuptools.packages.find] [tool.setuptools.packages.find]
where = ["."] where = ["."]
include = [ include = ["pr_agent*"] # include pr_agent and any sub-packages it finds under it.
"pr_agent*",
] # include pr_agent and any sub-packages it finds under it.
[project.scripts] [project.scripts]
pr-agent = "pr_agent.cli:run" pr-agent = "pr_agent.cli:run"
[tool.ruff] [tool.ruff]
line-length = 120 line-length = 120
lint.select = [ select = [
"E", # Pyflakes "E", # Pyflakes
"F", # Pyflakes "F", # Pyflakes
"B", # flake8-bugbear "B", # flake8-bugbear
"I001", # isort basic checks "I001", # isort basic checks
"I002", # isort missing-required-import "I002", # isort missing-required-import
] ]
# First commit - only fixing isort # First commit - only fixing isort
lint.fixable = [ fixable = [
"I001", # isort basic checks "I001", # isort basic checks
] ]
lint.unfixable = [ unfixable = [
"B", # Avoid trying to fix flake8-bugbear (`B`) violations. "B", # Avoid trying to fix flake8-bugbear (`B`) violations.
]
exclude = [
"api/code_completions",
] ]
lint.exclude = ["api/code_completions"] ignore = [
"E999", "B008"
]
lint.ignore = ["E999", "B008"] [tool.ruff.per-file-ignores]
"__init__.py" = ["E402"] # Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`.
[tool.ruff.lint.per-file-ignores] # TODO: should decide if maybe not to ignore these.
"__init__.py" = [
"E402",
] # Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`.
[tool.bandit]
exclude_dirs = ["tests"]
skips = ["B101"]
tests = []

View File

@ -1,4 +1,3 @@
pytest==7.4.0 pytest==7.4.0
poetry poetry
twine twine
pre-commit>=4,<5

View File

@ -1,5 +1,5 @@
aiohttp==3.9.5 aiohttp==3.9.5
anthropic[vertex]==0.39.0 anthropic[vertex]==0.37.1
atlassian-python-api==3.41.4 atlassian-python-api==3.41.4
azure-devops==7.1.0b3 azure-devops==7.1.0b3
azure-identity==1.15.0 azure-identity==1.15.0
@ -12,17 +12,17 @@ google-cloud-aiplatform==1.38.0
google-generativeai==0.8.3 google-generativeai==0.8.3
google-cloud-storage==2.10.0 google-cloud-storage==2.10.0
Jinja2==3.1.2 Jinja2==3.1.2
litellm==1.52.0 litellm==1.50.2
loguru==0.7.2 loguru==0.7.2
msrest==0.7.1 msrest==0.7.1
openai==1.54.1 openai==1.52.1
pytest==7.4.0 pytest==7.4.0
PyGithub==1.59.* PyGithub==1.59.*
PyYAML==6.0.1 PyYAML==6.0.1
python-gitlab==3.15.0 python-gitlab==3.15.0
retry==0.9.2 retry==0.9.2
starlette-context==0.3.6 starlette-context==0.3.6
tiktoken==0.8.0 tiktoken==0.7.0
ujson==5.8.0 ujson==5.8.0
uvicorn==0.22.0 uvicorn==0.22.0
tenacity==8.2.3 tenacity==8.2.3

View File

@ -3,3 +3,4 @@
from setuptools import setup from setuptools import setup
setup() setup()
print("aaa")

View File

@ -32,3 +32,4 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
""" """

View File

@ -5,16 +5,16 @@ import time
from datetime import datetime from datetime import datetime
import jwt import jwt
import requests
from atlassian.bitbucket import Cloud from atlassian.bitbucket import Cloud
import requests
from requests.auth import HTTPBasicAuth from requests.auth import HTTPBasicAuth
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.log import get_logger, setup_logger from pr_agent.log import setup_logger, get_logger
from tests.e2e_tests.e2e_utils import (FILE_PATH, from tests.e2e_tests.e2e_utils import NEW_FILE_CONTENT, FILE_PATH, PR_HEADER_START_WITH, REVIEW_START_WITH, \
IMPROVE_START_WITH_REGEX_PATTERN, IMPROVE_START_WITH_REGEX_PATTERN, NUM_MINUTES
NEW_FILE_CONTENT, NUM_MINUTES,
PR_HEADER_START_WITH, REVIEW_START_WITH)
log_level = os.environ.get("LOG_LEVEL", "INFO") log_level = os.environ.get("LOG_LEVEL", "INFO")
setup_logger(log_level) setup_logger(log_level)

View File

@ -5,11 +5,9 @@ from datetime import datetime
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.git_providers import get_git_provider from pr_agent.git_providers import get_git_provider
from pr_agent.log import get_logger, setup_logger from pr_agent.log import setup_logger, get_logger
from tests.e2e_tests.e2e_utils import (FILE_PATH, from tests.e2e_tests.e2e_utils import NEW_FILE_CONTENT, FILE_PATH, PR_HEADER_START_WITH, REVIEW_START_WITH, \
IMPROVE_START_WITH_REGEX_PATTERN, IMPROVE_START_WITH_REGEX_PATTERN, NUM_MINUTES
NEW_FILE_CONTENT, NUM_MINUTES,
PR_HEADER_START_WITH, REVIEW_START_WITH)
log_level = os.environ.get("LOG_LEVEL", "INFO") log_level = os.environ.get("LOG_LEVEL", "INFO")
setup_logger(log_level) setup_logger(log_level)

View File

@ -7,11 +7,9 @@ import gitlab
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.git_providers import get_git_provider from pr_agent.git_providers import get_git_provider
from pr_agent.log import get_logger, setup_logger from pr_agent.log import setup_logger, get_logger
from tests.e2e_tests.e2e_utils import (FILE_PATH, from tests.e2e_tests.e2e_utils import NEW_FILE_CONTENT, FILE_PATH, PR_HEADER_START_WITH, REVIEW_START_WITH, \
IMPROVE_START_WITH_REGEX_PATTERN, IMPROVE_START_WITH_REGEX_PATTERN, NUM_MINUTES
NEW_FILE_CONTENT, NUM_MINUTES,
PR_HEADER_START_WITH, REVIEW_START_WITH)
log_level = os.environ.get("LOG_LEVEL", "INFO") log_level = os.environ.get("LOG_LEVEL", "INFO")
setup_logger(log_level) setup_logger(log_level)

View File

@ -1,10 +1,8 @@
from unittest.mock import MagicMock
from atlassian.bitbucket import Bitbucket
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
from pr_agent.git_providers import BitbucketServerProvider from pr_agent.git_providers import BitbucketServerProvider
from pr_agent.git_providers.bitbucket_provider import BitbucketProvider from pr_agent.git_providers.bitbucket_provider import BitbucketProvider
from unittest.mock import MagicMock
from atlassian.bitbucket import Bitbucket
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
class TestBitbucketProvider: class TestBitbucketProvider:

View File

@ -1,5 +1,4 @@
from unittest.mock import MagicMock from unittest.mock import MagicMock
from pr_agent.git_providers.codecommit_client import CodeCommitClient from pr_agent.git_providers.codecommit_client import CodeCommitClient

View File

@ -1,11 +1,9 @@
from unittest.mock import patch
import pytest import pytest
from unittest.mock import patch
from pr_agent.git_providers.codecommit_provider import CodeCommitFile
from pr_agent.git_providers.codecommit_provider import CodeCommitProvider
from pr_agent.git_providers.codecommit_provider import PullRequestCCMimic
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
from pr_agent.git_providers.codecommit_provider import (CodeCommitFile,
CodeCommitProvider,
PullRequestCCMimic)
class TestCodeCommitFile: class TestCodeCommitFile:

View File

@ -1,5 +1,4 @@
import pytest import pytest
from pr_agent.algo.git_patch_processing import extend_patch from pr_agent.algo.git_patch_processing import extend_patch
from pr_agent.algo.pr_processing import pr_generate_extended_diff from pr_agent.algo.pr_processing import pr_generate_extended_diff
from pr_agent.algo.token_handler import TokenHandler from pr_agent.algo.token_handler import TokenHandler

View File

@ -1,9 +1,7 @@
import pytest import pytest
from pr_agent.algo.file_filter import filter_ignored from pr_agent.algo.file_filter import filter_ignored
from pr_agent.config_loader import global_settings from pr_agent.config_loader import global_settings
class TestIgnoreFilter: class TestIgnoreFilter:
def test_no_ignores(self): def test_no_ignores(self):
""" """

View File

@ -1,10 +1,9 @@
# Generated by CodiumAI # Generated by CodiumAI
import pytest
from pr_agent.algo.types import FilePatchInfo from pr_agent.algo.types import FilePatchInfo
from pr_agent.algo.utils import find_line_number_of_relevant_line_in_file from pr_agent.algo.utils import find_line_number_of_relevant_line_in_file
import pytest
class TestFindLineNumberOfRelevantLineInFile: class TestFindLineNumberOfRelevantLineInFile:
# Tests that the function returns the correct line number and absolute position when the relevant line is found in the patch # Tests that the function returns the correct line number and absolute position when the relevant line is found in the patch

View File

@ -1,9 +1,7 @@
import json
import os import os
import json
from pr_agent.algo.utils import get_settings, github_action_output from pr_agent.algo.utils import get_settings, github_action_output
class TestGitHubOutput: class TestGitHubOutput:
def test_github_action_output_enabled(self, monkeypatch, tmp_path): def test_github_action_output_enabled(self, monkeypatch, tmp_path):
get_settings().set('GITHUB_ACTION_CONFIG.ENABLE_OUTPUT', True) get_settings().set('GITHUB_ACTION_CONFIG.ENABLE_OUTPUT', True)

View File

@ -47,3 +47,7 @@ PR Feedback:
expected_output = [{'relevant file': 'src/app.py:\n', 'suggestion content': 'The print statement is outside inside the if __name__ ==:'}] expected_output = [{'relevant file': 'src/app.py:\n', 'suggestion content': 'The print statement is outside inside the if __name__ ==:'}]
assert load_yaml(yaml_str) == expected_output assert load_yaml(yaml_str) == expected_output

View File

@ -1,10 +1,10 @@
# Generated by CodiumAI # Generated by CodiumAI
import pytest
from pr_agent.algo.utils import try_fix_yaml from pr_agent.algo.utils import try_fix_yaml
import pytest
class TestTryFixYaml: class TestTryFixYaml:
# The function successfully parses a valid YAML string. # The function successfully parses a valid YAML string.