Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions patchwork/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,20 @@ def find_patchflow(possible_module_paths: Iterable[str], patchflow: str) -> Any
return getattr(module, patchflow)
except AttributeError:
logger.debug(f"Patchflow {patchflow} not found in {module_path}")
except ImportError:
raise
except Exception:
logger.debug(f"Patchflow {patchflow} not found as a file/directory in {module_path}")

try:
module = importlib.import_module(module_path)
logger.info(f"Patchflow {patchflow} loaded from {module_path}")
return getattr(module, patchflow)
except ModuleNotFoundError:
logger.debug(f"Patchflow {patchflow} not found as a module in {module_path}")
except ModuleNotFoundError as e:
if e.name == module_path:
logger.debug(f"Patchflow {patchflow} not found as a module in {module_path}")
else:
raise
except AttributeError:
logger.debug(f"Patchflow {patchflow} not found in {module_path}")

Expand Down Expand Up @@ -213,7 +218,13 @@ def cli(
if debug:
inputs["debug"] = True

patchflow_class = find_patchflow(possbile_module_paths, patchflow_name)
patchflow_class = None
try:
patchflow_class = find_patchflow(possbile_module_paths, patchflow_name)
except Exception as e:
logger.debug(traceback.format_exc())
logger.error(f"Error loading patchflow {patchflow_name}: {e}")
exit(1)
if patchflow_class is None:
logger.error(f"Patchflow {patchflow_name} not found in {possbile_module_paths}")
exit(1)
Expand Down
20 changes: 10 additions & 10 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ numpy = "1.26.4"
pandas = "2.2.3"
scipy = "1.13.1"
boto3 = "~1.37.11"
# pydantic-ai 0.0.37 imports opentelemetry._events, which was removed in opentelemetry-api>=1.30.0
opentelemetry-api = "1.29.0"

# extras
# security
Expand Down
36 changes: 36 additions & 0 deletions tests/common/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,42 @@ def test_default_find_module():
assert patchflow.__name__ == "AutoFix"


def test_find_missing_patchflow_returns_none():
patchflow = find_patchflow(["patchwork.patchflows"], "ThisPatchflowDoesNotExist")
assert patchflow is None


def test_find_fails_on_broken_patchflow_file(tmp_path):
flow_dir = tmp_path / "brokenflow"
flow_dir.mkdir(parents=True, exist_ok=True)
flow_file = flow_dir / "brokenflow.py"
flow_file.write_text("import missing_transitive_dependency_xyz\nclass BrokenFlow:\n pass\n")

with pytest.raises(ModuleNotFoundError, match="missing_transitive_dependency_xyz"):
find_patchflow([str(flow_file.resolve())], "BrokenFlow")


def test_find_fails_on_broken_transitive_dependency(mocker):
def broken_import(name, *args, **kwargs):
raise ModuleNotFoundError("No module named 'opentelemetry._events'", name="opentelemetry._events")

mocker.patch("patchwork.app.importlib.import_module", side_effect=broken_import)

with pytest.raises(ModuleNotFoundError, match="opentelemetry._events"):
find_patchflow(["patchwork.patchflows"], "GenerateDocstring")


def test_cli_surfaces_broken_import(mocker, runner):
def broken_import(name, *args, **kwargs):
raise ModuleNotFoundError("No module named 'opentelemetry._events'", name="opentelemetry._events")

mocker.patch("patchwork.app.importlib.import_module", side_effect=broken_import)

result = runner.invoke(cli, ["GenerateDocstring"])
assert result.exit_code == 1
assert "opentelemetry._events" in result.output


def test_config_find_module(patchflow_file):
code = """\
class noop:
Expand Down