|
| 1 | +from __future__ import annotations |
| 2 | + |
| 3 | +import sys |
| 4 | +import types |
| 5 | +from contextlib import nullcontext |
| 6 | +from pathlib import Path |
| 7 | +from types import SimpleNamespace |
| 8 | +from typing import Any |
| 9 | + |
| 10 | +import pyarrow as pa |
| 11 | +import pytest |
| 12 | + |
| 13 | +APP_DIR = Path(__file__).resolve().parents[2] / "app" |
| 14 | + |
| 15 | + |
| 16 | +class _RecordingStreamlit(types.ModuleType): |
| 17 | + def __init__(self) -> None: |
| 18 | + super().__init__("streamlit") |
| 19 | + self.dataframes: list[Any] = [] |
| 20 | + self.dataframe_kwargs: list[dict[str, Any]] = [] |
| 21 | + |
| 22 | + def expander(self, *_: Any, **__: Any) -> Any: |
| 23 | + return nullcontext() |
| 24 | + |
| 25 | + def columns(self, count: int) -> list[Any]: |
| 26 | + return [nullcontext() for _ in range(count)] |
| 27 | + |
| 28 | + def dataframe(self, frame: Any, **kwargs: Any) -> None: |
| 29 | + self.dataframes.append(frame) |
| 30 | + self.dataframe_kwargs.append(kwargs) |
| 31 | + |
| 32 | + def markdown(self, *_: Any, **__: Any) -> None: |
| 33 | + pass |
| 34 | + |
| 35 | + def write(self, *_: Any, **__: Any) -> None: |
| 36 | + pass |
| 37 | + |
| 38 | + def error(self, *_: Any, **__: Any) -> None: |
| 39 | + pass |
| 40 | + |
| 41 | + |
| 42 | +@pytest.fixture |
| 43 | +def show_working_module(monkeypatch: pytest.MonkeyPatch) -> Any: |
| 44 | + fake_st = _RecordingStreamlit() |
| 45 | + fake_i18n = types.ModuleType("i18n") |
| 46 | + fake_i18n.t = lambda key: key # type: ignore[attr-defined] |
| 47 | + fake_output = types.ModuleType("components.output") |
| 48 | + fake_output.confidence_label = lambda confidence: str(confidence) # type: ignore[attr-defined] |
| 49 | + |
| 50 | + for name in [ |
| 51 | + module_name |
| 52 | + for module_name in sys.modules |
| 53 | + if module_name == "components" or module_name.startswith("components.") |
| 54 | + ]: |
| 55 | + monkeypatch.delitem(sys.modules, name, raising=False) |
| 56 | + monkeypatch.setitem(sys.modules, "streamlit", fake_st) |
| 57 | + monkeypatch.setitem(sys.modules, "i18n", fake_i18n) |
| 58 | + monkeypatch.setitem(sys.modules, "components.output", fake_output) |
| 59 | + monkeypatch.syspath_prepend(str(APP_DIR)) |
| 60 | + |
| 61 | + from components import show_working as show_working_mod # type: ignore[import-not-found] |
| 62 | + |
| 63 | + return show_working_mod, fake_st |
| 64 | + |
| 65 | + |
| 66 | +def _result_with_mixed_trace_tokens() -> SimpleNamespace: |
| 67 | + return SimpleNamespace( |
| 68 | + trace=[ |
| 69 | + {"node": "retrieve_context"}, |
| 70 | + { |
| 71 | + "node": "generate_sql", |
| 72 | + "model": "codestral-latest", |
| 73 | + "input_tokens": 3376, |
| 74 | + "output_tokens": 62, |
| 75 | + "confidence": 1.0, |
| 76 | + }, |
| 77 | + { |
| 78 | + "node": "explain_trace", |
| 79 | + "model": "codestral-latest", |
| 80 | + "input_tokens": 169, |
| 81 | + "output_tokens": 12, |
| 82 | + }, |
| 83 | + ], |
| 84 | + confidence=1.0, |
| 85 | + repair_attempted=False, |
| 86 | + db_id="chinook", |
| 87 | + outcome=None, |
| 88 | + rationale="", |
| 89 | + error_kind=None, |
| 90 | + ) |
| 91 | + |
| 92 | + |
| 93 | +def test_trace_dataframe_is_arrow_serializable(show_working_module: Any) -> None: |
| 94 | + show_working_mod, fake_st = show_working_module |
| 95 | + |
| 96 | + show_working_mod.render_show_working(_result_with_mixed_trace_tokens()) |
| 97 | + |
| 98 | + assert len(fake_st.dataframes) == 1 |
| 99 | + pa.Table.from_pandas(fake_st.dataframes[0], preserve_index=False) |
| 100 | + |
| 101 | + |
| 102 | +def test_trace_dataframe_uses_current_streamlit_width_api( |
| 103 | + show_working_module: Any, |
| 104 | +) -> None: |
| 105 | + show_working_mod, fake_st = show_working_module |
| 106 | + |
| 107 | + show_working_mod.render_show_working(_result_with_mixed_trace_tokens()) |
| 108 | + |
| 109 | + assert fake_st.dataframe_kwargs == [{"width": "stretch", "hide_index": True}] |
0 commit comments