fix(python): include namespaces in remote table repr (#4799)

A synchronous remote table opened in a namespace is displayed as if it
were in the database root. For example, `db.open_table("test",
namespace_path=["analytics", "features"])` shows
`RemoteTable(dev.test)`.

Include the cached namespace path using the existing `$`-delimited table
identifier: `RemoteTable(dev.analytics$features$test)`. Root output and
the leaf `table.name` stay the same. Using cached identity also keeps
repr available after pickle without reopening the native handle or
making a request.

Addresses the repr portion of ENT-3559. The native formatter already
carries the qualified identifier used by Python async and TypeScript;
this is a Python sync wrapper fix with no new API or naming rules.

Validation:

- Before the fix, three namespaced sync cases failed while root and
async controls passed. All six cases now pass through the real SDK and
mock HTTP endpoint.
- Covers default/explicit root, nested paths, valid
dotted/mixed-case/dashed names, and repr after pickle with the server
stopped. Requests remain limited to the original table open.
- Rebuilt current editable bindings in the repository's uv environment
with Python 3.12 and pinned Rust 1.97.0 through mbx.
- Ruff check and format check pass for the Python package (178 files).
- Required repository-wide Ruff check reports 48 pre-existing
diagnostics in six `docs/web-tests` files; the repository-wide format
check reports nine unchanged documentation files. Their source matches
the base commit. No lint suppression or documentation cleanup is
included.
This commit is contained in:
Yang Cen
2026-11-03 16:02:49 +08:00
committed by GitHub
parent 2d70bec53a
commit c16216e1c4
2 changed files with 61 additions and 1 deletions
+2 -1
View File
@@ -209,7 +209,8 @@ class RemoteTable(Table):
LOOP.run(self._table.replace_field_metadata(field_name, new_metadata))
def __repr__(self) -> str:
return f"RemoteTable({self.db_name}.{self.name})"
identifier = "$".join([*self._namespace_path, self.name])
return f"RemoteTable({self.db_name}.{identifier})"
@property
def schema(self) -> pa.Schema:
+59
View File
@@ -147,6 +147,65 @@ def missing_table_handler(cached, namespace_path):
return handler, request_ids
@pytest.mark.parametrize(
"namespace_path,name,identifier",
[
pytest.param(None, "test", "test", id="default-root"),
pytest.param([], "test", "test", id="explicit-root"),
pytest.param(["analytics"], "test", "analytics$test", id="namespace"),
pytest.param(
["analytics", "features"],
"test",
"analytics$features$test",
id="nested",
),
pytest.param(
["Team.Images", "raw-items"],
"frame.info",
"Team.Images$raw-items$frame.info",
id="sql-quoted-names",
),
],
)
def test_remote_table_repr_namespace(namespace_path, name, identifier):
requests = []
path = f"/v1/table/{identifier}/describe/"
def handler(request):
requests.append(request.path)
assert request.path == path
send_json(request, {})
expected = f"RemoteTable(dev.{identifier})"
with mock_lancedb_connection(handler) as db:
table = db.open_table(name, namespace_path=namespace_path)
assert table.name == name
assert repr(table) == expected
restored = pickle.loads(pickle.dumps(table))
# A diagnostic repr must work from cached identity without reopening the handle.
assert repr(restored) == expected
assert requests == [path]
@pytest.mark.asyncio
async def test_async_remote_table_repr_namespace():
requests = []
path = "/v1/table/analytics$features$frame.info/describe/"
def handler(request):
requests.append(request.path)
assert request.path == path
send_json(request, {})
async with mock_lancedb_connection_async(handler) as db:
table = await db.open_table(
"frame.info", namespace_path=["analytics", "features"]
)
assert repr(table) == "RemoteTable(analytics$features$frame.info)"
assert requests == [path]
@pytest.mark.parametrize("cached", [False, True])
@pytest.mark.parametrize("namespace_path", [[], ["ns"]])
def test_remote_open_missing_table(cached, namespace_path, missing_table_handler):