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<'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 | |
29 | impl<'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 | |
72 | impl 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 | |
78 | impl 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 | |
84 | impl 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 | |
90 | impl 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)] |
97 | mod 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 | } |
148 | |