diff --git a/docs/src/python/python.md b/docs/src/python/python.md index 36044d35d..143fe891c 100644 --- a/docs/src/python/python.md +++ b/docs/src/python/python.md @@ -17,6 +17,14 @@ The general flow of using the API is: pip install lancedb ``` +The core package does not require PyLance. Install LanceDB's optional PyLance +dependency when you need access to the underlying Lance dataset or +GPU-accelerated indexing: + +```shell +pip install "lancedb[pylance]" +``` + The following methods describe the synchronous API client. There is also an [asynchronous API client](#connections-asynchronous). diff --git a/python/README.md b/python/README.md index 550698500..f81e5a5e3 100644 --- a/python/README.md +++ b/python/README.md @@ -8,6 +8,14 @@ A Python library for [LanceDB](https://github.com/lancedb/lancedb). pip install lancedb ``` +The core package does not require PyLance. Install LanceDB's optional PyLance +dependency when you need access to the underlying Lance dataset or +GPU-accelerated indexing: + +```bash +pip install "lancedb[pylance]" +``` + ### Pre-Haswell x86_64 hosts: `lancedb-compat` The default `lancedb` wheel targets `x86-64-haswell` (AVX2 + FMA + F16C) for full performance on modern hardware. Pre-Haswell hosts — Intel Sandy Bridge / Ivy Bridge / Westmere; AMD Bulldozer / Piledriver / Steamroller — don't have AVX2 and crash with `Illegal instruction` at `import lancedb`. diff --git a/python/python/lancedb/table.py b/python/python/lancedb/table.py index ae36bac7a..0a8dfd83f 100644 --- a/python/python/lancedb/table.py +++ b/python/python/lancedb/table.py @@ -2251,7 +2251,7 @@ class LanceTable(Table): except ImportError: raise ImportError( "The lance library is required to use this function. " - "Please install with `pip install pylance`." + 'Please install with `pip install "lancedb[pylance]"`.' ) branch = self.current_branch() @@ -4759,7 +4759,7 @@ class AsyncTable: except ImportError: raise ImportError( "The lance library is required to use this function. " - "Please install with `pip install pylance`." + 'Please install with `pip install "lancedb[pylance]"`.' ) # lance.dataset() can't open a branch directly, so open the base table diff --git a/python/python/tests/test_import.py b/python/python/tests/test_import.py new file mode 100644 index 000000000..7295d0be7 --- /dev/null +++ b/python/python/tests/test_import.py @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright The LanceDB Authors + +import subprocess +import sys + + +def test_import_lancedb_without_pylance(): + script = """ +import sys + + +class BlockLanceImports: + def find_spec(self, fullname, path=None, target=None): + if fullname == "lance" or fullname.startswith("lance."): + raise ModuleNotFoundError(f"blocked optional dependency: {fullname}") + return None + + +sys.meta_path.insert(0, BlockLanceImports()) +import lancedb +""" + + result = subprocess.run( + [sys.executable, "-c", script], + capture_output=True, + text=True, + ) + + assert result.returncode == 0, result.stderr diff --git a/python/python/tests/test_table.py b/python/python/tests/test_table.py index 069527b21..cf24a5d6f 100644 --- a/python/python/tests/test_table.py +++ b/python/python/tests/test_table.py @@ -1258,6 +1258,14 @@ def test_branch_to_lance_targets_branch(tmp_path): assert table.to_lance().count_rows() == 1 +def test_to_lance_recommends_pylance_extra(tmp_db): + table = tmp_db.create_table("t", [{"i": 1}]) + + with patch("builtins.__import__", side_effect=ImportError): + with pytest.raises(ImportError, match=r"lancedb\[pylance\]"): + table.to_lance() + + @pytest.mark.asyncio async def test_async_to_lance(tmp_path): pytest.importorskip("lance") @@ -1269,6 +1277,16 @@ async def test_async_to_lance(tmp_path): assert dataset.count_rows() == 1 +@pytest.mark.asyncio +async def test_async_to_lance_recommends_pylance_extra(tmp_path): + db = await lancedb.connect_async(tmp_path) + table = await db.create_table("t", [{"i": 1}]) + + with patch("builtins.__import__", side_effect=ImportError): + with pytest.raises(ImportError, match=r"lancedb\[pylance\]"): + await table.to_lance() + + @pytest.mark.asyncio async def test_async_branch_to_lance_targets_branch(tmp_path): pytest.importorskip("lance")