test(python): load checked-in error classes for Rust tests (#4876)

Bare Rust test runners do not install the `lancedb` Python package, so
the typed-error tests fail with `ModuleNotFoundError`. Load the
checked-in exception modules once for the embedded interpreter,
preserving exception-class identity across parallel tests and the
existing type/cause assertions.

Rust CI now watches the binding sources and the Python exception modules
embedded by these tests, so those changes exercise the macOS Rust test
jobs.
This commit is contained in:
Xuanwo
2026-11-05 21:34:58 +08:00
committed by GitHub
parent 5def26112f
commit 57cd31e1aa
2 changed files with 72 additions and 4 deletions
+3
View File
@@ -14,6 +14,9 @@ on:
- docs/web-tests/blob-v2/rs/**
- nodejs/Cargo.toml
- python/Cargo.toml
- python/src/**
- python/python/lancedb/exceptions.py
- python/python/lancedb/remote/errors.py
- ci/build_no_lock.py
- ci/tests/test_build_no_lock.py
- .github/actions/setup-protoc/**
+69 -4
View File
@@ -386,16 +386,81 @@ fn is_transport_error(err: &(dyn std::error::Error + 'static)) -> bool {
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::ffi::CString;
use std::sync::{Arc, Once};
use lancedb::error::{JobFailure, RemoteResourceIdentity};
use pyo3::types::PyAnyMethods;
use pyo3::types::{PyAnyMethods, PyModule};
use super::*;
static INSTALL_PYTHON_ERROR_MODULES: Once = Once::new();
fn install_python_error_modules(py: Python<'_>) -> PyResult<()> {
// Load the checked-in error classes without requiring an installed wheel.
// The namespace exception is only a base for an unrelated remote class.
PyModule::from_code(
py,
c"import sys\n\nimport types\n\n\
lancedb = types.ModuleType('lancedb')\n\
lancedb.__path__ = []\n\
lancedb_remote = types.ModuleType('lancedb.remote')\n\
lancedb_remote.__path__ = []\n\
lancedb.remote = lancedb_remote\n\
namespace = types.ModuleType('lance_namespace')\n\
namespace_errors = types.ModuleType('lance_namespace.errors')\n\
namespace_errors.TableNotFoundError = RuntimeError\n\
namespace.errors = namespace_errors\n\
sys.modules['lancedb'] = lancedb\n\
sys.modules['lancedb.remote'] = lancedb_remote\n\
sys.modules['lance_namespace'] = namespace\n\
sys.modules['lance_namespace.errors'] = namespace_errors",
c"error_test_bootstrap.py",
c"lancedb_error_test_bootstrap",
)?;
let exceptions_code =
CString::new(include_str!("../../python/python/lancedb/exceptions.py")).unwrap();
let exceptions = PyModule::from_code(
py,
&exceptions_code,
c"exceptions.py",
c"lancedb.exceptions",
)?;
let lancedb = py.import("lancedb")?;
lancedb.setattr("exceptions", &exceptions)?;
py.import("sys")?
.getattr("modules")?
.set_item("lancedb.exceptions", &exceptions)?;
let remote_errors_code =
CString::new(include_str!("../../python/python/lancedb/remote/errors.py")).unwrap();
let remote_errors = PyModule::from_code(
py,
&remote_errors_code,
c"remote/errors.py",
c"lancedb.remote.errors",
)?;
lancedb
.getattr("remote")?
.setattr("errors", &remote_errors)?;
py.import("sys")?
.getattr("modules")?
.set_item("lancedb.remote.errors", &remote_errors)?;
Ok(())
}
fn ensure_python_error_modules() {
Python::initialize();
// Once waits without holding the GIL; only its one-time closure attaches.
INSTALL_PYTHON_ERROR_MODULES.call_once(|| {
Python::attach(install_python_error_modules).unwrap();
});
}
#[test]
fn generic_job_failure_retains_the_named_resource_as_its_cause() {
Python::initialize();
ensure_python_error_modules();
let source = LanceError::Sql {
message: "Function 'missing' was not found".into(),
request_id: "current-request".into(),
@@ -515,7 +580,7 @@ mod tests {
#[test]
fn direct_catalog_lookup_is_raised_as_the_named_resource_error() {
Python::initialize();
ensure_python_error_modules();
for (kind, name, namespace_path, exception) in [
(
"function",