fix(python): validate function package requirements before registration (#4971)

Function dependency typos currently reach the environment build before
failing. Validate pip requirements with
`packaging.requirements.Requirement` and conda requirements with
Rattler's MatchSpec parser when constructing a function definition,
before source packaging or registration. Errors identify the original
entry and its index.

```python
@lancedb.function(pip=["numpy", "torch>=2.0,,<3"])
def score(value: float) -> float:
    return value
# ValueError: invalid pip[1] requirement 'torch>=2.0,,<3': ...
```

Preserves pip extras, markers, named references, bare archive/git URLs,
and conda channel/build specifications. The existing native extension
exposes the conda parser, so no additional Python package is required.
Adds regression coverage for invalid and supported forms, including the
deprecated `udf` alias.
This commit is contained in:
Jack Ye
2026-11-11 22:56:36 -08:00
committed by GitHub
parent 42f11d13a2
commit ee231aad06
7 changed files with 450 additions and 7 deletions
+1
View File
@@ -150,6 +150,7 @@ pub fn _lancedb(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(table::tokenize, m)?)?;
m.add_function(wrap_pyfunction!(permutation::async_permutation_builder, m)?)?;
m.add_function(wrap_pyfunction!(util::validate_table_name, m)?)?;
m.add_function(wrap_pyfunction!(util::validate_conda_requirement, m)?)?;
m.add_function(wrap_pyfunction!(util::validate_function_retry_policy, m)?)?;
m.add_function(wrap_pyfunction!(util::parse_field_path, m)?)?;
m.add_function(wrap_pyfunction!(query::fts_query_to_json, m)?)?;
+8
View File
@@ -9,6 +9,7 @@ use pyo3::{
exceptions::{PyRuntimeError, PyValueError},
pyfunction,
};
use rattler_conda_types::{MatchSpec, ParseStrictness};
/// A wrapper around a rust builder
///
@@ -61,6 +62,13 @@ pub fn validate_table_name(table_name: &str) -> PyResult<()> {
.map_err(|e| PyValueError::new_err(e.to_string()))
}
#[pyfunction(name = "_validate_conda_requirement")]
pub fn validate_conda_requirement(requirement: &str) -> PyResult<()> {
MatchSpec::from_str(requirement, ParseStrictness::Lenient)
.map(|_| ())
.map_err(|error| PyValueError::new_err(error.to_string()))
}
/// Parse field references with the same identifier syntax as Lance writes.
#[pyfunction(name = "_parse_field_path")]
pub fn parse_field_path(path: &str) -> PyResult<Vec<String>> {