diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3fe182f..bd885ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,5 @@ jobs: - name: Lint with ruff run: make lint - # TODO: enable this once all the tests pass - # - name: Run tests - # run: make tests + - name: Run tests + run: make tests diff --git a/src/guardrails/types.py b/src/guardrails/types.py index 0d8dba7..c8e8845 100644 --- a/src/guardrails/types.py +++ b/src/guardrails/types.py @@ -71,8 +71,8 @@ class GuardrailResult: original_exception (Exception | None): The original exception if execution failed. info (dict[str, Any]): Additional structured data about the check result, such as error details, matched patterns, or diagnostic messages. - Must include 'checked_text' field containing the processed/validated text. - Defaults to an empty dict. + Implementations may include a 'checked_text' field containing the + processed/validated text when applicable. Defaults to an empty dict. """ tripwire_triggered: bool @@ -82,9 +82,6 @@ class GuardrailResult: def __post_init__(self) -> None: """Validate required fields and consistency.""" - if "checked_text" not in self.info: - raise ValueError("GuardrailResult.info must contain 'checked_text' field") - # Ensure consistency: if execution_failed=True, original_exception should be present if self.execution_failed and self.original_exception is None: raise ValueError( @@ -108,5 +105,4 @@ def __post_init__(self) -> None: TCfg (TypeVar): The configuration type, usually a Pydantic model. Returns: GuardrailResult or Awaitable[GuardrailResult]: The outcome of the guardrail check. - The result must include 'checked_text' in the info dict. """ diff --git a/tests/unit/test_registry.py b/tests/unit/test_registry.py index ea85541..c6fefe0 100644 --- a/tests/unit/test_registry.py +++ b/tests/unit/test_registry.py @@ -37,7 +37,12 @@ def check(_ctx: CtxProto, _value: str, _config: int) -> GuardrailResult: return GuardrailResult(tripwire_triggered=False) model = _resolve_ctx_requirements(check) - fields = getattr(model, "model_fields", getattr(model, "__fields__", {})) + # Prefer Pydantic v2 API without eagerly touching deprecated v1 attributes + fields = ( + model.model_fields + if hasattr(model, "model_fields") + else getattr(model, "__fields__", {}) + ) assert issubclass(model, BaseModel) # noqa: S101 assert set(fields) == {"foo"} # noqa: S101 diff --git a/tests/unit/test_runtime.py b/tests/unit/test_runtime.py index 659a1e8..3eb196b 100644 --- a/tests/unit/test_runtime.py +++ b/tests/unit/test_runtime.py @@ -29,14 +29,36 @@ @pytest.fixture(autouse=True) def stub_openai_module(monkeypatch: pytest.MonkeyPatch) -> Iterator[types.ModuleType]: - """Provide a stub ``openai.AsyncOpenAI`` for modules under test.""" + """Provide a stub ``openai.AsyncOpenAI`` and patch imports in guardrails.*. + + Ensures tests don't require real OPENAI_API_KEY or networked clients. + """ module = types.ModuleType("openai") - class AsyncOpenAI: + class AsyncOpenAI: # noqa: D401 - simple stub + """Stubbed AsyncOpenAI client.""" + pass module.__dict__["AsyncOpenAI"] = AsyncOpenAI + # Ensure any downstream import finds our stub module monkeypatch.setitem(sys.modules, "openai", module) + # Also patch already-imported symbols on guardrails modules + try: + import guardrails.runtime as gr_runtime # type: ignore + + monkeypatch.setattr(gr_runtime, "AsyncOpenAI", AsyncOpenAI, raising=False) + except Exception: + pass + try: + import guardrails.types as gr_types # type: ignore + + monkeypatch.setattr(gr_types, "AsyncOpenAI", AsyncOpenAI, raising=False) + except Exception: + pass + # Provide dummy API key to satisfy any code paths that inspect env + monkeypatch.setenv("OPENAI_API_KEY", "test-key") + yield module monkeypatch.delitem(sys.modules, "openai", raising=False) diff --git a/tests/unit/test_types.py b/tests/unit/test_types.py index 4ea30fc..8a1ae3e 100644 --- a/tests/unit/test_types.py +++ b/tests/unit/test_types.py @@ -10,14 +10,29 @@ @pytest.fixture(autouse=True) def stub_openai_module(monkeypatch: pytest.MonkeyPatch) -> Iterator[types.ModuleType]: - """Provide a stub ``openai.AsyncOpenAI`` for modules under test.""" + """Provide a stub ``openai.AsyncOpenAI`` and patch guardrails types symbol. + + Ensures tests don't require real OPENAI_API_KEY or networked clients. + """ module = types.ModuleType("openai") - class AsyncOpenAI: + class AsyncOpenAI: # noqa: D401 - simple stub + """Stubbed AsyncOpenAI client.""" + pass module.__dict__["AsyncOpenAI"] = AsyncOpenAI monkeypatch.setitem(sys.modules, "openai", module) + # Patch already-imported symbol in guardrails.types if present + try: + import guardrails.types as gr_types # type: ignore + + monkeypatch.setattr(gr_types, "AsyncOpenAI", AsyncOpenAI, raising=False) + except Exception: + pass + # Provide dummy API key in case any code inspects env + monkeypatch.setenv("OPENAI_API_KEY", "test-key") + yield module monkeypatch.delitem(sys.modules, "openai", raising=False)