codespector 0.1.2__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {codespector-0.1.2 → codespector-0.2.0}/PKG-INFO +1 -2
  2. codespector-0.2.0/cicd_examples/.github-ci.yml +64 -0
  3. codespector-0.2.0/cicd_examples/.gitlab-ci.yml +33 -0
  4. codespector-0.2.0/codespector/base.py +7 -0
  5. codespector-0.2.0/codespector/codespector.py +84 -0
  6. codespector-0.2.0/codespector/errors.py +14 -0
  7. codespector-0.1.2/codespector/local/prepare.py → codespector-0.2.0/codespector/local.py +10 -11
  8. {codespector-0.1.2 → codespector-0.2.0}/codespector/main.py +19 -24
  9. codespector-0.2.0/codespector/reviewer.py +84 -0
  10. codespector-0.2.0/codespector/types.py +50 -0
  11. {codespector-0.1.2 → codespector-0.2.0}/pyproject.toml +1 -2
  12. codespector-0.2.0/tests/unit/test_codespector.py +78 -0
  13. {codespector-0.1.2 → codespector-0.2.0}/uv.lock +83 -27
  14. codespector-0.1.2/codespector/clients/codestral.py +0 -5
  15. codespector-0.1.2/codespector/controller.py +0 -44
  16. codespector-0.1.2/codespector/local/__init__.py +0 -1
  17. codespector-0.1.2/codespector/local/main.py +0 -35
  18. codespector-0.1.2/codespector/local/reviewer.py +0 -108
  19. codespector-0.1.2/tests/unit/__init__.py +0 -0
  20. codespector-0.1.2/tests/unit/test_prepare.py +0 -26
  21. codespector-0.1.2/tests/unit/test_reviewer.py +0 -74
  22. {codespector-0.1.2 → codespector-0.2.0}/.github/workflows/build-publish.yml +0 -0
  23. {codespector-0.1.2 → codespector-0.2.0}/.github/workflows/tests.yml +0 -0
  24. {codespector-0.1.2 → codespector-0.2.0}/.gitignore +0 -0
  25. {codespector-0.1.2 → codespector-0.2.0}/LICENSE +0 -0
  26. {codespector-0.1.2 → codespector-0.2.0}/Makefile +0 -0
  27. {codespector-0.1.2 → codespector-0.2.0}/README.md +0 -0
  28. {codespector-0.1.2 → codespector-0.2.0}/codespector/__init__.py +0 -0
  29. {codespector-0.1.2/codespector/clients → codespector-0.2.0/tests}/__init__.py +0 -0
  30. {codespector-0.1.2 → codespector-0.2.0}/tests/conftest.py +0 -0
  31. {codespector-0.1.2/tests → codespector-0.2.0/tests/unit}/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codespector
3
- Version: 0.1.2
3
+ Version: 0.2.0
4
4
  Summary: Assistant for reviewing your code
5
5
  Project-URL: Repository, https://github.com/Vladimir-Titov/codespector
6
6
  Project-URL: Issues, https://github.com/Vladimir-Titov/codespector/issues
@@ -15,7 +15,6 @@ Requires-Dist: click>=8.1.8
15
15
  Requires-Dist: environs>=14.1.1
16
16
  Requires-Dist: loguru>=0.7.3
17
17
  Requires-Dist: requests>=2.32.3
18
- Requires-Dist: ujson>=5.10.0
19
18
  Description-Content-Type: text/markdown
20
19
 
21
20
  # CodeSpector
