feat(rust-bridge): expose module version and build info

This commit is contained in:
Yujong Lee 2026-09-03 13:56:45 -07:00
parent df73c623b2
commit 8bfe009522
5 changed files with 98 additions and 0 deletions

View file

@ -1456,6 +1456,7 @@ dependencies = [
"litellm-python-interop",
"pyo3",
"pyo3-async-runtimes",
"pyo3-build-config",
"serde",
"serde_json",
"tokio",

View file

@ -28,6 +28,9 @@ serde.workspace = true
serde_json.workspace = true
tokio.workspace = true
[build-dependencies]
pyo3-build-config = "0.29.2"
[dev-dependencies]
criterion = "0.8.2"
tokio-tungstenite.workspace = true

View file

@ -1,6 +1,34 @@
use std::path::Path;
fn pyo3_version_from_lock() -> Option<String> {
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").ok()?;
let lockfile = Path::new(&manifest_dir).join("../../Cargo.lock");
let contents = std::fs::read_to_string(lockfile).ok()?;
let lines: Vec<&str> = contents.lines().collect();
for pair in lines.windows(2) {
if pair[0].trim() != "name = \"pyo3\"" {
continue;
}
if let Some(version) = pair[1].trim().strip_prefix("version = ") {
return Some(version.trim_matches('"').to_owned());
}
}
None
}
fn main() {
pyo3_build_config::use_pyo3_cfgs();
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("macos") {
println!("cargo:rustc-cdylib-link-arg=-undefined");
println!("cargo:rustc-cdylib-link-arg=dynamic_lookup");
}
println!(
"cargo:rustc-env=LITELLM_NATIVE_PROFILE={}",
std::env::var("PROFILE").unwrap_or_else(|_| "unknown".to_owned())
);
println!(
"cargo:rustc-env=LITELLM_PYO3_VERSION={}",
pyo3_version_from_lock().unwrap_or_else(|| "unknown".to_owned())
);
println!("cargo:rerun-if-changed=../../Cargo.lock");
}

View file

@ -9,6 +9,17 @@ fn gil_stats(py: Python<'_>) -> PyResult<Py<PyAny>> {
Ok(stats.into_any().unbind())
}
#[pyfunction]
fn build_info(py: Python<'_>) -> PyResult<Py<PyAny>> {
let info = PyDict::new(py);
info.set_item("profile", env!("LITELLM_NATIVE_PROFILE"))?;
info.set_item("pyo3", env!("LITELLM_PYO3_VERSION"))?;
info.set_item("abi3", cfg!(Py_LIMITED_API))?;
info.set_item("gil_disabled", cfg!(Py_GIL_DISABLED))?;
info.set_item("debug_assertions", cfg!(debug_assertions))?;
Ok(info.into_any().unbind())
}
#[cfg(feature = "panic-test")]
#[pyfunction]
fn _panic_for_test() {
@ -17,6 +28,7 @@ fn _panic_for_test() {
pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
module.add_function(wrap_pyfunction!(gil_stats, module)?)?;
module.add_function(wrap_pyfunction!(build_info, module)?)?;
#[cfg(feature = "panic-test")]
module.add_function(wrap_pyfunction!(_panic_for_test, module)?)?;
Ok(())

View file

@ -68,6 +68,7 @@ mod _native {
#[pymodule_init]
fn init(module: &Bound<'_, PyModule>) -> PyResult<()> {
module.add("__version__", env!("CARGO_PKG_VERSION"))?;
super::errors::register(module)?;
super::routes::register(module)?;
module.add_class::<super::ResponsesWebSocketConnection>()?;
@ -107,6 +108,7 @@ mod tests {
"achat_completions",
"ResponsesWebSocketConnection",
"gil_stats",
"build_info",
];
let public_names: Vec<String> = module
@ -118,6 +120,58 @@ mod tests {
.filter(|name| !name.starts_with("__"))
.collect();
assert_eq!(public_names, expected);
let version = module
.getattr("__version__")
.expect("module should expose __version__")
.extract::<String>()
.expect("__version__ should be a string");
assert_eq!(version, env!("CARGO_PKG_VERSION"));
});
}
#[test]
fn version_and_build_info_report_expected_shape() {
Python::initialize();
Python::attach(|py| {
let module = pyo3::wrap_pymodule!(_native)(py).into_bound(py);
let version = module
.getattr("__version__")
.expect("module should expose __version__")
.extract::<String>()
.expect("__version__ should be a string");
let segments: Vec<&str> = version.split('.').collect();
assert_eq!(segments.len(), 3, "__version__ should be x.y.z: {version}");
for segment in segments {
assert!(
!segment.is_empty() && segment.bytes().all(|b| b.is_ascii_digit()),
"__version__ segments should be numeric: {version}"
);
}
let build_info = module
.getattr("build_info")
.expect("module should expose build_info")
.call0()
.expect("build_info should be callable")
.cast_into::<PyDict>()
.expect("build_info should return a dict");
let mut keys: Vec<String> = build_info
.keys()
.extract::<Vec<String>>()
.expect("build_info keys should be strings");
keys.sort();
assert_eq!(
keys,
[
"abi3",
"debug_assertions",
"gil_disabled",
"profile",
"pyo3"
]
);
});
}