mirror of
https://github.com/lancedb/lancedb.git
synced 2026-11-13 14:16:54 +00:00
fix(python): surface Function drop warnings (#4880)
Remote Python `drop_function` currently discards the dependent-view warnings returned by SQL DROP. Both the synchronous and coroutine APIs now emit those warnings through `warnings.warn` while preserving their boolean result and executing DROP once. The server support is already merged in lancedb/sophon#9820. The Rust decoder reads warnings across every result batch, including an empty first batch. Missing warning columns remain compatible with older servers; malformed or null warning lists are rejected. The hidden binding bridge carries the warnings to Python. The separate `drop_function_async` API and its `(bool, Job)` contract are unchanged. Python warning filters can raise after the catalog mutation has completed. Validation: a compiled PyO3/public-server reproduction failed before the fix and passed afterward; the public async path also returned `true` with the expected dependent-view warning. Final source passes two focused Rust tests, two Python wrapper tests, strict targeted Clippy, formatting, and Ruff. Three decoder tests cover batch/null/type and older-server behavior. The public runs preceded a two-file simplification of the completed Job projection; focused final-source checks cover that delta. Current-main integration is conflict-free, and its overlapping changes concern independent retry-policy APIs/tests. Documentation now covers warning filters, the post-removal warnings-as-errors behavior, and silent compatibility with older servers. The follow-up changes only prose and a Python docstring; the runtime validation above remains applicable. Refs ENT-3659. Whole-issue independent acceptance remains pending.
This commit is contained in:
@@ -201,6 +201,9 @@ class Connection(object):
|
||||
async def drop_function(
|
||||
self, name: str, version: str, namespace_path: Optional[List[str]] = None
|
||||
) -> bool: ...
|
||||
async def _drop_function_with_warnings(
|
||||
self, name: str, version: str, namespace_path: Optional[List[str]] = None
|
||||
) -> Tuple[bool, list[str]]: ...
|
||||
async def drop_function_async(
|
||||
self, name: str, version: str, namespace_path: Optional[List[str]] = None
|
||||
) -> Tuple[bool, Job]: ...
|
||||
|
||||
@@ -11,6 +11,7 @@ from pathlib import Path
|
||||
from dataclasses import asdict
|
||||
import json
|
||||
import sys
|
||||
import warnings
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
@@ -1085,7 +1086,9 @@ class DBConnection(EnforceOverrides):
|
||||
Returns True when the revision was removed and False for a repeated drop
|
||||
or a missing name. A revision never published under an existing name is
|
||||
an error. Dropping the last visible revision releases the name and
|
||||
schedules physical cleanup in the background. Use
|
||||
schedules physical cleanup in the background. When the remote server
|
||||
reports dependent warnings, they are emitted as ``UserWarning`` before
|
||||
this method returns. Use
|
||||
:meth:`drop_function_async` to
|
||||
access the query job, whose completion confirms catalog removal of the
|
||||
revision. Local connections raise NotImplementedError.
|
||||
@@ -3647,9 +3650,29 @@ class AsyncConnection(object):
|
||||
Returns True when the requested revision was removed and False for a
|
||||
repeated drop or a missing name. A revision never published under an
|
||||
existing name is an error. Only the last visible revision's removal
|
||||
releases the name and schedules background object cleanup.
|
||||
releases the name and schedules background object cleanup. When the
|
||||
remote server reports a warning, it is emitted as ``UserWarning``
|
||||
before this method returns, subject to the standard ``warnings``
|
||||
filters. A warnings-as-errors setting can therefore raise after the
|
||||
catalog removal; it does not roll back the drop. Older servers that
|
||||
omit the warning field remain silent.
|
||||
"""
|
||||
return await self._inner.drop_function(name, version, namespace_path)
|
||||
dropped, drop_warnings = await self._drop_function_with_warnings(
|
||||
name, version, namespace_path
|
||||
)
|
||||
for warning in drop_warnings:
|
||||
warnings.warn(warning, UserWarning, stacklevel=2)
|
||||
return dropped
|
||||
|
||||
async def _drop_function_with_warnings(
|
||||
self,
|
||||
name: str,
|
||||
version: str,
|
||||
namespace_path: Optional[List[str]] = None,
|
||||
) -> Tuple[bool, List[str]]:
|
||||
return await self._inner._drop_function_with_warnings(
|
||||
name, version, namespace_path
|
||||
)
|
||||
|
||||
async def drop_function_async(
|
||||
self,
|
||||
|
||||
@@ -1024,11 +1024,14 @@ class RemoteDBConnection(DBConnection):
|
||||
version: str,
|
||||
namespace_path: Optional[List[str]] = None,
|
||||
) -> bool:
|
||||
return LOOP.run(
|
||||
self._conn.drop_function(
|
||||
dropped, drop_warnings = LOOP.run(
|
||||
self._conn._drop_function_with_warnings(
|
||||
name, version=version, namespace_path=namespace_path
|
||||
)
|
||||
)
|
||||
for warning in drop_warnings:
|
||||
warnings.warn(warning, UserWarning, stacklevel=2)
|
||||
return dropped
|
||||
|
||||
@override
|
||||
def drop_function_async(
|
||||
|
||||
@@ -1972,6 +1972,45 @@ def test_sync_catalog_path_preserves_execution_controls():
|
||||
assert _assert_execution_control_encoding(version) == definition
|
||||
|
||||
|
||||
def test_sync_drop_function_emits_server_warnings_without_changing_bool_api():
|
||||
class _DropConnection:
|
||||
async def drop_function(self, name, *, version, namespace_path=None):
|
||||
return True
|
||||
|
||||
async def _drop_function_with_warnings(
|
||||
self, name, *, version, namespace_path=None
|
||||
):
|
||||
return True, [
|
||||
'materialized view "pinned" references Function "embed" version "1"'
|
||||
]
|
||||
|
||||
connection = object.__new__(RemoteDBConnection)
|
||||
connection._conn = _DropConnection()
|
||||
|
||||
with pytest.warns(UserWarning, match="references Function"):
|
||||
assert connection.drop_function("embed", version="1") is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_drop_function_emits_server_warnings_without_changing_bool_api():
|
||||
class _Inner:
|
||||
async def _drop_function_with_warnings(
|
||||
self, name, version, namespace_path=None
|
||||
):
|
||||
return True, [
|
||||
'materialized view "pinned" references Function "embed" version "1"'
|
||||
]
|
||||
|
||||
async def drop_function(self, name, version, namespace_path=None):
|
||||
return True
|
||||
|
||||
connection = object.__new__(AsyncConnection)
|
||||
connection._inner = _Inner()
|
||||
|
||||
with pytest.warns(UserWarning, match="references Function"):
|
||||
assert await connection.drop_function("embed", version="1") is True
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _mock_remote_function_catalog():
|
||||
state = {"requests": [], "version": None}
|
||||
|
||||
@@ -928,6 +928,23 @@ impl Connection {
|
||||
})
|
||||
}
|
||||
|
||||
#[pyo3(signature = (name, version, namespace_path=None))]
|
||||
pub fn _drop_function_with_warnings(
|
||||
self_: PyRef<'_, Self>,
|
||||
name: String,
|
||||
version: String,
|
||||
namespace_path: Option<Vec<String>>,
|
||||
) -> PyResult<Bound<'_, PyAny>> {
|
||||
let inner = self_.get_inner()?.clone();
|
||||
let namespace_path = namespace_path.unwrap_or_default();
|
||||
future_into_py(self_.py(), async move {
|
||||
inner
|
||||
.drop_function_with_warnings(name, version, &namespace_path)
|
||||
.await
|
||||
.infer_error()
|
||||
})
|
||||
}
|
||||
|
||||
#[pyo3(signature = (name, version, namespace_path=None))]
|
||||
pub fn drop_function_async(
|
||||
self_: PyRef<'_, Self>,
|
||||
|
||||
Reference in New Issue
Block a user