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)] |
18 | pub struct PythonVersionInfo<'py> { |
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<&'py str>, |
27 | } |
28 | |
29 | impl<'py> PythonVersionInfo<'py> { |
30 | /// Parses a hard-coded Python interpreter version string (e.g. 3.9.0a4+). |
31 | pub(crate) fn from_str(version_number_str: &'py str) -> Result<Self, &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.split('.' ); |
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 | if parts.next().is_some() { |
48 | return Err("Python version string has too many parts" ); |
49 | }; |
50 | |
51 | let major = major_str |
52 | .parse() |
53 | .map_err(|_| "Python major version not an integer" )?; |
54 | let (minor, suffix) = split_and_parse_number(minor_str); |
55 | if suffix.is_some() { |
56 | assert!(patch_str.is_none()); |
57 | return Ok(PythonVersionInfo { |
58 | major, |
59 | minor, |
60 | patch: 0, |
61 | suffix, |
62 | }); |
63 | } |
64 | |
65 | let (patch, suffix) = patch_str.map(split_and_parse_number).unwrap_or_default(); |
66 | Ok(PythonVersionInfo { |
67 | major, |
68 | minor, |
69 | patch, |
70 | suffix, |
71 | }) |
72 | } |
73 | } |
74 | |
75 | impl PartialEq<(u8, u8)> for PythonVersionInfo<'_> { |
76 | fn eq(&self, other: &(u8, u8)) -> bool { |
77 | self.major == other.0 && self.minor == other.1 |
78 | } |
79 | } |
80 | |
81 | impl PartialEq<(u8, u8, u8)> for PythonVersionInfo<'_> { |
82 | fn eq(&self, other: &(u8, u8, u8)) -> bool { |
83 | self.major == other.0 && self.minor == other.1 && self.patch == other.2 |
84 | } |
85 | } |
86 | |
87 | impl PartialOrd<(u8, u8)> for PythonVersionInfo<'_> { |
88 | fn partial_cmp(&self, other: &(u8, u8)) -> Option<std::cmp::Ordering> { |
89 | (self.major, self.minor).partial_cmp(other) |
90 | } |
91 | } |
92 | |
93 | impl PartialOrd<(u8, u8, u8)> for PythonVersionInfo<'_> { |
94 | fn partial_cmp(&self, other: &(u8, u8, u8)) -> Option<std::cmp::Ordering> { |
95 | (self.major, self.minor, self.patch).partial_cmp(other) |
96 | } |
97 | } |
98 | |
99 | #[cfg (test)] |
100 | mod test { |
101 | use super::*; |
102 | use crate::Python; |
103 | #[test ] |
104 | fn test_python_version_info() { |
105 | Python::with_gil(|py| { |
106 | let version = py.version_info(); |
107 | #[cfg (Py_3_7)] |
108 | assert!(version >= (3, 7)); |
109 | #[cfg (Py_3_7)] |
110 | assert!(version >= (3, 7, 0)); |
111 | #[cfg (Py_3_8)] |
112 | assert!(version >= (3, 8)); |
113 | #[cfg (Py_3_8)] |
114 | assert!(version >= (3, 8, 0)); |
115 | #[cfg (Py_3_9)] |
116 | assert!(version >= (3, 9)); |
117 | #[cfg (Py_3_9)] |
118 | assert!(version >= (3, 9, 0)); |
119 | #[cfg (Py_3_10)] |
120 | assert!(version >= (3, 10)); |
121 | #[cfg (Py_3_10)] |
122 | assert!(version >= (3, 10, 0)); |
123 | #[cfg (Py_3_11)] |
124 | assert!(version >= (3, 11)); |
125 | #[cfg (Py_3_11)] |
126 | assert!(version >= (3, 11, 0)); |
127 | }); |
128 | } |
129 | |
130 | #[test ] |
131 | fn test_python_version_info_parse() { |
132 | assert!(PythonVersionInfo::from_str("3.5.0a1" ).unwrap() >= (3, 5, 0)); |
133 | assert!(PythonVersionInfo::from_str("3.5+" ).unwrap() >= (3, 5, 0)); |
134 | assert!(PythonVersionInfo::from_str("3.5+" ).unwrap() == (3, 5, 0)); |
135 | assert!(PythonVersionInfo::from_str("3.5+" ).unwrap() != (3, 5, 1)); |
136 | assert!(PythonVersionInfo::from_str("3.5.2a1+" ).unwrap() < (3, 5, 3)); |
137 | assert!(PythonVersionInfo::from_str("3.5.2a1+" ).unwrap() == (3, 5, 2)); |
138 | assert!(PythonVersionInfo::from_str("3.5.2a1+" ).unwrap() == (3, 5)); |
139 | assert!(PythonVersionInfo::from_str("3.5+" ).unwrap() == (3, 5)); |
140 | assert!(PythonVersionInfo::from_str("3.5.2a1+" ).unwrap() < (3, 6)); |
141 | assert!(PythonVersionInfo::from_str("3.5.2a1+" ).unwrap() > (3, 4)); |
142 | } |
143 | } |
144 | |