diff --git a/CHANGELOG.md b/CHANGELOG.md index b3a82f1c..5b5f14b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,8 @@ - Every database has a name: the key in `lancedb.databases`, or the path's stem for `--db PATH`, `db_path=` and the default database, which is the entry `haiku.rag` under `storage.data_dir` and selectable by that name. - `SearchResult.source`, `Document.source` and `Citation.source` always carry it. + `SearchResult.source`, `Document.source` and `Citation.source` carry it on + every value a database produces. - `db_path=` beside a configured `lancedb.databases` raises `AmbiguousDatabaseError` (`HaikuRAG`, `create_capability`, `create_mcp_server`, `Sandbox`). `haiku-rag --db PATH` and `haiku-ingester --db PATH` open that path diff --git a/haiku_rag_slim/haiku/rag/cli.py b/haiku_rag_slim/haiku/rag/cli.py index ed6cd813..836b1efb 100644 --- a/haiku_rag_slim/haiku/rag/cli.py +++ b/haiku_rag_slim/haiku/rag/cli.py @@ -102,7 +102,10 @@ def resolve_scope( "pass --db or --db-name, not both: they name the same thing" ) if db is not None: - return DatabaseScope.at(db) + try: + return DatabaseScope.at(db) + except ValueError as error: + raise typer.BadParameter(str(error), param_hint="--db") from error scope = DatabaseScope.resolve(get_config(), database_name=_db_name) if scope.covers_multiple and not covers_set: raise AmbiguousDatabaseError( diff --git a/haiku_rag_slim/haiku/rag/config/models.py b/haiku_rag_slim/haiku/rag/config/models.py index 40516a5b..a0ab7716 100644 --- a/haiku_rag_slim/haiku/rag/config/models.py +++ b/haiku_rag_slim/haiku/rag/config/models.py @@ -120,9 +120,14 @@ class LanceDBConfig(ConfigModel): @classmethod def _uri_names_its_replacement(cls, data: Any) -> Any: if isinstance(data, dict) and "uri" in data: + if str(data["uri"]).strip(): + raise ValueError( + "lancedb.uri was removed; write lancedb.databases: {NAME: " + f"{data['uri']!r}}} instead" + ) raise ValueError( - "lancedb.uri was removed; write lancedb.databases: {NAME: " - f"{data['uri']!r}}} instead" + "lancedb.uri was removed; remove the empty key. With no " + "lancedb.databases the database is haiku.rag under storage.data_dir" ) return data diff --git a/haiku_rag_slim/haiku/rag/ingester/cli.py b/haiku_rag_slim/haiku/rag/ingester/cli.py index 5b764048..2389f538 100644 --- a/haiku_rag_slim/haiku/rag/ingester/cli.py +++ b/haiku_rag_slim/haiku/rag/ingester/cli.py @@ -224,7 +224,12 @@ def _scope_for(db: Path | None) -> "DatabaseScope | None": is configured. None leaves placement to the configuration.""" from haiku.rag.client.scope import DatabaseScope - return DatabaseScope.at(db) if db is not None else None + if db is None: + return None + try: + return DatabaseScope.at(db) + except ValueError as error: + raise typer.BadParameter(str(error), param_hint="--db") from error @_cli.command("serve") diff --git a/tests/ingester/test_cli.py b/tests/ingester/test_cli.py index 54cfaf3e..bdad46b6 100644 --- a/tests/ingester/test_cli.py +++ b/tests/ingester/test_cli.py @@ -547,6 +547,16 @@ class TestPlacingTheIngesterDatabase: tmp_path / "local.lancedb" ) + def test_a_path_without_a_stem_is_a_usage_error(self): + from pathlib import Path + + import typer + + from haiku.rag.ingester.cli import _scope_for + + with pytest.raises(typer.BadParameter, match="no name"): + _scope_for(Path("/")) + def test_a_python_caller_cannot_pass_a_path(self): from haiku.rag.ingester.app import IngesterApp diff --git a/tests/test_cli.py b/tests/test_cli.py index 7e52270b..5c2a7b44 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -169,6 +169,16 @@ class TestOneDatabaseCommands: assert scope.names == ("other",) assert not scope.covers_multiple + def test_a_path_without_a_stem_is_a_usage_error(self, monkeypatch): + """A path that names no database is the operator's mistake, reported as + one.""" + import typer + + self._install(monkeypatch) + + with pytest.raises(typer.BadParameter, match="no name"): + resolve_scope(Path("/")) + def test_no_configured_databases_is_allowed(self, monkeypatch, tmp_path): import haiku.rag.config as config_module diff --git a/tests/test_config.py b/tests/test_config.py index a5665499..cd35436d 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -812,7 +812,19 @@ def test_lancedb_uri_is_refused_with_the_replacement_named(): message = str(raised.value) assert "lancedb.uri" in message - assert "lancedb.databases" in message + assert "lancedb.databases: {NAME: 's3://bucket/notes.lancedb'}" in message with pytest.raises(ValidationError, match="lancedb.databases"): LanceDBConfig.model_validate({"uri": "/data/notes.lancedb"}) + + +def test_an_empty_lancedb_uri_is_refused_with_removal_as_the_remedy(): + """Generated configs carried `uri: ""` for the local default. The remedy is + to delete the key, never a mapping with an empty location.""" + with pytest.raises(ValidationError) as raised: + AppConfig.model_validate({"lancedb": {"uri": ""}}) + + message = str(raised.value) + assert "lancedb.uri" in message + assert "remove" in message + assert "{NAME" not in message