nautilus_core/python/
version.rs1#![expect(
19 clippy::manual_let_else,
20 reason = "Prefer explicit control flow for error handling"
21)]
22use pyo3::{Bound, prelude::*, types::PyTuple};
23
24#[must_use]
27pub fn get_python_version() -> String {
28 Python::attach(|py| {
29 let sys = match py.import("sys") {
30 Ok(mod_sys) => mod_sys,
31 Err(_) => return "Unavailable (failed to import sys)".to_string(),
32 };
33
34 let version_info = match sys.getattr("version_info") {
35 Ok(info) => info,
36 Err(_) => return "Unavailable (version_info not found)".to_string(),
37 };
38
39 let version_tuple: &Bound<'_, PyTuple> = match version_info.cast::<PyTuple>() {
40 Ok(tuple) => tuple,
41 Err(_) => return "Unavailable (failed to extract version_info)".to_string(),
42 };
43
44 let major = version_tuple
45 .get_item(0)
46 .ok()
47 .and_then(|item| item.extract::<i32>().ok())
48 .unwrap_or(-1);
49 let minor = version_tuple
50 .get_item(1)
51 .ok()
52 .and_then(|item| item.extract::<i32>().ok())
53 .unwrap_or(-1);
54 let micro = version_tuple
55 .get_item(2)
56 .ok()
57 .and_then(|item| item.extract::<i32>().ok())
58 .unwrap_or(-1);
59
60 if major == -1 || minor == -1 || micro == -1 {
61 "Unavailable (failed to extract version components)".to_string()
62 } else {
63 format!("{major}.{minor}.{micro}")
64 }
65 })
66}
67
68#[must_use]
69pub fn get_python_package_version(package_name: &str) -> String {
79 Python::attach(|py| match py.import(package_name) {
80 Ok(package) => match package.getattr("__version__") {
81 Ok(version_attr) => match version_attr.extract::<String>() {
82 Ok(version) => version,
83 Err(_) => "Unavailable (failed to extract version)".to_string(),
84 },
85 Err(_) => "Unavailable (__version__ attribute not found)".to_string(),
86 },
87 Err(_) => "Unavailable (failed to import package)".to_string(),
88 })
89}
90
91#[cfg(test)]
92mod tests {
93 use rstest::rstest;
94
95 use super::*;
96
97 #[rstest]
98 fn test_get_python_version_handles_malformed_version_info() {
99 Python::initialize();
100 let version = Python::attach(|py| -> PyResult<String> {
101 let sys = py.import("sys")?;
102 let original = sys.getattr("version_info")?;
103 sys.setattr("version_info", "malformed")?;
104 let version = get_python_version();
105 sys.setattr("version_info", original)?;
106 Ok(version)
107 })
108 .expect("test Python setup should succeed");
109
110 assert_eq!(version, "Unavailable (failed to extract version_info)");
111 }
112}