Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 153 additions & 3 deletions litellm-rust/crates/python-bridge/src/python_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,13 @@ impl PythonSettings {
pub(crate) fn read_or_unset(self, py: Python<'_>) -> PyResult<Option<Snapshot<'_>>> {
match self.read(py) {
Ok(snapshot) => Ok(Some(snapshot)),
Err(error) if error.is_instance_of::<PyModuleNotFoundError>(py) => Ok(None),
Err(error) => Err(error),
Err(error) => {
if missing_module(py, &error, "litellm")? {
Ok(None)
} else {
Err(error)
}
}
}
}

Expand All @@ -56,9 +61,24 @@ impl PythonSettings {
}
}

fn missing_module(py: Python<'_>, error: &PyErr, expected: &str) -> PyResult<bool> {
if !error.is_instance_of::<PyModuleNotFoundError>(py) {
return Ok(false);
}
Ok(error
.value(py)
.getattr("name")?
.extract::<Option<String>>()?
.is_some_and(|name| name == expected))
}

#[cfg(test)]
mod tests {
use pyo3::{exceptions::PyRuntimeError, prelude::*, types::PyDict};
use pyo3::{
exceptions::{PyImportError, PyModuleNotFoundError, PyRuntimeError},
prelude::*,
types::PyDict,
};

use super::PythonSettings;
use crate::coercion::FieldSpec;
Expand Down Expand Up @@ -150,4 +170,134 @@ values = (Descriptor(), SimpleNamespace(flag=Truth()))
);
});
}

#[test]
fn read_or_unset_returns_none_when_litellm_is_missing() {
Python::initialize();
Python::attach(|py| {
let locals = PyDict::new(py);
py.run(
c"
import sys
class MissingLitellm:
def find_spec(self, fullname, path=None, target=None):
if fullname == 'litellm':
raise ModuleNotFoundError('No module named litellm', name='litellm')
sys.meta_path.insert(0, MissingLitellm())
sys.modules.pop('litellm', None)
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Resolved
",
Some(&locals),
Some(&locals),
)
.unwrap();
let result = PythonSettings::Http.read_or_unset(py);
assert!(result.unwrap().is_none());
py.run(
c"
sys.meta_path.pop(0)
",
Some(&locals),
Some(&locals),
)
.unwrap();
});
}

#[test]
fn read_or_unset_propagates_nested_module_not_found_errors() {
Python::initialize();
Python::attach(|py| {
let locals = PyDict::new(py);
py.run(
c"
import sys
import types
litellm = types.ModuleType('litellm')
litellm.__path__ = []
rust_bridge = types.ModuleType('litellm.rust_bridge')
rust_bridge.__path__ = []
settings = types.ModuleType('litellm.rust_bridge.settings')
def http_settings():
raise ModuleNotFoundError('No module named certifi', name='certifi')
settings.http_settings = http_settings
litellm.rust_bridge = rust_bridge
rust_bridge.settings = settings
sys.modules['litellm'] = litellm
sys.modules['litellm.rust_bridge'] = rust_bridge
sys.modules['litellm.rust_bridge.settings'] = settings
",
Some(&locals),
Some(&locals),
)
.unwrap();
let error = match PythonSettings::Http.read_or_unset(py) {
Ok(_) => panic!("nested module errors must propagate"),
Err(error) => error,
};
assert!(error.is_instance_of::<PyModuleNotFoundError>(py));
assert_eq!(
error
.value(py)
.getattr("name")
.unwrap()
.extract::<String>()
.unwrap(),
"certifi"
);
py.run(
c"
for name in ('litellm.rust_bridge.settings', 'litellm.rust_bridge', 'litellm'):
sys.modules.pop(name, None)
",
Some(&locals),
Some(&locals),
)
.unwrap();
});
}

#[test]
fn read_or_unset_propagates_import_errors() {
Python::initialize();
Python::attach(|py| {
let locals = PyDict::new(py);
py.run(
c"
import sys
import types
litellm = types.ModuleType('litellm')
litellm.__path__ = []
rust_bridge = types.ModuleType('litellm.rust_bridge')
rust_bridge.__path__ = []
settings = types.ModuleType('litellm.rust_bridge.settings')
def http_settings():
raise ImportError('cannot import name setting')
settings.http_settings = http_settings
litellm.rust_bridge = rust_bridge
rust_bridge.settings = settings
sys.modules['litellm'] = litellm
sys.modules['litellm.rust_bridge'] = rust_bridge
sys.modules['litellm.rust_bridge.settings'] = settings
",
Some(&locals),
Some(&locals),
)
.unwrap();
let error = match PythonSettings::Http.read_or_unset(py) {
Ok(_) => panic!("import errors must propagate"),
Err(error) => error,
};
assert!(error.is_instance_of::<PyImportError>(py));
assert_eq!(error.to_string(), "ImportError: cannot import name setting");
py.run(
c"
for name in ('litellm.rust_bridge.settings', 'litellm.rust_bridge', 'litellm'):
sys.modules.pop(name, None)
",
Some(&locals),
Some(&locals),
)
.unwrap();
});
}
}
Loading