@@ -0,0 +1,64 @@
1
+ name: Codespector Review
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, synchronize]
6
+
7
+ env:
8
+ CODESPECTOR_RESULT_FILE: "codespector/result.md"
9
+ CODESPECTOR_COMBINED_FILE: "codespector/combined.json"
10
+
11
+ jobs:
12
+ codespector:
13
+ runs-on: ubuntu-latest
14
+
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ with:
18
+ fetch-depth: 0
19
+
20
+ - name: Set up Python
21
+ uses: actions/setup-python@v5
22
+ with:
23
+ python-version: '3.12'
24
+
25
+ - name: Install dependencies
26
+ run: |
27
+ sudo apt-get update
28
+ sudo apt-get install -y jq
29
+ pip install codespector
30
+
31
+ - name: Fetch target branch
32
+ run: git fetch origin ${{ github.base_ref }}
33
+
34
+ - name: Run Codespector
35
+ env:
36
+ CODESPECTOR_CHAT_TOKEN: ${{ secrets.CODESPECTOR_CHAT_TOKEN }}
37
+ CODESPECTOR_SYSTEM_CONTENT: ${{ secrets.CODESPECTOR_SYSTEM_CONTENT }}
38
+ CODESPECTOR_PROMPT_CONTENT: ${{ secrets.CODESPECTOR_PROMPT_CONTENT }}
39
+ CODESPECTOR_RESULT_FILE: ${{ secrets.CODESPECTOR_RESULT_FILE }}
40
+ CODESPECTOR_CHAT_AGENT: ${{ secrets.CODESPECTOR_CHAT_AGENT }}
41
+ run: |
42
+ codespector \
43
+ --chat-token $CODESPECTOR_CHAT_TOKEN \
44
+ --chat-agent $CODESPECTOR_CHAT_AGENT \
45
+ --compare-branch origin/${{ github.base_ref }}...HEAD
46
+ --system-content $CODESPECTOR_SYSTEM_CONTENT
47
+ --prompt-content $CODESPECTOR_PROMPT_CONTENT
48
+ --result-file $CODESPECTOR_RESULT_FILE
49
+
50
+ - name: Post review comment
51
+ env:
52
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
53
+ run: |
54
+ COMMENT_CONTENT=$(cat ${CODESPECTOR_RESULT_FILE} | jq -Rs .)
55
+ gh pr comment ${{ github.event.pull_request.number }} --body "${COMMENT_CONTENT}"
56
+
57
+ - name: Upload artifacts
58
+ uses: actions/upload-artifact@v4
59
+ with:
60
+ name: codespector-results
61
+ path: |
62
+ ${{ env.CODESPECTOR_RESULT_FILE }}
63
+ ${{ env.CODESPECTOR_COMBINED_FILE }}
64
+ retention-days: 1
@@ -0,0 +1,33 @@
1
+ workflow:
2
+ rules:
3
+ - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
4
+
5
+ variables:
6
+ GIT_DEPTH: 0
7
+ CODESPECTOR_RESULT_FILE: "codespector/result.md"
8
+ CODESPECTOR_COMBINED_FILE: "codespector/combined.json"
9
+
10
+ stages:
11
+ - codespector
12
+
13
+ codespector-job:
14
+ stage: codespector
15
+ image: python:3.12
16
+ script:
17
+ - apt-get update && apt-get install -y jq
18
+ - git fetch --no-tags origin $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
19
+ - pip install codespector
20
+ - codespector --chat-token $CODESPECTOR_CHAT_TOKEN --chat-agent codestral --compare-branch origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME...HEAD
21
+ - |
22
+ COMMENT_CONTENT=$(cat ${CODESPECTOR_RESULT_FILE} | jq -Rs .)
23
+ echo "Posting comment to MR..."
24
+ curl -X POST \
25
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
26
+ --header "Content-Type: application/json" \
27
+ --data "{\"body\": ${COMMENT_CONTENT}}" \
28
+ "$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes"
29
+ artifacts:
30
+ paths:
31
+ - ${CODESPECTOR_RESULT_FILE}
32
+ - ${CODESPECTOR_COMBINED_FILE}
33
+ expire_in: 10 minutes
@@ -0,0 +1,7 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class BasePipe(ABC):
5
+ @abstractmethod
6
+ def start(self, *args, **kwargs) -> None:
7
+ raise NotImplementedError
@@ -0,0 +1,84 @@
1
+ from codespector.base import BasePipe
2
+ from codespector.local import CodeSpectorDataPreparer
3
+ from codespector.reviewer import CodeSpectorReviewer
4
+ from codespector.types import AgentInfo
5
+
6
+
7
+ class CodeSpector:
8
+ def __init__(
9
+ self,
10
+ chat_token: str,
11
+ chat_agent: str,
12
+ compare_branch: str,
13
+ system_content: str,
14
+ prompt_content: str,
15
+ data_preparer: CodeSpectorDataPreparer,
16
+ reviewer: CodeSpectorReviewer,
17
+ pipeline: list[BasePipe],
18
+ result_file: str,
19
+ output_dir: str,
20
+ chat_model: str | None = None,
21
+ ):
22
+ self.chat_token = chat_token
23
+ self.chat_agent = chat_agent
24
+ self.compare_branch = compare_branch
25
+ self.output_dir = output_dir
26
+ self.system_content = system_content
27
+ self.prompt_content = prompt_content
28
+ self.data_preparer = data_preparer
29
+ self.reviewer = reviewer
30
+ self.pipeline = pipeline
31
+ self.result_file = result_file
32
+ self.chat_model = chat_model
33
+
34
+ @classmethod
35
+ def create(
36
+ cls,
37
+ chat_token: str,
38
+ chat_agent: str,
39
+ compare_branch: str,
40
+ system_content: str,
41
+ prompt_content: str,
42
+ result_file: str,
43
+ output_dir: str,
44
+ chat_model: str | None = None,
45
+ ) -> 'CodeSpector':
46
+ agent_info = AgentInfo.create(
47
+ chat_agent=chat_agent,
48
+ chat_token=chat_token,
49
+ chat_model=chat_model,
50
+ )
51
+ data_preparer = CodeSpectorDataPreparer(
52
+ output_dir=output_dir,
53
+ compare_branch=compare_branch,
54
+ )
55
+ reviewer = CodeSpectorReviewer(
56
+ diff_file=data_preparer.combined_file,
57
+ chat_token=chat_token,
58
+ chat_agent=chat_agent,
59
+ system_content=system_content,
60
+ output_dir=output_dir,
61
+ chat_model=chat_model,
62
+ agent_info=agent_info,
63
+ prompt_content=prompt_content,
64
+ result_file=result_file,
65
+ )
66
+ pipeline = [data_preparer, reviewer]
67
+
68
+ return CodeSpector(
69
+ chat_token=chat_token,
70
+ chat_agent=chat_agent,
71
+ chat_model=chat_model,
72
+ compare_branch=compare_branch,
73
+ system_content=system_content,
74
+ prompt_content=prompt_content,
75
+ data_preparer=data_preparer,
76
+ reviewer=reviewer,
77
+ pipeline=pipeline,
78
+ output_dir=output_dir,
79
+ result_file=result_file,
80
+ )
81
+
82
+ def review(self):
83
+ for pipe in self.pipeline:
84
+ pipe.start()
@@ -0,0 +1,14 @@
1
+ class NotValidCfgError(Exception):
2
+ """Exception raised when the configuration is not valid."""
3
+
4
+ def __init__(self, message):
5
+ super().__init__(message)
6
+ self.message = message
7
+
8
+
9
+ class AppError(Exception):
10
+ """Exception raised for application errors."""
11
+
12
+ def __init__(self, message):
13
+ super().__init__(message)
14
+ self.message = message
@@ -1,9 +1,11 @@
1
- import ujson
1
+ import json
2
2
  import subprocess
3
3
  import os
4
4
 
5
+ from codespector.base import BasePipe
5
6
 
