From 0d518c0f89125668e789d34715996c654df5519f Mon Sep 17 00:00:00 2001 From: Nitjsefnie Date: Wed, 29 Jul 2026 09:01:43 +0200 Subject: [PATCH] fix(cicd): lazy-load pytest in plugin.py (#227) Move the pytest import out of module scope so the oaeval CLI can start on a clean install that does not include pytest. Co-Authored-By: Kimi K2.7 Code --- openagent_eval/cicd/plugin.py | 14 ++++-- tests/unit/test_cicd/test_no_pytest_import.py | 48 +++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 tests/unit/test_cicd/test_no_pytest_import.py diff --git a/openagent_eval/cicd/plugin.py b/openagent_eval/cicd/plugin.py index 62a81fa..71bb3d6 100644 --- a/openagent_eval/cicd/plugin.py +++ b/openagent_eval/cicd/plugin.py @@ -24,12 +24,11 @@ def test_rag_evaluation(): from __future__ import annotations import asyncio +import sys import time from pathlib import Path from typing import Any, Generator -import pytest - from openagent_eval.cicd.models import CICDConfig, EvaluationGate, ThresholdConfig from openagent_eval.cicd.thresholds import EvaluationResult, ThresholdEvaluator @@ -89,6 +88,8 @@ def pytest_collection_modifyitems( config: pytest.Config, items: list[pytest.Item] ) -> None: """Modify collected items to add OpenAgent Eval markers.""" + import pytest + for item in items: if "oaeval" in item.keywords: item.add_marker(pytest.mark.oaeval) @@ -263,7 +264,6 @@ def pytest_runtest_makereport( report.oaeval_result = item._oaeval_result # type: ignore[attr-defined] -@pytest.hookimpl(tryfirst=True) def pytest_runtest_setup(item: pytest.Item) -> None: """Setup hook for OpenAgent Eval tests.""" # Check if this is an oaeval test @@ -272,6 +272,14 @@ def pytest_runtest_setup(item: pytest.Item) -> None: item._is_oaeval_test = True # type: ignore[attr-defined] +# Apply pytest hook decorator only when pytest is available. This keeps the +# module importable on clean installs that do not include pytest. +if sys.modules.get("pytest") is not None: + import pytest + + pytest_runtest_setup = pytest.hookimpl(tryfirst=True)(pytest_runtest_setup) + + def pytest_sessionfinish( session: pytest.Session, exitstatus: int ) -> None: diff --git a/tests/unit/test_cicd/test_no_pytest_import.py b/tests/unit/test_cicd/test_no_pytest_import.py new file mode 100644 index 0000000..4c9b9cb --- /dev/null +++ b/tests/unit/test_cicd/test_no_pytest_import.py @@ -0,0 +1,48 @@ +"""Regression test for issue #227. + +The CLI must be importable and runnable on a clean install that does not +include pytest. We verify this in-process by blocking the ``pytest`` name +before importing the modules. +""" + +import subprocess +import sys + + +def test_cicd_and_cli_import_without_pytest() -> None: + """Plugin and CLI entry point import cleanly when pytest is unavailable.""" + code = ( + "import sys\n" + "sys.modules['pytest'] = None\n" + "from openagent_eval.cicd import OAEvalPlugin\n" + "from openagent_eval.cli.main import app\n" + "print('import-ok')\n" + ) + result = subprocess.run( + [sys.executable, "-c", code], + capture_output=True, + text=True, + ) + assert result.returncode == 0, result.stderr + assert "import-ok" in result.stdout + + +def test_oaeval_help_runs_without_pytest() -> None: + """``oaeval --help`` works when pytest is unavailable.""" + code = ( + "import sys\n" + "sys.modules['pytest'] = None\n" + "from typer.testing import CliRunner\n" + "from openagent_eval.cli.main import app\n" + "runner = CliRunner()\n" + "result = runner.invoke(app, ['--help'])\n" + "print(result.output)\n" + "assert result.exit_code == 0, result.exception\n" + ) + result = subprocess.run( + [sys.executable, "-c", code], + capture_output=True, + text=True, + ) + assert result.returncode == 0, result.stderr + assert "Usage:" in result.stdout