github-haystack 2.0.0__tar.gz → 2.1.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.
- {github_haystack-2.0.0 → github_haystack-2.1.0}/CHANGELOG.md +23 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/PKG-INFO +2 -1
- github_haystack-2.1.0/pydoc/config_docusaurus.yml +18 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/pyproject.toml +16 -3
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/components/connectors/github/file_editor.py +1 -1
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/components/connectors/github/issue_commenter.py +1 -1
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/components/connectors/github/issue_viewer.py +1 -1
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/components/connectors/github/pr_creator.py +6 -1
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/components/connectors/github/repo_forker.py +1 -1
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/components/connectors/github/repo_viewer.py +1 -1
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/tools/github/file_editor_tool.py +1 -1
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/tools/github/issue_commenter_tool.py +1 -1
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/tools/github/issue_viewer_tool.py +1 -1
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/tools/github/pr_creator_tool.py +1 -1
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/tools/github/repo_forker_tool.py +1 -1
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/tools/github/repo_viewer_tool.py +1 -1
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/tools/github/utils.py +2 -2
- {github_haystack-2.0.0 → github_haystack-2.1.0}/tests/test_file_editor.py +91 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/tests/test_file_editor_tool.py +7 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/tests/test_pr_creator.py +79 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/tests/test_repo_forker.py +54 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/tests/test_repo_viewer.py +49 -0
- github_haystack-2.1.0/tests/test_utils.py +63 -0
- github_haystack-2.0.0/pydoc/config_docusaurus.yml +0 -33
- {github_haystack-2.0.0 → github_haystack-2.1.0}/.gitignore +0 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/LICENSE.txt +0 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/README.md +0 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/components/connectors/github/__init__.py +0 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/components/connectors/py.typed +0 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/prompts/github/__init__.py +0 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/prompts/github/context_prompt.py +0 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/prompts/github/file_editor_prompt.py +0 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/prompts/github/issue_commenter_prompt.py +0 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/prompts/github/issue_viewer_prompt.py +0 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/prompts/github/pr_creator_prompt.py +0 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/prompts/github/repo_forker_prompt.py +0 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/prompts/github/repo_viewer_prompt.py +0 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/prompts/github/system_prompt.py +0 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/prompts/py.typed +0 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/tools/github/__init__.py +0 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/tools/py.typed +0 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/tests/__init__.py +0 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/tests/test_issue_commenter.py +0 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/tests/test_issue_commenter_tool.py +0 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/tests/test_issue_viewer.py +0 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/tests/test_issue_viewer_tool.py +0 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/tests/test_pr_creator_tool.py +0 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/tests/test_repo_forker_tool.py +0 -0
- {github_haystack-2.0.0 → github_haystack-2.1.0}/tests/test_repo_viewer_tool.py +0 -0
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [integrations/github-v2.0.0] - 2026-01-12
|
|
4
|
+
|
|
5
|
+
### 📚 Documentation
|
|
6
|
+
|
|
7
|
+
- Add pydoc configurations for Docusaurus (#2411)
|
|
8
|
+
|
|
9
|
+
### ⚙️ CI
|
|
10
|
+
|
|
11
|
+
- Change pytest command (#2475)
|
|
12
|
+
|
|
13
|
+
### 🧹 Chores
|
|
14
|
+
|
|
15
|
+
- Remove Readme API CI workflow and configs (#2573)
|
|
16
|
+
- Fix nightly test (#2580)
|
|
17
|
+
- Make fmt command more forgiving (#2671)
|
|
18
|
+
- [**breaking**] Github - drop Python 3.9 and use X|Y typing (#2705)
|
|
19
|
+
|
|
20
|
+
### 🌀 Miscellaneous
|
|
21
|
+
|
|
22
|
+
- Adopt PEP 585 type hinting (part 3) (#2510)
|
|
23
|
+
- Chore: Fix tool validation test failures in github integration (#2659)
|
|
24
|
+
- Fix: Fix github integration unit tests (#2703)
|
|
25
|
+
|
|
3
26
|
## [integrations/github-v1.2.1] - 2025-10-20
|
|
4
27
|
|
|
5
28
|
### 🚀 Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: github-haystack
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: Haystack components for interacting with GitHub repositories
|
|
5
5
|
Project-URL: Source, https://github.com/deepset-ai/haystack-core-integrations/github
|
|
6
6
|
Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/github/README.md
|
|
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
19
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
19
20
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
20
21
|
Requires-Python: >=3.10
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
loaders:
|
|
2
|
+
- modules:
|
|
3
|
+
- haystack_integrations.components.connectors.github.file_editor
|
|
4
|
+
- haystack_integrations.components.connectors.github.issue_commenter
|
|
5
|
+
- haystack_integrations.components.connectors.github.issue_viewer
|
|
6
|
+
- haystack_integrations.components.connectors.github.pr_creator
|
|
7
|
+
- haystack_integrations.components.connectors.github.repo_viewer
|
|
8
|
+
- haystack_integrations.components.connectors.github.repo_forker
|
|
9
|
+
search_path: [../src]
|
|
10
|
+
processors:
|
|
11
|
+
- type: filter
|
|
12
|
+
documented_only: true
|
|
13
|
+
skip_empty_modules: true
|
|
14
|
+
renderer:
|
|
15
|
+
description: GitHub integration for Haystack
|
|
16
|
+
id: integrations-github
|
|
17
|
+
filename: github.md
|
|
18
|
+
title: GitHub
|
|
@@ -19,6 +19,7 @@ classifiers = [
|
|
|
19
19
|
"Programming Language :: Python :: 3.11",
|
|
20
20
|
"Programming Language :: Python :: 3.12",
|
|
21
21
|
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Programming Language :: Python :: 3.14",
|
|
22
23
|
"Programming Language :: Python :: Implementation :: CPython",
|
|
23
24
|
"Programming Language :: Python :: Implementation :: PyPy",
|
|
24
25
|
]
|
|
@@ -45,7 +46,7 @@ installer = "uv"
|
|
|
45
46
|
dependencies = ["haystack-pydoc-tools", "ruff"]
|
|
46
47
|
|
|
47
48
|
[tool.hatch.envs.default.scripts]
|
|
48
|
-
docs = ["pydoc
|
|
49
|
+
docs = ["haystack-pydoc pydoc/config_docusaurus.yml"]
|
|
49
50
|
fmt = "ruff check --fix {args}; ruff format {args}"
|
|
50
51
|
fmt-check = "ruff check {args} && ruff format --check {args}"
|
|
51
52
|
|
|
@@ -63,7 +64,8 @@ dependencies = [
|
|
|
63
64
|
unit = 'pytest -m "not integration" {args:tests}'
|
|
64
65
|
integration = 'pytest -m "integration" {args:tests}'
|
|
65
66
|
all = 'pytest {args:tests}'
|
|
66
|
-
cov-retry = 'pytest --cov=haystack_integrations --reruns 3 --reruns-delay 30 -x {args:tests}'
|
|
67
|
+
unit-cov-retry = 'pytest --cov=haystack_integrations --reruns 3 --reruns-delay 30 -x -m "not integration" {args:tests}'
|
|
68
|
+
integration-cov-append-retry = 'pytest --cov=haystack_integrations --cov-append --reruns 3 --reruns-delay 30 -x -m "integration" {args:tests}'
|
|
67
69
|
|
|
68
70
|
types = """mypy -p haystack_integrations.components.connectors.github \
|
|
69
71
|
-p haystack_integrations.prompts.github \
|
|
@@ -81,9 +83,17 @@ line-length = 120
|
|
|
81
83
|
[tool.ruff.lint]
|
|
82
84
|
select = [
|
|
83
85
|
"A",
|
|
86
|
+
"ANN",
|
|
84
87
|
"ARG",
|
|
85
88
|
"B",
|
|
86
89
|
"C",
|
|
90
|
+
"D102", # Missing docstring in public method
|
|
91
|
+
"D103", # Missing docstring in public function
|
|
92
|
+
"D205", # 1 blank line required between summary line and description
|
|
93
|
+
"D209", # Closing triple quotes go to new line
|
|
94
|
+
"D213", # summary lines must be positioned on the second physical line of the docstring
|
|
95
|
+
"D417", # Missing argument descriptions in the docstring
|
|
96
|
+
"D419", # Docstring is empty
|
|
87
97
|
"DTZ",
|
|
88
98
|
"E",
|
|
89
99
|
"EM",
|
|
@@ -121,6 +131,8 @@ ignore = [
|
|
|
121
131
|
# Misc
|
|
122
132
|
"B008",
|
|
123
133
|
"S101",
|
|
134
|
+
# Allow `Any` - used legitimately for dynamic types and SDK boundaries
|
|
135
|
+
"ANN401",
|
|
124
136
|
]
|
|
125
137
|
|
|
126
138
|
[tool.ruff.lint.isort]
|
|
@@ -131,13 +143,14 @@ ban-relative-imports = "parents"
|
|
|
131
143
|
|
|
132
144
|
[tool.ruff.lint.per-file-ignores]
|
|
133
145
|
# Tests can use magic values, assertions, and relative imports
|
|
134
|
-
"tests/**/*" = ["PLR2004", "S101", "TID252"]
|
|
146
|
+
"tests/**/*" = ["D", "PLR2004", "S101", "TID252", "ANN"]
|
|
135
147
|
# Ignore RUF001 for all files in the prompts directory
|
|
136
148
|
"src/haystack_integrations/components/prompts/**/*" = ["RUF001"]
|
|
137
149
|
|
|
138
150
|
[tool.coverage.run]
|
|
139
151
|
source = ["haystack_integrations"]
|
|
140
152
|
branch = true
|
|
153
|
+
relative_files = true
|
|
141
154
|
parallel = false
|
|
142
155
|
|
|
143
156
|
|
|
@@ -38,7 +38,12 @@ class GitHubPRCreator:
|
|
|
38
38
|
```
|
|
39
39
|
"""
|
|
40
40
|
|
|
41
|
-
def __init__(
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
*,
|
|
44
|
+
github_token: Secret = Secret.from_env_var("GITHUB_TOKEN"),
|
|
45
|
+
raise_on_failure: bool = True,
|
|
46
|
+
) -> None:
|
|
42
47
|
"""
|
|
43
48
|
Initialize the component.
|
|
44
49
|
|
|
@@ -31,7 +31,7 @@ class GitHubFileEditorTool(ComponentTool):
|
|
|
31
31
|
outputs_to_string: dict[str, str | Callable[[Any], str]] | None = None,
|
|
32
32
|
inputs_from_state: dict[str, str] | None = None,
|
|
33
33
|
outputs_to_state: dict[str, dict[str, str | Callable]] | None = None,
|
|
34
|
-
):
|
|
34
|
+
) -> None:
|
|
35
35
|
"""
|
|
36
36
|
Initialize the GitHub file editor tool.
|
|
37
37
|
|
|
@@ -30,7 +30,7 @@ class GitHubIssueCommenterTool(ComponentTool):
|
|
|
30
30
|
outputs_to_string: dict[str, str | Callable[[Any], str]] | None = None,
|
|
31
31
|
inputs_from_state: dict[str, str] | None = None,
|
|
32
32
|
outputs_to_state: dict[str, dict[str, str | Callable]] | None = None,
|
|
33
|
-
):
|
|
33
|
+
) -> None:
|
|
34
34
|
"""
|
|
35
35
|
Initialize the GitHub issue commenter tool.
|
|
36
36
|
|
|
@@ -30,7 +30,7 @@ class GitHubIssueViewerTool(ComponentTool):
|
|
|
30
30
|
outputs_to_string: dict[str, str | Callable[[Any], str]] | None = None,
|
|
31
31
|
inputs_from_state: dict[str, str] | None = None,
|
|
32
32
|
outputs_to_state: dict[str, dict[str, str | Callable]] | None = None,
|
|
33
|
-
):
|
|
33
|
+
) -> None:
|
|
34
34
|
"""
|
|
35
35
|
Initialize the GitHub issue viewer tool.
|
|
36
36
|
|
|
@@ -29,7 +29,7 @@ class GitHubPRCreatorTool(ComponentTool):
|
|
|
29
29
|
outputs_to_string: dict[str, str | Callable[[Any], str]] | None = None,
|
|
30
30
|
inputs_from_state: dict[str, str] | None = None,
|
|
31
31
|
outputs_to_state: dict[str, dict[str, str | Callable]] | None = None,
|
|
32
|
-
):
|
|
32
|
+
) -> None:
|
|
33
33
|
"""
|
|
34
34
|
Initialize the GitHub PR creator tool.
|
|
35
35
|
|
|
@@ -29,7 +29,7 @@ class GitHubRepoForkerTool(ComponentTool):
|
|
|
29
29
|
outputs_to_string: dict[str, str | Callable[[Any], str]] | None = None,
|
|
30
30
|
inputs_from_state: dict[str, str] | None = None,
|
|
31
31
|
outputs_to_state: dict[str, dict[str, str | Callable]] | None = None,
|
|
32
|
-
):
|
|
32
|
+
) -> None:
|
|
33
33
|
"""
|
|
34
34
|
Initialize the GitHub Repo Forker tool.
|
|
35
35
|
|
|
@@ -32,7 +32,7 @@ class GitHubRepoViewerTool(ComponentTool):
|
|
|
32
32
|
outputs_to_string: dict[str, str | Callable[[Any], str]] | None = None,
|
|
33
33
|
inputs_from_state: dict[str, str] | None = None,
|
|
34
34
|
outputs_to_state: dict[str, dict[str, str | Callable]] | None = None,
|
|
35
|
-
):
|
|
35
|
+
) -> None:
|
|
36
36
|
"""
|
|
37
37
|
Initialize the GitHub repository viewer tool.
|
|
38
38
|
|
{github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/tools/github/utils.py
RENAMED
|
@@ -21,7 +21,7 @@ def message_handler(documents: list[Document], max_length: int = 150_000) -> str
|
|
|
21
21
|
result_str = ""
|
|
22
22
|
for document in documents:
|
|
23
23
|
if document.meta["type"] in ["file", "dir", "error"]:
|
|
24
|
-
result_str += document.content or "" + "\n"
|
|
24
|
+
result_str += (document.content or "") + "\n"
|
|
25
25
|
else:
|
|
26
26
|
result_str += f"File Content for {document.meta['path']}\n\n"
|
|
27
27
|
result_str += document.content or ""
|
|
@@ -29,7 +29,7 @@ def message_handler(documents: list[Document], max_length: int = 150_000) -> str
|
|
|
29
29
|
if len(result_str) > max_length:
|
|
30
30
|
result_str = result_str[:max_length] + "...(large file can't be fully displayed)"
|
|
31
31
|
|
|
32
|
-
return result_str
|
|
32
|
+
return result_str.strip()
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
def serialize_handlers(
|
|
@@ -280,3 +280,94 @@ class TestGitHubFileEditor:
|
|
|
280
280
|
assert "Authorization" not in headers
|
|
281
281
|
assert headers["Accept"] == "application/vnd.github.v3+json"
|
|
282
282
|
assert headers["User-Agent"] == "Haystack/GitHubFileEditor"
|
|
283
|
+
|
|
284
|
+
@pytest.mark.parametrize(
|
|
285
|
+
"file_b64,original,expected",
|
|
286
|
+
[
|
|
287
|
+
("SGVsbG8gV29ybGQ=", "missing", "Error: Original string not found in file"),
|
|
288
|
+
("SGVsbG8gSGVsbG8=", "Hello", "Error: Original string appears multiple times. Please provide more context"),
|
|
289
|
+
],
|
|
290
|
+
)
|
|
291
|
+
@patch("requests.get")
|
|
292
|
+
def test_run_edit_string_edge_cases(self, mock_get, file_b64, original, expected, monkeypatch):
|
|
293
|
+
monkeypatch.setenv("GITHUB_TOKEN", "test-token")
|
|
294
|
+
mock_get.return_value.json.return_value = {"content": file_b64, "sha": "abc123"}
|
|
295
|
+
mock_get.return_value.raise_for_status.return_value = None
|
|
296
|
+
|
|
297
|
+
editor = GitHubFileEditor()
|
|
298
|
+
result = editor.run(
|
|
299
|
+
command=Command.EDIT,
|
|
300
|
+
payload={"path": "f.txt", "original": original, "replacement": "X", "message": "m"},
|
|
301
|
+
repo="owner/repo",
|
|
302
|
+
branch="main",
|
|
303
|
+
)
|
|
304
|
+
assert result["result"] == expected
|
|
305
|
+
|
|
306
|
+
@patch("requests.get")
|
|
307
|
+
def test_run_undo_not_last_user(self, mock_get, monkeypatch):
|
|
308
|
+
monkeypatch.setenv("GITHUB_TOKEN", "test-token")
|
|
309
|
+
|
|
310
|
+
mock_get.return_value.raise_for_status.return_value = None
|
|
311
|
+
mock_get.return_value.json.side_effect = [
|
|
312
|
+
[{"author": {"login": "different_user"}, "sha": "sha1"}],
|
|
313
|
+
{"login": "another_user"},
|
|
314
|
+
]
|
|
315
|
+
|
|
316
|
+
editor = GitHubFileEditor()
|
|
317
|
+
result = editor.run(command=Command.UNDO, payload={"message": "m"}, repo="owner/repo", branch="main")
|
|
318
|
+
assert result["result"] == "Error: Last commit was not made by the current user"
|
|
319
|
+
|
|
320
|
+
@pytest.mark.parametrize(
|
|
321
|
+
"kwargs,expected_substring",
|
|
322
|
+
[
|
|
323
|
+
({"command": Command.EDIT, "payload": {}}, "Error: No repository specified"),
|
|
324
|
+
(
|
|
325
|
+
{"command": "bogus", "payload": {}, "repo": "owner/repo"},
|
|
326
|
+
None,
|
|
327
|
+
),
|
|
328
|
+
],
|
|
329
|
+
)
|
|
330
|
+
def test_run_validation_errors(self, kwargs, expected_substring, monkeypatch):
|
|
331
|
+
monkeypatch.setenv("GITHUB_TOKEN", "test-token")
|
|
332
|
+
editor = GitHubFileEditor()
|
|
333
|
+
if expected_substring is None:
|
|
334
|
+
with pytest.raises(ValueError):
|
|
335
|
+
editor.run(**kwargs)
|
|
336
|
+
else:
|
|
337
|
+
result = editor.run(**kwargs)
|
|
338
|
+
assert expected_substring in result["result"]
|
|
339
|
+
|
|
340
|
+
@patch("requests.get")
|
|
341
|
+
def test_run_command_as_string(self, mock_get, monkeypatch):
|
|
342
|
+
monkeypatch.setenv("GITHUB_TOKEN", "test-token")
|
|
343
|
+
mock_get.return_value.json.return_value = {"content": "SGVsbG8=", "sha": "abc"}
|
|
344
|
+
mock_get.return_value.raise_for_status.return_value = None
|
|
345
|
+
|
|
346
|
+
editor = GitHubFileEditor(repo="owner/repo")
|
|
347
|
+
with patch.object(editor, "_update_file", return_value=True):
|
|
348
|
+
result = editor.run(
|
|
349
|
+
command="EDIT",
|
|
350
|
+
payload={"path": "f.txt", "original": "Hello", "replacement": "Hi", "message": "m"},
|
|
351
|
+
)
|
|
352
|
+
assert result["result"] == "Edit successful"
|
|
353
|
+
|
|
354
|
+
@pytest.mark.parametrize(
|
|
355
|
+
"command,payload",
|
|
356
|
+
[
|
|
357
|
+
(Command.UNDO, {"message": "m"}),
|
|
358
|
+
(Command.CREATE, {"path": "n.txt", "content": "x", "message": "m"}),
|
|
359
|
+
(Command.DELETE, {"path": "n.txt", "message": "m"}),
|
|
360
|
+
],
|
|
361
|
+
)
|
|
362
|
+
@patch("requests.get")
|
|
363
|
+
def test_run_command_error_handling_no_raise(self, mock_get, command, payload, monkeypatch):
|
|
364
|
+
monkeypatch.setenv("GITHUB_TOKEN", "test-token")
|
|
365
|
+
mock_get.side_effect = requests.RequestException("API Error")
|
|
366
|
+
|
|
367
|
+
editor = GitHubFileEditor(raise_on_failure=False)
|
|
368
|
+
with (
|
|
369
|
+
patch("requests.put", side_effect=requests.RequestException("API Error")),
|
|
370
|
+
patch("requests.delete", side_effect=requests.RequestException("API Error")),
|
|
371
|
+
):
|
|
372
|
+
result = editor.run(command=command, payload=payload, repo="owner/repo", branch="main")
|
|
373
|
+
assert "Error: API Error" in result["result"]
|
|
@@ -205,6 +205,13 @@ class TestGitHubFileEditorTool:
|
|
|
205
205
|
"connection_type_validation": True,
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
# Compatibility with newer versions of Haystack that include these parameters
|
|
209
|
+
for key in ["confirmation_strategies", "required_variables", "user_prompt"]:
|
|
210
|
+
if key in pipeline_dict["components"]["agent"]["init_parameters"]:
|
|
211
|
+
expected_dict["components"]["agent"]["init_parameters"][key] = pipeline_dict["components"]["agent"][
|
|
212
|
+
"init_parameters"
|
|
213
|
+
][key]
|
|
214
|
+
|
|
208
215
|
assert pipeline_dict == expected_dict
|
|
209
216
|
|
|
210
217
|
deserialized_pipeline = Pipeline.from_dict(pipeline_dict)
|
|
@@ -143,3 +143,82 @@ class TestGitHubPRCreator:
|
|
|
143
143
|
assert "Authorization" not in headers
|
|
144
144
|
assert headers["Accept"] == "application/vnd.github.v3+json"
|
|
145
145
|
assert headers["User-Agent"] == "Haystack/GitHubPRCreator"
|
|
146
|
+
|
|
147
|
+
def test_parse_issue_url_invalid(self, monkeypatch):
|
|
148
|
+
monkeypatch.setenv("GITHUB_TOKEN", "test-token")
|
|
149
|
+
pr_creator = GitHubPRCreator()
|
|
150
|
+
with pytest.raises(ValueError, match="Invalid GitHub issue URL format"):
|
|
151
|
+
pr_creator._parse_issue_url("https://github.com/owner/repo/pull/1")
|
|
152
|
+
|
|
153
|
+
@patch("requests.get")
|
|
154
|
+
def test_run_returns_error_when_fork_not_found(self, mock_get, monkeypatch):
|
|
155
|
+
monkeypatch.setenv("GITHUB_TOKEN", "test-token")
|
|
156
|
+
mock_get.return_value.json.return_value = {"login": "test_user"}
|
|
157
|
+
mock_get.return_value.raise_for_status.return_value = None
|
|
158
|
+
|
|
159
|
+
pr_creator = GitHubPRCreator()
|
|
160
|
+
with patch.object(pr_creator, "_check_fork_exists", return_value=False):
|
|
161
|
+
result = pr_creator.run(
|
|
162
|
+
issue_url="https://github.com/owner/repo/issues/456",
|
|
163
|
+
title="Test PR",
|
|
164
|
+
branch="feature-branch",
|
|
165
|
+
base="main",
|
|
166
|
+
)
|
|
167
|
+
assert result == {"result": "Error: Fork not found at test_user/repo"}
|
|
168
|
+
|
|
169
|
+
@pytest.mark.parametrize(
|
|
170
|
+
"fork_payload,raises_request,expected",
|
|
171
|
+
[
|
|
172
|
+
({"fork": True}, False, True),
|
|
173
|
+
({"fork": False}, False, False),
|
|
174
|
+
({}, False, False),
|
|
175
|
+
(None, True, False),
|
|
176
|
+
],
|
|
177
|
+
)
|
|
178
|
+
@patch("requests.get")
|
|
179
|
+
def test_check_fork_exists(self, mock_get, fork_payload, raises_request, expected, monkeypatch):
|
|
180
|
+
monkeypatch.setenv("GITHUB_TOKEN", "test-token")
|
|
181
|
+
if raises_request:
|
|
182
|
+
mock_get.side_effect = requests.RequestException("boom")
|
|
183
|
+
else:
|
|
184
|
+
mock_get.return_value.json.return_value = fork_payload
|
|
185
|
+
mock_get.return_value.raise_for_status.return_value = None
|
|
186
|
+
|
|
187
|
+
pr_creator = GitHubPRCreator()
|
|
188
|
+
assert pr_creator._check_fork_exists("repo", "test_user") is expected
|
|
189
|
+
|
|
190
|
+
@pytest.mark.parametrize("raise_on_failure", [True, False])
|
|
191
|
+
@patch("requests.post")
|
|
192
|
+
def test_create_fork(self, mock_post, raise_on_failure, monkeypatch):
|
|
193
|
+
monkeypatch.setenv("GITHUB_TOKEN", "test-token")
|
|
194
|
+
mock_post.return_value.json.return_value = {"owner": {"login": "test_user"}}
|
|
195
|
+
mock_post.return_value.raise_for_status.return_value = None
|
|
196
|
+
|
|
197
|
+
pr_creator = GitHubPRCreator(raise_on_failure=raise_on_failure)
|
|
198
|
+
assert pr_creator._create_fork("owner", "repo") == "test_user"
|
|
199
|
+
|
|
200
|
+
mock_post.side_effect = requests.RequestException("boom")
|
|
201
|
+
if raise_on_failure:
|
|
202
|
+
with pytest.raises(RuntimeError, match="Failed to create fork"):
|
|
203
|
+
pr_creator._create_fork("owner", "repo")
|
|
204
|
+
else:
|
|
205
|
+
assert pr_creator._create_fork("owner", "repo") is None
|
|
206
|
+
|
|
207
|
+
@pytest.mark.parametrize("raise_on_failure", [True, False])
|
|
208
|
+
@patch("requests.post")
|
|
209
|
+
@patch("requests.get")
|
|
210
|
+
def test_create_branch(self, mock_get, mock_post, raise_on_failure, monkeypatch):
|
|
211
|
+
monkeypatch.setenv("GITHUB_TOKEN", "test-token")
|
|
212
|
+
mock_get.return_value.json.return_value = {"object": {"sha": "abc123"}}
|
|
213
|
+
mock_get.return_value.raise_for_status.return_value = None
|
|
214
|
+
mock_post.return_value.raise_for_status.return_value = None
|
|
215
|
+
|
|
216
|
+
pr_creator = GitHubPRCreator(raise_on_failure=raise_on_failure)
|
|
217
|
+
assert pr_creator._create_branch("owner", "repo", "feature", "main") is True
|
|
218
|
+
|
|
219
|
+
mock_get.side_effect = requests.RequestException("boom")
|
|
220
|
+
if raise_on_failure:
|
|
221
|
+
with pytest.raises(RuntimeError, match="Failed to create branch"):
|
|
222
|
+
pr_creator._create_branch("owner", "repo", "feature", "main")
|
|
223
|
+
else:
|
|
224
|
+
assert pr_creator._create_branch("owner", "repo", "feature", "main") is False
|
|
@@ -272,3 +272,57 @@ class TestGitHubRepoForker:
|
|
|
272
272
|
assert "Authorization" not in headers
|
|
273
273
|
assert headers["Accept"] == "application/vnd.github.v3+json"
|
|
274
274
|
assert headers["User-Agent"] == "Haystack/GitHubRepoForker"
|
|
275
|
+
|
|
276
|
+
@pytest.mark.parametrize(
|
|
277
|
+
"status_code,raises,expected",
|
|
278
|
+
[
|
|
279
|
+
(200, False, True),
|
|
280
|
+
(404, False, False),
|
|
281
|
+
(None, True, False),
|
|
282
|
+
],
|
|
283
|
+
)
|
|
284
|
+
@patch("requests.get")
|
|
285
|
+
def test_check_fork_status(self, mock_get, status_code, raises, expected, monkeypatch):
|
|
286
|
+
monkeypatch.setenv("GITHUB_TOKEN", "test-token")
|
|
287
|
+
if raises:
|
|
288
|
+
mock_get.side_effect = requests.RequestException("boom")
|
|
289
|
+
else:
|
|
290
|
+
mock_get.return_value.status_code = status_code
|
|
291
|
+
|
|
292
|
+
forker = GitHubRepoForker()
|
|
293
|
+
assert forker._check_fork_status("test_user/repo") is expected
|
|
294
|
+
|
|
295
|
+
def test_get_existing_repository_returns_none_on_exception(self, monkeypatch):
|
|
296
|
+
monkeypatch.setenv("GITHUB_TOKEN", "test-token")
|
|
297
|
+
|
|
298
|
+
forker = GitHubRepoForker()
|
|
299
|
+
with (
|
|
300
|
+
patch.object(forker, "_get_authenticated_user", return_value="test_user"),
|
|
301
|
+
patch("requests.get", side_effect=requests.RequestException("boom")),
|
|
302
|
+
):
|
|
303
|
+
assert forker._get_existing_repository("repo") is None
|
|
304
|
+
|
|
305
|
+
@pytest.mark.parametrize("succeeds_within_timeout", [True, False])
|
|
306
|
+
def test_run_wait_for_completion(self, succeeds_within_timeout, monkeypatch):
|
|
307
|
+
monkeypatch.setenv("GITHUB_TOKEN", "test-token")
|
|
308
|
+
|
|
309
|
+
forker = GitHubRepoForker(
|
|
310
|
+
wait_for_completion=True,
|
|
311
|
+
max_wait_seconds=5 if succeeds_within_timeout else 0,
|
|
312
|
+
poll_interval=0,
|
|
313
|
+
create_branch=False,
|
|
314
|
+
)
|
|
315
|
+
with (
|
|
316
|
+
patch.object(forker, "_parse_github_url", return_value=("owner", "repo", "123")),
|
|
317
|
+
patch.object(forker, "_get_authenticated_user", return_value="test_user"),
|
|
318
|
+
patch.object(forker, "_get_existing_repository", return_value=None),
|
|
319
|
+
patch.object(forker, "_create_fork", return_value="test_user/repo"),
|
|
320
|
+
patch.object(forker, "_check_fork_status", side_effect=[False, True]),
|
|
321
|
+
patch("time.sleep", lambda _: None),
|
|
322
|
+
):
|
|
323
|
+
if succeeds_within_timeout:
|
|
324
|
+
result = forker.run(url="https://github.com/owner/repo/issues/123")
|
|
325
|
+
assert result == {"repo": "test_user/repo", "issue_branch": None}
|
|
326
|
+
else:
|
|
327
|
+
with pytest.raises(TimeoutError):
|
|
328
|
+
forker.run(url="https://github.com/owner/repo/issues/123")
|
|
@@ -186,3 +186,52 @@ class TestGitHubRepoViewer:
|
|
|
186
186
|
assert "Authorization" not in headers
|
|
187
187
|
assert headers["Accept"] == "application/vnd.github.v3+json"
|
|
188
188
|
assert headers["User-Agent"] == "Haystack/GitHubRepoViewer"
|
|
189
|
+
|
|
190
|
+
def test_run_no_repo_raises(self, monkeypatch):
|
|
191
|
+
monkeypatch.setenv("GITHUB_TOKEN", "test-token")
|
|
192
|
+
viewer = GitHubRepoViewer()
|
|
193
|
+
with pytest.raises(ValueError, match="Repository not provided"):
|
|
194
|
+
viewer.run(path="README.md")
|
|
195
|
+
|
|
196
|
+
@pytest.mark.parametrize(
|
|
197
|
+
"file_payload,error_substring",
|
|
198
|
+
[
|
|
199
|
+
(
|
|
200
|
+
{
|
|
201
|
+
"name": "big.bin",
|
|
202
|
+
"path": "big.bin",
|
|
203
|
+
"size": 2_000_000,
|
|
204
|
+
"html_url": "https://x",
|
|
205
|
+
"content": "",
|
|
206
|
+
"encoding": "base64",
|
|
207
|
+
},
|
|
208
|
+
"exceeds limit",
|
|
209
|
+
),
|
|
210
|
+
(
|
|
211
|
+
{
|
|
212
|
+
"name": "raw.txt",
|
|
213
|
+
"path": "raw.txt",
|
|
214
|
+
"size": 5,
|
|
215
|
+
"html_url": "https://x",
|
|
216
|
+
"content": "Hello",
|
|
217
|
+
"encoding": "utf-8",
|
|
218
|
+
},
|
|
219
|
+
None,
|
|
220
|
+
),
|
|
221
|
+
],
|
|
222
|
+
)
|
|
223
|
+
@patch("requests.get")
|
|
224
|
+
def test_run_file_size_and_encoding(self, mock_get, file_payload, error_substring, monkeypatch):
|
|
225
|
+
monkeypatch.setenv("GITHUB_TOKEN", "test-token")
|
|
226
|
+
mock_get.return_value.json.return_value = file_payload
|
|
227
|
+
mock_get.return_value.raise_for_status.return_value = None
|
|
228
|
+
|
|
229
|
+
viewer = GitHubRepoViewer(raise_on_failure=False)
|
|
230
|
+
result = viewer.run(repo="owner/repo", path=file_payload["path"])
|
|
231
|
+
doc = result["documents"][0]
|
|
232
|
+
if error_substring is None:
|
|
233
|
+
assert doc.content == "Hello"
|
|
234
|
+
assert doc.meta["type"] == "file_content"
|
|
235
|
+
else:
|
|
236
|
+
assert doc.meta["type"] == "error"
|
|
237
|
+
assert error_substring in doc.content
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023-present deepset GmbH <info@deepset.ai>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
import pytest
|
|
5
|
+
from haystack import Document
|
|
6
|
+
|
|
7
|
+
from haystack_integrations.tools.github.utils import message_handler, serialize_handlers
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestMessageHandler:
|
|
11
|
+
@pytest.mark.parametrize(
|
|
12
|
+
"documents,expected",
|
|
13
|
+
[
|
|
14
|
+
(
|
|
15
|
+
[Document(content="error message", meta={"type": "error"})],
|
|
16
|
+
"error message",
|
|
17
|
+
),
|
|
18
|
+
(
|
|
19
|
+
[
|
|
20
|
+
Document(content="docs", meta={"type": "dir"}),
|
|
21
|
+
Document(content="README.md", meta={"type": "file"}),
|
|
22
|
+
],
|
|
23
|
+
"docs\nREADME.md",
|
|
24
|
+
),
|
|
25
|
+
(
|
|
26
|
+
[Document(content="print('hi')", meta={"type": "file_content", "path": "main.py"})],
|
|
27
|
+
"File Content for main.py\n\nprint('hi')",
|
|
28
|
+
),
|
|
29
|
+
],
|
|
30
|
+
)
|
|
31
|
+
def test_message_handler_renders_documents(self, documents, expected):
|
|
32
|
+
assert message_handler(documents) == expected
|
|
33
|
+
|
|
34
|
+
def test_message_handler_truncates_to_max_length(self):
|
|
35
|
+
big_content = "x" * 200
|
|
36
|
+
doc = Document(content=big_content, meta={"type": "file_content", "path": "main.py"})
|
|
37
|
+
result = message_handler([doc], max_length=50)
|
|
38
|
+
assert result.endswith("...(large file can't be fully displayed)")
|
|
39
|
+
assert len(result) == 50 + len("...(large file can't be fully displayed)")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TestSerializeHandlers:
|
|
43
|
+
@pytest.mark.parametrize(
|
|
44
|
+
"outputs_to_state,outputs_to_string,error_match",
|
|
45
|
+
[
|
|
46
|
+
(
|
|
47
|
+
{"documents": {"handler": "not_callable"}},
|
|
48
|
+
None,
|
|
49
|
+
"outputs_to_state\\[documents\\] is not a callable",
|
|
50
|
+
),
|
|
51
|
+
(
|
|
52
|
+
None,
|
|
53
|
+
{"handler": "not_callable"},
|
|
54
|
+
"outputs_to_string is not a callable",
|
|
55
|
+
),
|
|
56
|
+
],
|
|
57
|
+
)
|
|
58
|
+
def test_serialize_handlers_raises_when_handler_not_callable(
|
|
59
|
+
self, outputs_to_state, outputs_to_string, error_match
|
|
60
|
+
):
|
|
61
|
+
serialized: dict = {}
|
|
62
|
+
with pytest.raises(ValueError, match=error_match):
|
|
63
|
+
serialize_handlers(serialized, outputs_to_state, outputs_to_string)
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
loaders:
|
|
2
|
-
- ignore_when_discovered:
|
|
3
|
-
- __init__
|
|
4
|
-
modules:
|
|
5
|
-
- haystack_integrations.components.connectors.github.file_editor
|
|
6
|
-
- haystack_integrations.components.connectors.github.issue_commenter
|
|
7
|
-
- haystack_integrations.components.connectors.github.issue_viewer
|
|
8
|
-
- haystack_integrations.components.connectors.github.pr_creator
|
|
9
|
-
- haystack_integrations.components.connectors.github.repo_viewer
|
|
10
|
-
- haystack_integrations.components.connectors.github.repo_forker
|
|
11
|
-
search_path:
|
|
12
|
-
- ../src
|
|
13
|
-
type: haystack_pydoc_tools.loaders.CustomPythonLoader
|
|
14
|
-
processors:
|
|
15
|
-
- do_not_filter_modules: false
|
|
16
|
-
documented_only: true
|
|
17
|
-
expression: null
|
|
18
|
-
skip_empty_modules: true
|
|
19
|
-
type: filter
|
|
20
|
-
- type: smart
|
|
21
|
-
- type: crossref
|
|
22
|
-
renderer:
|
|
23
|
-
description: GitHub integration for Haystack
|
|
24
|
-
id: integrations-github
|
|
25
|
-
markdown:
|
|
26
|
-
add_member_class_prefix: false
|
|
27
|
-
add_method_class_prefix: true
|
|
28
|
-
classdef_code_block: false
|
|
29
|
-
descriptive_class_title: false
|
|
30
|
-
descriptive_module_title: true
|
|
31
|
-
filename: github.md
|
|
32
|
-
title: GitHub
|
|
33
|
-
type: haystack_pydoc_tools.renderers.DocusaurusRenderer
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/prompts/github/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{github_haystack-2.0.0 → github_haystack-2.1.0}/src/haystack_integrations/tools/github/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|