6
- class CodeSpectorDataPreparer:
7
+
8
+ class CodeSpectorDataPreparer(BasePipe):
7
9
  def __init__(
8
10
  self,
9
11
  output_dir: str,
@@ -38,18 +40,18 @@ class CodeSpectorDataPreparer:
38
40
  diff_json = {'diff': '\n'.join(filtered_diff)}
39
41
  diff_filepath = os.path.join(self.output_dir, self.diff_file)
40
42
  with open(diff_filepath, 'w', encoding='utf-8') as f:
41
- ujson.dump(diff_json, f, indent=4, ensure_ascii=False)
43
+ json.dump(diff_json, f, indent=4, ensure_ascii=False)
42
44
 
43
45
  with open(os.path.join(self.output_dir, self.original_files_tmp), 'r', encoding='utf-8') as f:
44
- original_files_data = ujson.load(f)
46
+ original_files_data = json.load(f)
45
47
 
46
48
  with open(diff_filepath, 'r', encoding='utf-8') as f:
47
- diff_data = ujson.load(f)
49
+ diff_data = json.load(f)
48
50
 
49
51
  combined_data = {**original_files_data, **diff_data}
50
52
 
51
53
  with open(os.path.join(self.output_dir, self.combined_file), 'w', encoding='utf-8') as f:
52
- ujson.dump(combined_data, f, indent=4, ensure_ascii=False)
54
+ json.dump(combined_data, f, indent=4, ensure_ascii=False)
53
55
 
54
56
  def _prepare_name_only_file(self):
55
57
  changed_files = subprocess.run(
@@ -70,12 +72,9 @@ class CodeSpectorDataPreparer:
70
72
  filepath = os.path.join(self.output_dir, self.original_files_tmp)
71
73
 
72
74
  with open(filepath, 'w', encoding='utf-8') as f:
73
- ujson.dump(result, f, indent=4, ensure_ascii=False)
75
+ json.dump(result, f, indent=4, ensure_ascii=False)
74
76
 
75
- def prepare_data(self) -> str:
77
+ def start(self):
76
78
  self._prepare_dir()
77
79
  self._prepare_name_only_file()
78
80
  self._prepare_diff_file()
79
-
80
- def start(self):
81
- self.prepare_data()
@@ -3,8 +3,7 @@ from pathlib import Path
3
3
  import click
4
4
  from environs import Env
5
5
 
6
- from .controller import CodeSpectorController
7
- from loguru import logger
6
+ from codespector.codespector import CodeSpector
8
7
 
9
8
  BASE_PATH = Path(__file__).parent.parent
10
9
 
@@ -12,10 +11,23 @@ env = Env()
12
11
  env.read_env(path=str(BASE_PATH / '.env'))
13
12
 
14
13
 
14
+ @click.option(
15
+ '--result-file',
16
+ type=str,
17
+ help='Set file for saving the result',
18
+ envvar='CODESPECTOR_RESULT_FILE',
19
+ show_envvar=True,
20
+ )
21
+ @click.option(
22
+ '--prompt-content',
23
+ type=str,
24
+ help='Prompt content which included to review prompt',
25
+ envvar='CODESPECTOR_PROMPT_CONTENT',
26
+ show_envvar=True,
27
+ )
15
28
  @click.option(
16
29
  '--system-content',
17
30
  type=str,
18
- default='Ты код ревьювер. Отвечай на русском языке.',
19
31
  envvar='CODESPECTOR_SYSTEM_CONTENT',
20
32
  show_envvar=True,
21
33
  help='Content which used in system field for agent',
@@ -23,8 +35,8 @@ env.read_env(path=str(BASE_PATH / '.env'))
23
35
  @click.option(
24
36
  '--output-dir',
25
37
  type=str,
26
- default='codespector',
27
38
  envvar='CODESPECTOR_OUTPUT_DIR',
39
+ default='codespector',
28
40
  show_envvar=True,
29
41
  help='Select the output directory',
30
42
  )
@@ -32,16 +44,13 @@ env.read_env(path=str(BASE_PATH / '.env'))
32
44
  '-b',
33
45
  '--compare-branch',
34
46
  type=str,
35
- default='develop',
36
47
  help='Select the branch to compare the current one with',
37
48
  )
38
49
  @click.option(
39
50
  '--chat-agent',
40
- type=click.Choice(['codestral', 'chatgpt'], case_sensitive=False),
41
51
  envvar='CODESPECTOR_CHAT_AGENT',
42
52
  show_envvar=True,
43
- default='codestral',
44
- help='Choose the chat agent to use',
53
+ help='Choose the chat agent to use you can use one from [codestral, chatgpr, deepseek]. Or set yours chat agent',
45
54
  )
46
55
  @click.option(
47
56
  '--chat-model',
@@ -56,25 +65,11 @@ env.read_env(path=str(BASE_PATH / '.env'))
56
65
  envvar='CODESPECTOR_CHAT_TOKEN',
57
66
  show_envvar=True,
58
67
  )
59
- @click.option(
60
- '--mode',
61
- type=click.Choice(['local'], case_sensitive=False),
62
- default='local',
63
- help='Choose the mode of the application',
64
- )
65
68
  @click.version_option(message='%(version)s')
66
69
  @click.command()
67
70
  def main(*args, **kwargs):
68
- return start(*args, **kwargs)
69
-
70
-
71
- def start(*args, **kwargs):
72
- codespector = CodeSpectorController(*args, **kwargs)
73
- try:
74
- codespector.start()
75
- logger.info('Review completed successfully.See result.txt in {} directory', kwargs['output_dir'])
76
- except Exception as e:
77
- logger.error('Error while review: {}', e)
71
+ codespector = CodeSpector.create(*args, **kwargs)
72
+ codespector.review()
78
73
 
79
74
 
80
75
  if __name__ == '__main__':
@@ -0,0 +1,84 @@
1
+ import os.path
2
+
3
+ import json
4
+ from urllib.error import HTTPError
5
+
6
+ import requests
7
+
8
+ from loguru import logger
9
+
10
+ from codespector.errors import AppError
11
+ from codespector.types import AgentInfo
12
+
13
+
14
+ class CodeSpectorReviewer:
15
+ def __init__(
16
+ self,
17
+ diff_file: str,
18
+ chat_token: str,
19
+ chat_agent: str,
20
+ chat_model: str,
21
+ system_content: str,
22
+ prompt_content: str,
23
+ output_dir: str,
24
+ result_file: str,
25
+ agent_info: AgentInfo,
26
+ ):
27
+ self.diff_file = diff_file
28
+ self.chat_token = chat_token
29
+ self.chat_agent = chat_agent
30
+ self.chat_model = chat_model
31
+ self.system_content = system_content
32
+ self.prompt_content = prompt_content
33
+ self.output_dir = output_dir
34
+ self.result_file = result_file
35
+ self.agent_info = agent_info
36
+
37
+ self.request_file = 'request.json'
38
+ self.response_file = 'response.json'
39
+
40
+ def _request_to_chat_agent(self, prompt: str):
41
+ request_data = {
42
+ 'model': self.agent_info.model,
43
+ 'messages': [{'role': 'system', 'content': self.system_content}, {'role': 'user', 'content': prompt}],
44
+ }
45
+
46
+ with open(os.path.join(self.output_dir, self.request_file), 'w', encoding='utf-8') as f:
47
+ json.dump(request_data, f, indent=4, ensure_ascii=False)
48
+
49
+ response = requests.post(
50
+ self.agent_info.url,
51
+ json=request_data,
52
+ headers=self.agent_info.headers,
53
+ timeout=100,
54
+ )
55
+ response.raise_for_status()
56
+ return response
57
+
58
+ def send_to_review(self):
59
+ with open(os.path.join(self.output_dir, self.diff_file), 'r', encoding='utf-8') as f:
60
+ diff_data = json.load(f)
61
+
62
+ diff_content = diff_data.get('diff', '')
63
+ original_files = diff_data.get('original files', [])
64
+
65
+ original_files_str = json.dumps(original_files, indent=4, ensure_ascii=False)
66
+
67
+ prompt = f'{self.prompt_content}\n\nDIFF:\n{diff_content}\n\nORIGINAL FILES:\n{original_files_str}'
68
+ try:
69
+ response = self._request_to_chat_agent(prompt=prompt)
70
+ except HTTPError as e:
71
+ logger.error('Error while send request: {}', e)
72
+ raise AppError('Error while send request') from e
73
+
74
+ with open(os.path.join(self.output_dir, self.response_file), 'w', encoding='utf-8') as f:
75
+ json.dump(response.json(), f, indent=4, ensure_ascii=False)
76
+
77
+ resp = response.json()
78
+ clear_response = resp['choices'][0]['message']['content']
79
+
80
+ with open(os.path.join(self.output_dir, self.result_file), 'w', encoding='utf-8') as f:
81
+ f.write(clear_response)
82
+
83
+ def start(self):
84
+ self.send_to_review()
@@ -0,0 +1,50 @@
1
+ from dataclasses import dataclass
2
+ from loguru import logger
3
+ from codespector.errors import NotValidCfgError
4
+
5
+ AGENT_URL_MAPPING = {
6
+ 'codestral': 'https://api.mistral.ai/v1/chat/completions',
7
+ 'chatgpt': 'https://api.openai.com/v1/chat/completions',
8
+ 'deepseek': 'https://api.deepseek.com/v1/chat/completions',
9
+ }
10
+
11
+ DEFAULT_AGENT_MODEL = {
12
+ 'codestral': 'codestral-latest',
13
+ 'chatgpt': 'gpt-4o',
14
+ 'deepseek': 'deepseek-chat',
15
+ }
16
+
17
+
18
+ @dataclass
19
+ class AgentInfo:
20
+ model: str
21
+ url: str
22
+ headers: dict
23
+
24
+ @classmethod
25
+ def create(
26
+ cls,
27
+ chat_agent: str,
28
+ chat_token: str,
29
+ chat_model: str | None = None,
30
+ completion_url: str | None = None,
31
+ ) -> 'AgentInfo':
32
+ if completion_url:
33
+ url = completion_url
34
+ else:
35
+ url = AGENT_URL_MAPPING.get(chat_agent)
36
+
37
+ if chat_model:
38
+ model = chat_model
39
+ else:
40
+ model = DEFAULT_AGENT_MODEL.get(chat_agent)
41
+
42
+ if model is None or url is None:
43
+ raise NotValidCfgError('Invalid chat model or completions url')
44
+
45
+ headers = {'Authorization': f'Bearer {chat_token}'}
46
+ return cls(
47
+ url=url,
48
+ model=model,
49
+ headers=headers,
50
+ )
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "codespector"
3
- version = "0.1.2"
3
+ version = "0.2.0"
4
4
  authors = [
5
5
  { name = "vtitov", email = "v.v.titov94@gmail.com" }
6
6
  ]
@@ -12,7 +12,6 @@ dependencies = [
12
12
  "environs>=14.1.1",
13
13
  "loguru>=0.7.3",
14
14
  "requests>=2.32.3",
15
- "ujson>=5.10.0",
16
15
  ]
17
16
 
18
17
  classifiers = [
@@ -0,0 +1,78 @@
1
+ import pytest
2
+ from unittest.mock import Mock, patch
3
+ from codespector.codespector import CodeSpector
4
+ from codespector.types import AgentInfo
5
+ from codespector.errors import NotValidCfgError
6
+
7
+
8
+ @pytest.fixture
9
+ def mock_response():
10
+ return {'choices': [{'message': {'content': 'Test review content'}}]}
11
+
12
+
13
+ @pytest.fixture
14
+ def test_params():
15
+ return {
16
+ 'chat_token': 'test_token',
17
+ 'chat_agent': 'chatgpt',
18
+ 'compare_branch': 'main',
19
+ 'system_content': 'test system content',
20
+ 'prompt_content': 'test prompt content',
21
+ 'result_file': 'result.txt',
22
+ 'output_dir': 'test_output',
23
+ }
24
+
25
+
26
+ def test_codespector_create(test_params):
27
+ codespector = CodeSpector.create(**test_params)
28
+
29
+ assert codespector.chat_token == test_params['chat_token']
30
+ assert codespector.chat_agent == test_params['chat_agent']
31
+ assert codespector.compare_branch == test_params['compare_branch']
32
+ assert len(codespector.pipeline) == 2
33
+
34
+
35
+ @pytest.mark.parametrize(
36
+ 'chat_agent,chat_model,expected_url,expected_model',
37
+ [
38
+ ('chatgpt', None, 'https://api.openai.com/v1/chat/completions', 'gpt-4o'),
39
+ ('codestral', 'custom-model', 'https://api.mistral.ai/v1/chat/completions', 'custom-model'),
40
+ ('deepseek', None, 'https://api.deepseek.com/v1/chat/completions', 'deepseek-chat'),
41
+ ],
42
+ )
43
+ def test_agent_info_create(chat_agent, chat_model, expected_url, expected_model):
44
+ agent_info = AgentInfo.create(chat_agent=chat_agent, chat_token='test_token', chat_model=chat_model)
45
+
46
+ assert agent_info.url == expected_url
47
+ assert agent_info.model == expected_model
48
+ assert agent_info.headers == {'Authorization': 'Bearer test_token'}
49
+
50
+
51
+ def test_agent_info_create_invalid():
52
+ with pytest.raises(NotValidCfgError):
53
+ AgentInfo.create(chat_agent='invalid_agent', chat_token='test_token')
54
+
55
+
56
+ @patch('builtins.open', create=True)
57
+ @patch('subprocess.run')
58
+ @patch('os.path.exists')
59
+ @patch('os.makedirs')
60
+ def test_result_file_writing(mock_makedirs, mock_exists, mock_run, mock_open, test_params, mock_response):
61
+ mock_file = Mock()
62
+ mock_open.return_value.__enter__.return_value = mock_file
63
+ mock_exists.return_value = False
64
+
65
+ mock_run.return_value.stdout = 'test diff'
66
+
67
+ mock_file.read.return_value = '{"original files": [], "diff": "test diff"}'
68
+
69
+ with patch('requests.post') as mock_post:
70
+ mock_post.return_value.json.return_value = mock_response
71
+ mock_post.return_value.raise_for_status = Mock()
72
+
73
+ codespector = CodeSpector.create(**test_params)
74
+ codespector.review()
75
+
76
+ write_calls = [args[0] for name, args, kwargs in mock_file.write.mock_calls]
77
+
78
+ assert any('Test review content' in str(call) for call in write_calls)
@@ -1,5 +1,5 @@
1
1
  version = 1
2
- requires-python = ">=3.12"
2
+ requires-python = ">=3.10"
3
3
 
4
4
  [[package]]
5
5
  name = "certifi"
@@ -16,6 +16,32 @@ version = "3.4.1"
16
16
  source = { registry = "https://pypi.org/simple" }
17
17
  sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 }
18
18
  wheels = [
19
+ { url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013 },
20
+ { url = "https://files.pythonhosted.org/packages/d0/11/00341177ae71c6f5159a08168bcb98c6e6d196d372c94511f9f6c9afe0c6/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", size = 141285 },
21
+ { url = "https://files.pythonhosted.org/packages/01/09/11d684ea5819e5a8f5100fb0b38cf8d02b514746607934134d31233e02c8/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", size = 151449 },
22
+ { url = "https://files.pythonhosted.org/packages/08/06/9f5a12939db324d905dc1f70591ae7d7898d030d7662f0d426e2286f68c9/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", size = 143892 },
23
+ { url = "https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", size = 146123 },
24
+ { url = "https://files.pythonhosted.org/packages/a9/ac/ab729a15c516da2ab70a05f8722ecfccc3f04ed7a18e45c75bbbaa347d61/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", size = 147943 },
25
+ { url = "https://files.pythonhosted.org/packages/03/d2/3f392f23f042615689456e9a274640c1d2e5dd1d52de36ab8f7955f8f050/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", size = 142063 },
26
+ { url = "https://files.pythonhosted.org/packages/f2/e3/e20aae5e1039a2cd9b08d9205f52142329f887f8cf70da3650326670bddf/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", size = 150578 },
27
+ { url = "https://files.pythonhosted.org/packages/8d/af/779ad72a4da0aed925e1139d458adc486e61076d7ecdcc09e610ea8678db/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", size = 153629 },
28
+ { url = "https://files.pythonhosted.org/packages/c2/b6/7aa450b278e7aa92cf7732140bfd8be21f5f29d5bf334ae987c945276639/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", size = 150778 },
29
+ { url = "https://files.pythonhosted.org/packages/39/f4/d9f4f712d0951dcbfd42920d3db81b00dd23b6ab520419626f4023334056/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", size = 146453 },
30
+ { url = "https://files.pythonhosted.org/packages/49/2b/999d0314e4ee0cff3cb83e6bc9aeddd397eeed693edb4facb901eb8fbb69/charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", size = 95479 },
31
+ { url = "https://files.pythonhosted.org/packages/2d/ce/3cbed41cff67e455a386fb5e5dd8906cdda2ed92fbc6297921f2e4419309/charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", size = 102790 },
32
+ { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 },
33
+ { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 },
34
+ { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 },
35
+ { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 },
36
+ { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 },
37
+ { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 },
38
+ { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 },
39
+ { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 },
40
+ { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 },
41
+ { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 },
42
+ { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 },
43
+ { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 },
44
+ { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 },
19
45
  { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 },
20
46
  { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 },
21
47
  { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 },
@@ -59,14 +85,13 @@ wheels = [
59
85
 
60
86
  [[package]]
61
87
  name = "codespector"
62
- version = "0.1.0"
88
+ version = "0.2.0"
63
89
  source = { editable = "." }
64
90
  dependencies = [
65
91
  { name = "click" },
66
92
  { name = "environs" },
67
93
  { name = "loguru" },
68
94
  { name = "requests" },
69
- { name = "ujson" },
70
95
  ]
71
96
 
72
97
  [package.dev-dependencies]
@@ -81,7 +106,6 @@ requires-dist = [
81
106
  { name = "environs", specifier = ">=14.1.1" },
82
107
  { name = "loguru", specifier = ">=0.7.3" },
83
108
  { name = "requests", specifier = ">=2.32.3" },
84
- { name = "ujson", specifier = ">=5.10.0" },
85
109
  ]
86
110
 
87
111
  [package.metadata.requires-dev]
@@ -106,12 +130,22 @@ source = { registry = "https://pypi.org/simple" }
106
130
  dependencies = [
107
131
  { name = "marshmallow" },
108
132
  { name = "python-dotenv" },
133
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
109
134
  ]
110
135
  sdist = { url = "https://files.pythonhosted.org/packages/31/d3/e82bdbb8cc332e751f67a3f668c5d134d57f983497d9f3a59a375b6e8fd8/environs-14.1.1.tar.gz", hash = "sha256:03db7ee2d50ec697b68814cd175a3a05a7c7954804e4e419ca8b570dc5a835cf", size = 32050 }
111
136
  wheels = [
112
137
  { url = "https://files.pythonhosted.org/packages/f4/1c/ab9752f02d32d981d647c05822be9ff93809be8953dacea2da2bec9a9de9/environs-14.1.1-py3-none-any.whl", hash = "sha256:45bc56f1d53bbc59d8dd69bba97377dd88ec28b8229d81cedbd455b21789445b", size = 15566 },
113
138
  ]
114
139
 
140
+ [[package]]
141
+ name = "exceptiongroup"
142
+ version = "1.2.2"
143
+ source = { registry = "https://pypi.org/simple" }
144
+ sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 }
145
+ wheels = [
146
+ { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 },
147
+ ]
148
+
115
149
  [[package]]
116
150
  name = "idna"
117
151
  version = "3.10"
@@ -179,9 +213,11 @@ version = "8.3.5"
179
213
  source = { registry = "https://pypi.org/simple" }
180
214
  dependencies = [
181
215
  { name = "colorama", marker = "sys_platform == 'win32'" },
216
+ { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
182
217
  { name = "iniconfig" },
183
218
  { name = "packaging" },
184
219
  { name = "pluggy" },
220
+ { name = "tomli", marker = "python_full_version < '3.11'" },
185
221
  ]
186
222
  sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 }
187
223
  wheels = [
@@ -238,31 +274,51 @@ wheels = [
238
274
  ]
239
275
 
240
276
  [[package]]
241
- name = "ujson"
242
- version = "5.10.0"
277
+ name = "tomli"
278
+ version = "2.2.1"
279
+ source = { registry = "https://pypi.org/simple" }
280
+ sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 }
281
+ wheels = [
282
+ { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 },
283
+ { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 },
284
+ { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 },
285
+ { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 },
286
+ { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 },
287
+ { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 },
288
+ { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 },
289
+ { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 },
290
+ { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 },
291
+ { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 },
292
+ { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 },
293
+ { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 },
294
+ { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 },
295
+ { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 },
296
+ { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 },
297
+ { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 },
298
+ { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 },
299
+ { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 },
300
+ { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 },
301
+ { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 },
302
+ { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 },
303
+ { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 },
304
+ { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 },
305
+ { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 },
306
+ { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 },
307
+ { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 },
308
+ { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 },
309
+ { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 },
310
+ { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 },
311
+ { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 },
312
+ { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 },
313
+ ]
314
+
315
+ [[package]]
316
+ name = "typing-extensions"
317
+ version = "4.13.0"
243
318
  source = { registry = "https://pypi.org/simple" }
244
- sdist = { url = "https://files.pythonhosted.org/packages/f0/00/3110fd566786bfa542adb7932d62035e0c0ef662a8ff6544b6643b3d6fd7/ujson-5.10.0.tar.gz", hash = "sha256:b3cd8f3c5d8c7738257f1018880444f7b7d9b66232c64649f562d7ba86ad4bc1", size = 7154885 }
319
+ sdist = { url = "https://files.pythonhosted.org/packages/0e/3e/b00a62db91a83fff600de219b6ea9908e6918664899a2d85db222f4fbf19/typing_extensions-4.13.0.tar.gz", hash = "sha256:0a4ac55a5820789d87e297727d229866c9650f6521b64206413c4fbada24d95b", size = 106520 }
245
320
  wheels = [
246
- { url = "https://files.pythonhosted.org/packages/e8/a6/fd3f8bbd80842267e2d06c3583279555e8354c5986c952385199d57a5b6c/ujson-5.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98ba15d8cbc481ce55695beee9f063189dce91a4b08bc1d03e7f0152cd4bbdd5", size = 55642 },
247
- { url = "https://files.pythonhosted.org/packages/a8/47/dd03fd2b5ae727e16d5d18919b383959c6d269c7b948a380fdd879518640/ujson-5.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9d2edbf1556e4f56e50fab7d8ff993dbad7f54bac68eacdd27a8f55f433578e", size = 51807 },
248
- { url = "https://files.pythonhosted.org/packages/25/23/079a4cc6fd7e2655a473ed9e776ddbb7144e27f04e8fc484a0fb45fe6f71/ujson-5.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6627029ae4f52d0e1a2451768c2c37c0c814ffc04f796eb36244cf16b8e57043", size = 51972 },
249
- { url = "https://files.pythonhosted.org/packages/04/81/668707e5f2177791869b624be4c06fb2473bf97ee33296b18d1cf3092af7/ujson-5.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8ccb77b3e40b151e20519c6ae6d89bfe3f4c14e8e210d910287f778368bb3d1", size = 53686 },
250
- { url = "https://files.pythonhosted.org/packages/bd/50/056d518a386d80aaf4505ccf3cee1c40d312a46901ed494d5711dd939bc3/ujson-5.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3caf9cd64abfeb11a3b661329085c5e167abbe15256b3b68cb5d914ba7396f3", size = 58591 },
251
- { url = "https://files.pythonhosted.org/packages/fc/d6/aeaf3e2d6fb1f4cfb6bf25f454d60490ed8146ddc0600fae44bfe7eb5a72/ujson-5.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6e32abdce572e3a8c3d02c886c704a38a1b015a1fb858004e03d20ca7cecbb21", size = 997853 },
252
- { url = "https://files.pythonhosted.org/packages/f8/d5/1f2a5d2699f447f7d990334ca96e90065ea7f99b142ce96e85f26d7e78e2/ujson-5.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a65b6af4d903103ee7b6f4f5b85f1bfd0c90ba4eeac6421aae436c9988aa64a2", size = 1140689 },
253
- { url = "https://files.pythonhosted.org/packages/f2/2c/6990f4ccb41ed93744aaaa3786394bca0875503f97690622f3cafc0adfde/ujson-5.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:604a046d966457b6cdcacc5aa2ec5314f0e8c42bae52842c1e6fa02ea4bda42e", size = 1043576 },
254
- { url = "https://files.pythonhosted.org/packages/14/f5/a2368463dbb09fbdbf6a696062d0c0f62e4ae6fa65f38f829611da2e8fdd/ujson-5.10.0-cp312-cp312-win32.whl", hash = "sha256:6dea1c8b4fc921bf78a8ff00bbd2bfe166345f5536c510671bccececb187c80e", size = 38764 },
255
- { url = "https://files.pythonhosted.org/packages/59/2d/691f741ffd72b6c84438a93749ac57bf1a3f217ac4b0ea4fd0e96119e118/ujson-5.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:38665e7d8290188b1e0d57d584eb8110951a9591363316dd41cf8686ab1d0abc", size = 42211 },
256
- { url = "https://files.pythonhosted.org/packages/0d/69/b3e3f924bb0e8820bb46671979770c5be6a7d51c77a66324cdb09f1acddb/ujson-5.10.0-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:618efd84dc1acbd6bff8eaa736bb6c074bfa8b8a98f55b61c38d4ca2c1f7f287", size = 55646 },
257
- { url = "https://files.pythonhosted.org/packages/32/8a/9b748eb543c6cabc54ebeaa1f28035b1bd09c0800235b08e85990734c41e/ujson-5.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38d5d36b4aedfe81dfe251f76c0467399d575d1395a1755de391e58985ab1c2e", size = 51806 },
258
- { url = "https://files.pythonhosted.org/packages/39/50/4b53ea234413b710a18b305f465b328e306ba9592e13a791a6a6b378869b/ujson-5.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67079b1f9fb29ed9a2914acf4ef6c02844b3153913eb735d4bf287ee1db6e557", size = 51975 },
259
- { url = "https://files.pythonhosted.org/packages/b4/9d/8061934f960cdb6dd55f0b3ceeff207fcc48c64f58b43403777ad5623d9e/ujson-5.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7d0e0ceeb8fe2468c70ec0c37b439dd554e2aa539a8a56365fd761edb418988", size = 53693 },
260
- { url = "https://files.pythonhosted.org/packages/f5/be/7bfa84b28519ddbb67efc8410765ca7da55e6b93aba84d97764cd5794dbc/ujson-5.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59e02cd37bc7c44d587a0ba45347cc815fb7a5fe48de16bf05caa5f7d0d2e816", size = 58594 },
261
- { url = "https://files.pythonhosted.org/packages/48/eb/85d465abafb2c69d9699cfa5520e6e96561db787d36c677370e066c7e2e7/ujson-5.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a890b706b64e0065f02577bf6d8ca3b66c11a5e81fb75d757233a38c07a1f20", size = 997853 },
262
- { url = "https://files.pythonhosted.org/packages/9f/76/2a63409fc05d34dd7d929357b7a45e3a2c96f22b4225cd74becd2ba6c4cb/ujson-5.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:621e34b4632c740ecb491efc7f1fcb4f74b48ddb55e65221995e74e2d00bbff0", size = 1140694 },
263
- { url = "https://files.pythonhosted.org/packages/45/ed/582c4daba0f3e1688d923b5cb914ada1f9defa702df38a1916c899f7c4d1/ujson-5.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9500e61fce0cfc86168b248104e954fead61f9be213087153d272e817ec7b4f", size = 1043580 },
264
- { url = "https://files.pythonhosted.org/packages/d7/0c/9837fece153051e19c7bade9f88f9b409e026b9525927824cdf16293b43b/ujson-5.10.0-cp313-cp313-win32.whl", hash = "sha256:4c4fc16f11ac1612f05b6f5781b384716719547e142cfd67b65d035bd85af165", size = 38766 },
265
- { url = "https://files.pythonhosted.org/packages/d7/72/6cb6728e2738c05bbe9bd522d6fc79f86b9a28402f38663e85a28fddd4a0/ujson-5.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:4573fd1695932d4f619928fd09d5d03d917274381649ade4328091ceca175539", size = 42212 },
321
+ { url = "https://files.pythonhosted.org/packages/e0/86/39b65d676ec5732de17b7e3c476e45bb80ec64eb50737a8dce1a4178aba1/typing_extensions-4.13.0-py3-none-any.whl", hash = "sha256:c8dd92cc0d6425a97c18fbb9d1954e5ff92c1ca881a309c45f06ebc0b79058e5", size = 45683 },
266
322
  ]
267
323
 
268
324
  [[package]]
@@ -1,5 +0,0 @@
1
- import ujson
2
-
3
-
4
- def chat_completion(diff: str):
5
- pass
@@ -1,44 +0,0 @@
1
- from codespector import local
2
- from loguru import logger
3
-
4
-
5
- class CodeSpectorController:
6
- __slots__ = (
7
- 'mode',
8
- 'chat_token',
9
- 'chat_agent',
10
- 'compare_branch',
11
- 'output_dir',
12
- 'system_content',
13
- 'chat_model',
14
- )
15
-
16
- def __init__(
17
- self,
18
- mode: str,
19
- chat_token: str,
20
- chat_agent: str,
21
- compare_branch: str,
22
- output_dir: str,
23
- system_content: str,
24
- chat_model: str,
25
- ):
26
- self.mode = mode
27
- self.chat_token = chat_token
28
- self.chat_agent = chat_agent
29
- self.compare_branch = compare_branch
30
- self.output_dir = output_dir
31
- self.system_content = system_content
32
- self.chat_model = chat_model
33
-
34
- def start(self):
35
- codespector = local.LocalCodespector(
36
- chat_token=self.chat_token,
37
- chat_agent=self.chat_agent,
38
- compare_branch=self.compare_branch,
39
- output_dir=self.output_dir,
40
- system_content=self.system_content,
41
- chat_model=self.chat_model,
42
- )
43
- codespector.review()
44
- logger.info('Review completed successfully.See result.txt in {} directory', self.output_dir)
@@ -1 +0,0 @@
1
- from .main import LocalCodespector
@@ -1,35 +0,0 @@
1
- from .prepare import CodeSpectorDataPreparer
2
- from .reviewer import CodeSpectorReviewer
3
-
4
-
5
- class LocalCodespector:
6
- def __init__(
7
- self,
8
- chat_token: str,
9
- chat_agent: str,
10
- compare_branch: str,
11
- output_dir: str,
12
- system_content: str,
13
- chat_model: str,
14
- ):
15
- self.chat_token = chat_token
16
- self.chat_agent = chat_agent
17
- self.compare_branch = compare_branch
18
- self.output_dir = output_dir
19
- self.system_content = system_content
20
- self.chat_model = chat_model
21
-
22
- self.data_preparer = CodeSpectorDataPreparer(output_dir=self.output_dir, compare_branch=self.compare_branch)
23
- self.reviewer = CodeSpectorReviewer(
24
- diff_file=self.data_preparer.combined_file,
25
- chat_token=self.chat_token,
26
- chat_agent=self.chat_agent,
27
- system_content=self.system_content,
28
- output_dir=self.output_dir,
29
- chat_model=self.chat_model,
30
- )
31
- self.processes = [self.data_preparer, self.reviewer]
32
-
33
- def review(self):
34
- for process in self.processes:
35
- process.start()
@@ -1,108 +0,0 @@
1
- import os.path
2
- from dataclasses import dataclass
3
-
4
- import ujson
5
- import requests
6
-
7
- from loguru import logger
8
-
9
- AGENT_URL_MAPPING = {
10
- 'codestral': 'https://api.mistral.ai/v1/chat/completions',
11
- 'chatgpt': 'https://api.openai.com/v1/chat/completions',
12
- }
13
-
14
- DEFAULT_AGENT_MODEL = {'codestral': 'codestral-latest', 'chatgpt': 'gpt-4o'}
15
-
16
-
17
- @dataclass
18
- class AgentInfo:
19
- model: str
20
- url: str
21
- headers: dict
22
-
23
- @classmethod
24
- def create(cls, chat_agent: str, chat_token: str, chat_model: str | None = None) -> 'AgentInfo':
25
- url = AGENT_URL_MAPPING[chat_agent]
26
- model = chat_model if chat_model else DEFAULT_AGENT_MODEL[chat_agent]
27
- headers = {'Authorization': f'Bearer {chat_token}'}
28
- return cls(
29
- url=url,
30
- model=model,
31
- headers=headers,
32
- )
33
-
34
-
35
- class CodeSpectorReviewer:
36
- def __init__(
37
- self,
38
- diff_file: str,
39
- chat_token: str,
40
- chat_agent: str,
41
- chat_model: str | None,
42
- system_content: str,
43
- output_dir: str,
44
- ):
45
- self.diff_file = diff_file
46
- self.chat_token = chat_token
47
- self.chat_agent = chat_agent
48
- self.chat_model = chat_model
49
- self.system_content = system_content
50
- self.output_dir = output_dir
51
-
52
- self.request_file = 'request.json'
53
- self.response_file = 'response.json'
54
- self.result_file = 'result.md'
55
-
56
- def _request_to_chat_agent(self, prompt: str):
57
- agent_info = AgentInfo.create(self.chat_agent, self.chat_token, self.chat_model)
58
- request_data = {
59
- 'model': agent_info.model,
60
- 'messages': [{'role': 'system', 'content': self.system_content}, {'role': 'user', 'content': prompt}],
61
- }
62
-
63
- with open(os.path.join(self.output_dir, self.request_file), 'w', encoding='utf-8') as f:
64
- ujson.dump(request_data, f, indent=4, ensure_ascii=False)
65
-
66
- response = requests.post(
67
- agent_info.url,
68
- json=request_data,
69
- headers=agent_info.headers,
70
- timeout=100,
71
- )
72
- response.raise_for_status()
73
- return response
74
-
75
- def send_to_review(self):
76
- with open(os.path.join(self.output_dir, self.diff_file), 'r', encoding='utf-8') as f:
77
- diff_data = ujson.load(f)
78
-
79
- diff_content = diff_data.get('diff', '')
80
- original_files = diff_data.get('original files', [])
81
-
82
- original_files_str = ujson.dumps(original_files, indent=4, ensure_ascii=False)
83
-
84
- prompt = (
85
- 'Пожалуйста, проверь следующие изменения в коде на наличие очевидных проблем с качеством или безопасностью. '
86
- 'Предоставь краткий отчет в формате markdown:\n\n'
87
- 'DIFF:\n'
88
- f'{diff_content}\n\n'
89
- 'ORIGINAL FILES:\n'
90
- f'{original_files_str}'
91
- )
92
- try:
93
- response = self._request_to_chat_agent(prompt=prompt)
94
- except Exception as e:
95
- logger.error('Error while send request: {}', e)
96
- raise e
97
-
98
- with open(os.path.join(self.output_dir, self.response_file), 'w', encoding='utf-8') as f:
99
- ujson.dump(response.json(), f, indent=4, ensure_ascii=False)
100
-
101
- resp = response.json()
102
- clear_response = resp['choices'][0]['message']['content']
103
-
104
- with open(os.path.join(self.output_dir, self.result_file), 'w', encoding='utf-8') as f:
105
- f.write(clear_response)
106
-
107
- def start(self):
108
- self.send_to_review()
File without changes
@@ -1,26 +0,0 @@
1
- import pytest
2
- from unittest.mock import patch
3
- from codespector.local.prepare import CodeSpectorDataPreparer
4
-
5
-
6
- @pytest.fixture
7
- def preparer():
8
- return CodeSpectorDataPreparer(output_dir='test_output', compare_branch='develop')
9
-
10
-
11
- def test_prepare_dir(preparer):
12
- with patch('os.makedirs') as mock_makedirs, patch('os.path.exists', return_value=False):
13
- preparer._prepare_dir()
14
- mock_makedirs.assert_called_once_with('test_output')
15
-
16
-
17
- def test_prepare_data(preparer):
18
- with (
19
- patch.object(preparer, '_prepare_dir') as mock_prepare_dir,
20
- patch.object(preparer, '_prepare_name_only_file') as mock_prepare_name_only_file,
21
- patch.object(preparer, '_prepare_diff_file') as mock_prepare_diff_file,
22
- ):
23
- preparer.prepare_data()
24
- mock_prepare_dir.assert_called_once()
25
- mock_prepare_name_only_file.assert_called_once()
26
- mock_prepare_diff_file.assert_called_once()
@@ -1,74 +0,0 @@
1
- import pytest
2
- from unittest.mock import patch, mock_open
3
- from codespector.local.reviewer import CodeSpectorReviewer
4
- import os
5
- import ujson
6
- from unittest.mock import MagicMock
7
-
8
-
9
- @pytest.fixture
10
- def reviewer():
11
- return CodeSpectorReviewer(
12
- diff_file='diff.json',
13
- chat_token='test_token',
14
- chat_agent='codestral',
15
- chat_model=None,
16
- system_content='Test system content',
17
- output_dir='test_output',
18
- )
19
-
20
-
21
- def test_request_to_chat_agent(reviewer):
22
- prompt = 'Test prompt'
23
- mock_response = MagicMock()
24
- mock_response.json.return_value = {'choices': [{'message': {'content': 'Test response'}}]}
25
-
26
- with (
27
- patch('requests.post', return_value=mock_response) as mock_post,
28
- patch('builtins.open', mock_open()) as mock_file,
29
- ):
30
- response = reviewer._request_to_chat_agent(prompt)
31
-
32
- mock_post.assert_called_once_with(
33
- 'https://api.mistral.ai/v1/chat/completions',
34
- json={
35
- 'model': 'codestral-latest',
36
- 'messages': [{'role': 'system', 'content': 'Test system content'}, {'role': 'user', 'content': prompt}],
37
- },
38
- headers={'Authorization': 'Bearer test_token'},
39
- timeout=100,
40
- )
41
- mock_file.assert_called_with(os.path.join('test_output', 'request.json'), 'w', encoding='utf-8')
42
- assert response == mock_response
43
-
44
-
45
- # tests/unit/test_reviewer.py
46
- def test_send_to_review(reviewer):
47
- diff_data = {'diff': 'Test diff', 'original files': ['file1.py', 'file2.py']}
48
- mock_response = MagicMock()
49
- mock_response.json.return_value = {'choices': [{'message': {'content': 'Test response'}}]}
50
-
51
- with (
52
- patch('builtins.open', mock_open(read_data=ujson.dumps(diff_data))) as mock_file,
53
- patch.object(reviewer, '_request_to_chat_agent', return_value=mock_response) as mock_request,
54
- patch('ujson.dump') as _,
55
- ):
56
- reviewer.send_to_review()
57
-
58
- mock_file.assert_any_call(os.path.join('test_output', 'diff.json'), 'r', encoding='utf-8')
59
- mock_request.assert_called_once_with(
60
- prompt='Пожалуйста, проверь следующие изменения в коде на наличие очевидных проблем с качеством или безопасностью. '
61
- 'Предоставь краткий отчет в формате markdown:\n\n'
62
- 'DIFF:\n'
63
- 'Test diff\n\n'
64
- 'ORIGINAL FILES:\n'
65
- '[\n "file1.py",\n "file2.py"\n]'
66
- )
67
- mock_file.assert_any_call(os.path.join('test_output', 'response.json'), 'w', encoding='utf-8')
68
- mock_file.assert_any_call(os.path.join('test_output', 'result.md'), 'w', encoding='utf-8')
69
-
70
-
71
- def test_start(reviewer):
72
- with patch.object(reviewer, 'send_to_review') as mock_send_to_review:
73
- reviewer.start()
74
- mock_send_to_review.assert_called_once()
File without changes
File without changes
File without changes
File without changes