Skip to content

Commit 9acff6e

Browse files
authored
Remove strict git dependency on run (#236)
* remove strict git dep * add rudimentary tests for app.py * fix test * better looking find_patchflow * sort patchflow list
1 parent 72c4c46 commit 9acff6e

File tree

4 files changed

+137
-23
lines changed

4 files changed

+137
-23
lines changed

patchwork/app.py

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from collections import deque
88
from pathlib import Path
99
from types import ModuleType
10+
from typing import Any
1011

1112
import click
1213
import yaml
@@ -28,14 +29,6 @@
2829
_PATCHFLOW_MODULE_NAME = "patchwork.patchflows"
2930

3031

31-
def _get_config_path(config: str | None, patchflow: str) -> Path | None:
32-
config_path = Path(config)
33-
if config_path.is_dir():
34-
patchwork_path = config_path / patchflow
35-
if patchwork_path.is_dir():
36-
return patchwork_path
37-
38-
3932
def _get_patchflow_names(base_path: Path | str | None) -> Iterable[str]:
4033
names = []
4134
if base_path is None:
@@ -48,7 +41,7 @@ def _get_patchflow_names(base_path: Path | str | None) -> Iterable[str]:
4841
for path in base_path.iterdir():
4942
if path.is_dir() and (path / f"{path.name}.py").is_file():
5043
names.append(path.name)
51-
return names
44+
return sorted(names)
5245

5346

5447
def list_option_callback(ctx: click.Context, param: click.Parameter, value: str | None) -> None:
@@ -181,21 +174,17 @@ def cli(
181174
# treat --key=value as a key-value pair
182175
inputs[key] = value
183176

184-
module = find_module(possbile_module_paths, patchflow)
185-
186-
try:
187-
patchflow_class = getattr(module, patchflow_name)
188-
except AttributeError:
189-
logger.debug(f"Patchflow {patchflow} not found as a class in {module_path}")
177+
patchflow_class = find_patchflow(possbile_module_paths, patchflow_name)
178+
if patchflow_class is None:
179+
logger.error(f"Patchflow {patchflow_name} not found in {possbile_module_paths}")
190180
exit(1)
191181

192182
try:
193-
repo = Repo(Path.cwd(), search_parent_directories=True)
194183
patched = PatchedClient(inputs.get("patched_api_key"))
195184
if not disable_telemetry:
196185
patched.send_public_telemetry(patchflow_name, inputs)
197186

198-
with patched.patched_telemetry(patchflow_name, repo, {}):
187+
with patched.patched_telemetry(patchflow_name, {}):
199188
patchflow_instance = patchflow_class(inputs)
200189
patchflow_instance.run()
201190
except Exception as e:
@@ -209,20 +198,25 @@ def cli(
209198
file.write(serialize(inputs))
210199

211200

212-
def find_module(possible_module_paths: Iterable[str], patchflow: str) -> ModuleType | None:
201+
def find_patchflow(possible_module_paths: Iterable[str], patchflow: str) -> Any | None:
213202
for module_path in possible_module_paths:
214203
try:
215204
spec = importlib.util.spec_from_file_location("custom_module", module_path)
216205
module = importlib.util.module_from_spec(spec)
217206
spec.loader.exec_module(module)
218-
return module
207+
return getattr(module, patchflow)
208+
except AttributeError:
209+
logger.debug(f"Patchflow {patchflow} not found in {module_path}")
219210
except Exception:
220211
logger.debug(f"Patchflow {patchflow} not found as a file/directory in {module_path}")
221212

222213
try:
223-
return importlib.import_module(module_path)
214+
module = importlib.import_module(module_path)
215+
return getattr(module, patchflow)
224216
except ModuleNotFoundError:
225217
logger.debug(f"Patchflow {patchflow} not found as a module in {module_path}")
218+
except AttributeError:
219+
logger.debug(f"Patchflow {patchflow} not found in {module_path}")
226220

227221
return None
228222

patchwork/common/client/patched.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import sys
1010
import uuid
1111
from importlib import metadata
12+
from pathlib import Path
1213
from threading import Thread
1314
from typing import Any
1415

@@ -167,7 +168,7 @@ def send_public_telemetry(self, patchflow: str, inputs: dict):
167168
logger.debug(f"Failed to send public telemetry: {e}")
168169

169170
@contextlib.contextmanager
170-
def patched_telemetry(self, patchflow: str, repo: Repo, inputs: dict):
171+
def patched_telemetry(self, patchflow: str, inputs: dict):
171172
if not self.access_token:
172173
yield
173174
return
@@ -184,7 +185,8 @@ def patched_telemetry(self, patchflow: str, repo: Repo, inputs: dict):
184185
return
185186

186187
try:
187-
patchflow_run_id = self.record_patchflow_run(patchflow, repo, inputs)
188+
repo = Repo(Path.cwd(), search_parent_directories=True)
189+
patchflow_run_id = self.record_patchflow_run(patchflow, repo, self.__handle_telemetry_inputs(inputs))
188190
except Exception as e:
189191
logger.error(f"Failed to record patchflow run: {e}")
190192
yield

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "patchwork-cli"
3-
version = "0.0.21"
3+
version = "0.0.22"
44
description = ""
55
authors = ["patched.codes"]
66
license = "AGPL"

tests/common/test_app.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
from types import ModuleType
2+
3+
import pytest
4+
import os
5+
from patchwork.app import cli, find_patchflow, _get_patchflow_names
6+
7+
import click
8+
import yaml
9+
from patchwork.app import list_option_callback
10+
from click.testing import CliRunner
11+
12+
13+
@pytest.fixture
14+
def config_dir(tmp_path):
15+
config_dir = tmp_path / "config"
16+
config_dir.mkdir(parents=True, exist_ok=True)
17+
return config_dir
18+
19+
20+
@pytest.fixture
21+
def patchflow_dir(config_dir):
22+
patchflow_dir = config_dir / "noop"
23+
patchflow_dir.mkdir(parents=True, exist_ok=True)
24+
return patchflow_dir
25+
26+
27+
@pytest.fixture
28+
def patchflow_file(patchflow_dir):
29+
patchflow_file = patchflow_dir / "noop.py"
30+
patchflow_file.touch(exist_ok=True)
31+
return patchflow_file
32+
33+
34+
@pytest.fixture
35+
def config_file(patchflow_dir):
36+
config_file = patchflow_dir / "config.yaml"
37+
config_file.touch(exist_ok=True)
38+
return config_file
39+
40+
41+
@pytest.fixture
42+
def runner():
43+
runner = CliRunner()
44+
with runner.isolated_filesystem():
45+
yield runner
46+
return
47+
48+
49+
def test_default_list_option_callback(runner):
50+
result = runner.invoke(cli, ['--list'])
51+
assert result.exit_code == 0
52+
assert result.output.strip() == """\
53+
AutoFix
54+
DependencyUpgrade
55+
GenerateDocstring
56+
GenerateREADME
57+
PRReview
58+
ResolveIssue"""
59+
60+
61+
def test_config_list_option_callback(runner, config_dir, patchflow_file):
62+
filename = patchflow_file.name
63+
name_without_ext = filename.replace(patchflow_file.suffix, '')
64+
result = runner.invoke(cli, ['--list', '--config', str(config_dir)])
65+
assert result.exit_code == 0
66+
assert result.output.strip() == f"""\
67+
AutoFix
68+
DependencyUpgrade
69+
GenerateDocstring
70+
GenerateREADME
71+
PRReview
72+
ResolveIssue
73+
{name_without_ext}"""
74+
75+
76+
def test_cli_success(runner, config_dir, patchflow_file):
77+
code = """\
78+
class noop:
79+
def __init__(self, inputs):
80+
pass
81+
def run(self):
82+
return dict(test='test')
83+
"""
84+
patchflow_file.write_text(code)
85+
86+
result = runner.invoke(cli, ['noop', '--config', str(config_dir)])
87+
88+
assert result.exit_code == 0
89+
90+
91+
def test_cli_failure(runner):
92+
result = runner.invoke(cli, ['noop', '--config', 'nonexistent'])
93+
assert result.exit_code == 2
94+
95+
96+
def test_default_find_module():
97+
# Try to import the module
98+
patchflow = find_patchflow(["patchwork.patchflows"], "AutoFix")
99+
100+
# Check the output
101+
assert isinstance(patchflow, type)
102+
assert patchflow.__name__ == "AutoFix"
103+
104+
105+
def test_config_find_module(patchflow_file):
106+
code = """\
107+
class noop:
108+
def __init__(self, inputs):
109+
pass
110+
def run(self):
111+
return dict(test='test')
112+
"""
113+
patchflow_file.write_text(code)
114+
patchflow = find_patchflow([str(patchflow_file.resolve()), "patchwork.patchflows"], "noop")
115+
116+
# Check the output
117+
assert isinstance(patchflow, type)
118+
assert patchflow.__name__ == "noop"

0 commit comments

Comments
 (0)