pyo3/
version.rs

1/// Represents the major, minor, and patch (if any) versions of this interpreter.
2///
3/// This struct is usually created with [`Python::version`].
4///
5/// # Examples
6///
7/// ```rust
8/// # use pyo3::Python;
9/// Python::with_gil(|py| {
10///     // PyO3 supports Python 3.7 and up.
11///     assert!(py.version_info() >= (3, 7));
12///     assert!(py.version_info() >= (3, 7, 0));
13/// });
14/// ```
15///
16/// [`Python::version`]: crate::marker::Python::version
17#[derive(Debug)]
18pub struct PythonVersionInfo<'a> {
19    /// Python major version (e.g. `3`).
20    pub major: u8,
21    /// Python minor version (e.g. `11`).
22    pub minor: u8,
23    /// Python patch version (e.g. `0`).
24    pub patch: u8,
25    /// Python version suffix, if applicable (e.g. `a0`).
26    pub suffix: Option<&'a str>,
27}
28
29impl<'a> PythonVersionInfo<'a> {
30    /// Parses a hard-coded Python interpreter version string (e.g. 3.9.0a4+).
31    pub(crate) fn from_str(version_number_str: &'a str) -> Result<PythonVersionInfo<'a>, &'a str> {
32        fn split_and_parse_number(version_part: &str) -> (u8, Option<&str>) {
33            match version_part.find(|c: char| !c.is_ascii_digit()) {
34                None => (version_part.parse().unwrap(), None),
35                Some(version_part_suffix_start) => {
36                    let (version_part, version_part_suffix) =
37                        version_part.split_at(version_part_suffix_start);
38                    (version_part.parse().unwrap(), Some(version_part_suffix))
39                }
40            }
41        }
42
43        let mut parts = version_number_str.splitn(3, '.');
44        let major_str = parts.next().ok_or("Python major version missing")?;
45        let minor_str = parts.next().ok_or("Python minor version missing")?;
46        let patch_str = parts.next();
47
48        let major = major_str
49            .parse()
50            .map_err(|_| "Python major version not an integer")?;
51        let (minor, suffix) = split_and_parse_number(minor_str);
52        if suffix.is_some() {
53            assert!(patch_str.is_none());
54            return Ok(PythonVersionInfo {
55                major,
56                minor,
57                patch: 0,
58                suffix,
59            });
60        }
61
62        let (patch, suffix) = patch_str.map(split_and_parse_number).unwrap_or_default();
63        Ok(PythonVersionInfo {
64            major,
65            minor,
66            patch,
67            suffix,
68        })
69    }
70}
71
72impl PartialEq<(u8, u8)> for PythonVersionInfo<'_> {
73    fn eq(&self, other: &(u8, u8)) -> bool {
74        self.major == other.0 && self.minor == other.1
75    }
76}
77
78impl PartialEq<(u8, u8, u8)> for PythonVersionInfo<'_> {
79    fn eq(&self, other: &(u8, u8, u8)) -> bool {
80        self.major == other.0 && self.minor == other.1 && self.patch == other.2
81    }
82}
83
84impl PartialOrd<(u8, u8)> for PythonVersionInfo<'_> {
85    fn partial_cmp(&self, other: &(u8, u8)) -> Option<std::cmp::Ordering> {
86        (self.major, self.minor).partial_cmp(other)
87    }
88}
89
90impl PartialOrd<(u8, u8, u8)> for PythonVersionInfo<'_> {
91    fn partial_cmp(&self, other: &(u8, u8, u8)) -> Option<std::cmp::Ordering> {
92        (self.major, self.minor, self.patch).partial_cmp(other)
93    }
94}
95
96#[cfg(test)]
97mod test {
98    use super::*;
99    use crate::Python;
100    #[test]
101    fn test_python_version_info() {
102        Python::with_gil(|py| {
103            let version = py.version_info();
104            #[cfg(Py_3_7)]
105            assert!(version >= (3, 7));
106            #[cfg(Py_3_7)]
107            assert!(version >= (3, 7, 0));
108            #[cfg(Py_3_8)]
109            assert!(version >= (3, 8));
110            #[cfg(Py_3_8)]
111            assert!(version >= (3, 8, 0));
112            #[cfg(Py_3_9)]
113            assert!(version >= (3, 9));
114            #[cfg(Py_3_9)]
115            assert!(version >= (3, 9, 0));
116            #[cfg(Py_3_10)]
117            assert!(version >= (3, 10));
118            #[cfg(Py_3_10)]
119            assert!(version >= (3, 10, 0));
120            #[cfg(Py_3_11)]
121            assert!(version >= (3, 11));
122            #[cfg(Py_3_11)]
123            assert!(version >= (3, 11, 0));
124        });
125    }
126
127    #[test]
128    fn test_python_version_info_parse() {
129        assert!(PythonVersionInfo::from_str("3.5.0a1").unwrap() >= (3, 5, 0));
130        assert!(PythonVersionInfo::from_str("3.5+").unwrap() >= (3, 5, 0));
131        assert!(PythonVersionInfo::from_str("3.5+").unwrap() == (3, 5, 0));
132        assert!(PythonVersionInfo::from_str("3.5+").unwrap() != (3, 5, 1));
133        assert!(PythonVersionInfo::from_str("3.5.2a1+").unwrap() < (3, 5, 3));
134        assert!(PythonVersionInfo::from_str("3.5.2a1+").unwrap() == (3, 5, 2));
135        assert!(PythonVersionInfo::from_str("3.5.2a1+").unwrap() == (3, 5));
136        assert!(PythonVersionInfo::from_str("3.5+").unwrap() == (3, 5));
137        assert!(PythonVersionInfo::from_str("3.5.2a1+").unwrap() < (3, 6));
138        assert!(PythonVersionInfo::from_str("3.5.2a1+").unwrap() > (3, 4));
139        assert!(PythonVersionInfo::from_str("3.11.3+chromium.29").unwrap() >= (3, 11, 3));
140        assert_eq!(
141            PythonVersionInfo::from_str("3.11.3+chromium.29")
142                .unwrap()
143                .suffix,
144            Some("+chromium.29")
145        );
146    }
147}
⚠️ Internal Docs ⚠️ Not Public API 👉 Official Docs Here