Name the remedy for a missing configured database and list the databases an unknown name could have meant
A configured or default database that does not exist raises SourceUnavailableError with the way to create it and without its location. An unknown --db-name lists the databases there are, configured or default. The CHANGELOG records Store.db_path, SingleDatabaseSession(ref, config), the empty lancedb.uri migration and the error-type change.
This commit is contained in:
parent
3814b2bc78
commit
187946a7ae
8 changed files with 47 additions and 15 deletions
|
|
@ -5,7 +5,8 @@
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- `lancedb.uri`. Write `lancedb.databases: {NAME: <location>}`; a config carrying
|
- `lancedb.uri`. Write `lancedb.databases: {NAME: <location>}`; a config carrying
|
||||||
`uri` fails to load with that message.
|
`uri` fails to load with that message. Configurations generated by
|
||||||
|
`init-config` through 0.81 carry `uri: ""` and must drop the key.
|
||||||
- `HAIKU_RAG_DB`. Capabilities cover the databases the configuration places, or
|
- `HAIKU_RAG_DB`. Capabilities cover the databases the configuration places, or
|
||||||
the `db_path` argument.
|
the `db_path` argument.
|
||||||
- `DB_PATH` in the `app/` backend and `examples/custom_agent_agui.py`. Both load
|
- `DB_PATH` in the `app/` backend and `examples/custom_agent_agui.py`. Both load
|
||||||
|
|
@ -33,6 +34,12 @@
|
||||||
- `DatabaseRef(name, location, given)` replaces `DatabaseRef(name, uri, db_path)`;
|
- `DatabaseRef(name, location, given)` replaces `DatabaseRef(name, uri, db_path)`;
|
||||||
`DatabaseScope.at(path)` added; `locate_database` returns `Path | str`.
|
`DatabaseScope.at(path)` added; `locate_database` returns `Path | str`.
|
||||||
`IngesterApp(config, scope)` takes a resolved scope in place of `db_path`.
|
`IngesterApp(config, scope)` takes a resolved scope in place of `db_path`.
|
||||||
|
`SingleDatabaseSession(ref, config)` replaces `SingleDatabaseSession(db_path,
|
||||||
|
config, source=)`. `Store.db_path` is `None` for a database behind a URI.
|
||||||
|
- Opening a configured or default database that does not exist raises
|
||||||
|
`SourceUnavailableError` naming the database and the remedy (`haiku-rag init`
|
||||||
|
or `create=True`), where the default database raised `FileNotFoundError` with
|
||||||
|
its path. A database given as a path still raises `FileNotFoundError`.
|
||||||
- `Store(location, config)`, `connect_lancedb(location, config)`,
|
- `Store(location, config)`, `connect_lancedb(location, config)`,
|
||||||
`gather_database_info(location, config)` and `run_doctor(config, location, ...)`
|
`gather_database_info(location, config)` and `run_doctor(config, location, ...)`
|
||||||
take the database location, a path or a URI. `ConnectionMode.of(location)`
|
take the database location, a path or a URI. `ConnectionMode.of(location)`
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ async with HaikuRAG(create=True) as client:
|
||||||
|
|
||||||
The [default location](index.md#configuration-file-locations) is platform-specific (e.g., `~/Library/Application Support/haiku.rag/` on macOS).
|
The [default location](index.md#configuration-file-locations) is platform-specific (e.g., `~/Library/Application Support/haiku.rag/` on macOS).
|
||||||
|
|
||||||
Opening a nonexistent local database given as a path raises `FileNotFoundError`, naming the path. This prevents accidental database creation from typos or misconfigured paths. A database placed by `lancedb.databases` raises `SourceUnavailableError` instead, naming the database and not its location.
|
Opening a nonexistent local database given as a path raises `FileNotFoundError`, naming the path. This prevents accidental database creation from typos or misconfigured paths. A configured or default database raises `SourceUnavailableError` instead, naming the database and not its location.
|
||||||
|
|
||||||
## Remote Storage
|
## Remote Storage
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ async with HaikuRAG("path/to/database.lancedb", read_only=True) as client:
|
||||||
`async with` is the lifecycle. A caller that owns the client some other way releases it with `await client.aclose()`, which does the same work for every client shape. `client.close()` closes the connection to one database and nothing else, since draining the background vacuum and releasing the embedder and reranker are awaitable; it refuses a client covering several.
|
`async with` is the lifecycle. A caller that owns the client some other way releases it with `await client.aclose()`, which does the same work for every client shape. `client.close()` closes the connection to one database and nothing else, since draining the background vacuum and releasing the embedder and reranker are awaitable; it refuses a client covering several.
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
Databases must be explicitly created with `create=True` or via `haiku-rag init` before use. Opening a nonexistent local database given as a path raises `FileNotFoundError`, naming the path; one placed by `lancedb.databases` raises `SourceUnavailableError`, which names the database rather than its location. A path beside a configured `lancedb.databases` raises `AmbiguousDatabaseError`.
|
Databases must be explicitly created with `create=True` or via `haiku-rag init` before use. Opening a nonexistent local database given as a path raises `FileNotFoundError`, naming the path; a configured or default database raises `SourceUnavailableError`, which names the database rather than its location. A path beside a configured `lancedb.databases` raises `AmbiguousDatabaseError`.
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
Read-only mode is useful for safely accessing databases without risk of modification. It blocks all write operations and downgrades an embedding provider/name mismatch to a warning instead of raising `ConfigMismatchError`.
|
Read-only mode is useful for safely accessing databases without risk of modification. It blocks all write operations and downgrades an embedding provider/name mismatch to a warning instead of raising `ConfigMismatchError`.
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ class DatabaseScope:
|
||||||
if database_name is not None:
|
if database_name is not None:
|
||||||
if database_name not in declared:
|
if database_name not in declared:
|
||||||
raise UnknownDatabaseError(
|
raise UnknownDatabaseError(
|
||||||
f"unknown database {database_name!r}; lancedb.databases names "
|
f"unknown database {database_name!r}; the databases are "
|
||||||
f"{', '.join(sorted(declared))}"
|
f"{', '.join(sorted(declared))}"
|
||||||
)
|
)
|
||||||
return cls(
|
return cls(
|
||||||
|
|
|
||||||
|
|
@ -113,13 +113,15 @@ class SingleDatabaseSession:
|
||||||
# A path the caller gave may be named: the caller knows it already.
|
# A path the caller gave may be named: the caller knows it already.
|
||||||
if self.ref.given:
|
if self.ref.given:
|
||||||
raise
|
raise
|
||||||
failure = type(error).__name__
|
failure = (
|
||||||
|
"does not exist; create it with `haiku-rag init` or `create=True`"
|
||||||
|
if isinstance(error, FileNotFoundError)
|
||||||
|
else f"could not be opened: {type(error).__name__}"
|
||||||
|
)
|
||||||
if failure is not None:
|
if failure is not None:
|
||||||
# Raised outside the handler: the exception carries neither a cause
|
# Raised outside the handler: the exception carries neither a cause
|
||||||
# nor a location-bearing context.
|
# nor a location-bearing context.
|
||||||
raise SourceUnavailableError(
|
raise SourceUnavailableError(f"database {self.source!r} {failure}")
|
||||||
f"database {self.source!r} could not be opened: {failure}"
|
|
||||||
)
|
|
||||||
self.document_repository = DocumentRepository(self.store)
|
self.document_repository = DocumentRepository(self.store)
|
||||||
self.chunk_repository = ChunkRepository(self.store)
|
self.chunk_repository = ChunkRepository(self.store)
|
||||||
self.document_item_repository = DocumentItemRepository(self.store)
|
self.document_item_repository = DocumentItemRepository(self.store)
|
||||||
|
|
|
||||||
|
|
@ -557,12 +557,6 @@ class TestPlacingTheIngesterDatabase:
|
||||||
with pytest.raises(typer.BadParameter, match="no name"):
|
with pytest.raises(typer.BadParameter, match="no name"):
|
||||||
_scope_for(Path("/"))
|
_scope_for(Path("/"))
|
||||||
|
|
||||||
def test_a_python_caller_cannot_pass_a_path(self):
|
|
||||||
from haiku.rag.ingester.app import IngesterApp
|
|
||||||
|
|
||||||
with pytest.raises(TypeError):
|
|
||||||
IngesterApp(config=AppConfig(), db_path="/db/other.lancedb") # type: ignore[call-arg] # ty: ignore[unknown-argument]
|
|
||||||
|
|
||||||
def test_one_configured_database_is_accepted(self, tmp_path):
|
def test_one_configured_database_is_accepted(self, tmp_path):
|
||||||
"""A one-entry mapping names which database to write."""
|
"""A one-entry mapping names which database to write."""
|
||||||
config = AppConfig(
|
config = AppConfig(
|
||||||
|
|
|
||||||
|
|
@ -474,6 +474,25 @@ class TestFailureNaming:
|
||||||
assert str(tmp_path) not in str(caught.value)
|
assert str(tmp_path) not in str(caught.value)
|
||||||
assert caught.value.__cause__ is None
|
assert caught.value.__cause__ is None
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_a_missing_default_database_names_the_remedy(self, tmp_path):
|
||||||
|
"""The location stays out of the message; the way to create the
|
||||||
|
database does not."""
|
||||||
|
from haiku.rag.config.models import AppConfig, StorageConfig
|
||||||
|
|
||||||
|
config = AppConfig(storage=StorageConfig(data_dir=tmp_path / "empty"))
|
||||||
|
|
||||||
|
with pytest.raises(SourceUnavailableError) as caught:
|
||||||
|
async with HaikuRAG(config=config):
|
||||||
|
pass
|
||||||
|
|
||||||
|
message = str(caught.value)
|
||||||
|
assert "database 'haiku.rag' does not exist" in message
|
||||||
|
assert "haiku-rag init" in message
|
||||||
|
assert "create=True" in message
|
||||||
|
assert str(tmp_path) not in message
|
||||||
|
assert caught.value.__cause__ is None
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_a_database_given_as_a_path_keeps_its_error(self, tmp_path):
|
async def test_a_database_given_as_a_path_keeps_its_error(self, tmp_path):
|
||||||
"""The caller gave the path, so the error may name it."""
|
"""The caller gave the path, so the error may name it."""
|
||||||
|
|
|
||||||
|
|
@ -54,11 +54,21 @@ class TestResolution:
|
||||||
assert scope.names == ("beta",)
|
assert scope.names == ("beta",)
|
||||||
|
|
||||||
def test_an_unknown_name_is_refused(self):
|
def test_an_unknown_name_is_refused(self):
|
||||||
|
"""The message lists the databases there are, configured or default."""
|
||||||
config = _config(databases={"alpha": "/data/alpha.lancedb"})
|
config = _config(databases={"alpha": "/data/alpha.lancedb"})
|
||||||
|
|
||||||
with pytest.raises(UnknownDatabaseError, match="unknown database 'nope'"):
|
with pytest.raises(
|
||||||
|
UnknownDatabaseError,
|
||||||
|
match="unknown database 'nope'.*the databases are alpha",
|
||||||
|
):
|
||||||
DatabaseScope.resolve(config, database_name="nope")
|
DatabaseScope.resolve(config, database_name="nope")
|
||||||
|
|
||||||
|
with pytest.raises(
|
||||||
|
UnknownDatabaseError,
|
||||||
|
match="unknown database 'nope'.*the databases are haiku.rag",
|
||||||
|
):
|
||||||
|
DatabaseScope.resolve(_config(), database_name="nope")
|
||||||
|
|
||||||
def test_no_selector_covers_the_configured_set_in_order(self):
|
def test_no_selector_covers_the_configured_set_in_order(self):
|
||||||
config = _config(
|
config = _config(
|
||||||
databases={"beta": "/data/b.lancedb", "alpha": "/data/a.lancedb"}
|
databases={"beta": "/data/b.lancedb", "alpha": "/data/a.lancedb"}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue