Render database names and failures verbatim in the inspector
A database name, a location and an exception message all reach the info modal as configuration-derived text, and Rich reads `[...]` in any of them as markup: a name like `beta [prod]` disappeared, and a message carrying `[Errno 2]` or a stray closing tag could break the line it sat in.
This commit is contained in:
parent
7dc853606f
commit
1de1b662ae
2 changed files with 54 additions and 12 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import asyncio
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from rich.markup import escape
|
||||
from textual.app import ComposeResult
|
||||
from textual.binding import Binding
|
||||
from textual.containers import Vertical, VerticalScroll
|
||||
|
|
@ -35,12 +36,11 @@ async def database_lines(client: "HaikuRAG") -> list[str]:
|
|||
db = client.store.db
|
||||
stats = await get_database_stats(db)
|
||||
except Exception as e:
|
||||
return [f"[red]Failed to open database: {e}[/red]"]
|
||||
return [f"[red]Failed to open database: {escape(str(e))}[/red]"]
|
||||
|
||||
# The store read these on open; a second query would re-read and re-parse
|
||||
# the same blob.
|
||||
# The store read and parsed these on open.
|
||||
settings = client.store.stored_settings
|
||||
stored_version = str(settings.get("version", "unknown"))
|
||||
stored_version = escape(str(settings.get("version", "unknown")))
|
||||
embed_model_obj = settings.get("embeddings", {}).get("model", {})
|
||||
embed_provider = embed_model_obj.get("provider")
|
||||
embed_model = embed_model_obj.get("name")
|
||||
|
|
@ -56,9 +56,9 @@ async def database_lines(client: "HaikuRAG") -> list[str]:
|
|||
)
|
||||
|
||||
if embed_provider or embed_model or vector_dim:
|
||||
provider_part = embed_provider or "unknown"
|
||||
model_part = embed_model or "unknown"
|
||||
dim_part = f"{vector_dim}" if vector_dim is not None else "unknown"
|
||||
provider_part = escape(str(embed_provider or "unknown"))
|
||||
model_part = escape(str(embed_model or "unknown"))
|
||||
dim_part = escape(str(vector_dim)) if vector_dim is not None else "unknown"
|
||||
lines.append(
|
||||
f"[bold $accent]embeddings[/bold $accent]: "
|
||||
f"{provider_part}/{model_part} (dim: {dim_part})"
|
||||
|
|
@ -172,11 +172,15 @@ class InfoModal(ModalScreen):
|
|||
for block in blocks:
|
||||
lines.extend(block)
|
||||
else:
|
||||
lines.append(f"[bold $accent]location[/bold $accent]: {location}")
|
||||
lines.append(
|
||||
f"[bold $accent]location[/bold $accent]: {escape(str(location))}"
|
||||
)
|
||||
lines.extend(await database_lines(self.client))
|
||||
|
||||
lines.append("[bold]Versions[/bold]")
|
||||
versions = get_package_versions()
|
||||
versions = {
|
||||
key: escape(str(value)) for key, value in get_package_versions().items()
|
||||
}
|
||||
lines.append(f"[bold $accent]haiku.rag[/bold $accent]: {versions['haiku_rag']}")
|
||||
lines.append(f"[bold $accent]lancedb[/bold $accent]: {versions['lancedb']}")
|
||||
lines.append(f"[bold $accent]docling[/bold $accent]: {versions['docling']}")
|
||||
|
|
@ -192,13 +196,13 @@ class InfoModal(ModalScreen):
|
|||
|
||||
async def _report(self, name: str) -> list[str]:
|
||||
"""One database's block, including its own failure to open."""
|
||||
lines = [f"[bold]{name}[/bold]"]
|
||||
lines = [f"[bold]{escape(name)}[/bold]"]
|
||||
try:
|
||||
(owner,) = await self.client.clients_for([name])
|
||||
except Exception as e:
|
||||
# The client names a configured database by name and never by
|
||||
# location, so its message is safe to show.
|
||||
return [*lines, f"[red]{e}[/red]", ""]
|
||||
return [*lines, f"[red]{escape(str(e))}[/red]", ""]
|
||||
return [*lines, *await database_lines(owner)]
|
||||
|
||||
async def action_dismiss(self, result=None) -> None:
|
||||
|
|
|
|||
|
|
@ -446,9 +446,47 @@ class TestReportingEachDatabase:
|
|||
"database 'beta' could not be opened: OSError"
|
||||
)
|
||||
modal.client = client
|
||||
modal.db_path = None
|
||||
|
||||
lines = await modal._report("beta")
|
||||
|
||||
assert lines[0] == "[bold]beta[/bold]"
|
||||
assert "could not be opened" in lines[1]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_a_failure_with_bracketed_text_renders_literally(self):
|
||||
"""Error text and database names render verbatim, not as Rich markup."""
|
||||
from rich.text import Text
|
||||
|
||||
from haiku.rag.inspector.widgets.info_modal import InfoModal
|
||||
from haiku.rag.store.exceptions import SourceUnavailableError
|
||||
|
||||
modal = InfoModal.__new__(InfoModal)
|
||||
client = AsyncMock()
|
||||
message = "database 'beta [prod]' could not be opened: [/red] [Errno 2]"
|
||||
client.clients_for.side_effect = SourceUnavailableError(message)
|
||||
modal.client = client
|
||||
|
||||
lines = await modal._report("beta [prod]")
|
||||
|
||||
assert Text.from_markup(lines[0]).plain == "beta [prod]"
|
||||
assert message in Text.from_markup(lines[1]).plain
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_an_open_failure_with_bracketed_text_renders_literally(
|
||||
self, tmp_path
|
||||
):
|
||||
"""A stats-read failure renders its message verbatim, not as markup."""
|
||||
from rich.text import Text
|
||||
|
||||
from haiku.rag.inspector.widgets.info_modal import database_lines
|
||||
from haiku.rag.store.engine import ConnectionMode
|
||||
|
||||
client = MagicMock()
|
||||
client.store.db_path = tmp_path
|
||||
client.store._connection_mode = ConnectionMode.LOCAL
|
||||
message = "Schema error: no field named [vector, text] [/red]"
|
||||
type(client.store).db = property(MagicMock(side_effect=RuntimeError(message)))
|
||||
|
||||
lines = await database_lines(client)
|
||||
|
||||
assert message in Text.from_markup(lines[0]).plain
|
||||
|
|
|
|||
Loading…
Reference in a new issue