ruff_python_ast/
python_version.rs1use std::{fmt, str::FromStr};
2
3#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
7#[cfg_attr(feature = "cache", derive(ruff_macros::CacheKey))]
8#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
9pub struct PythonVersion {
10 pub major: u8,
11 pub minor: u8,
12}
13
14impl PythonVersion {
15 pub const PY37: PythonVersion = PythonVersion { major: 3, minor: 7 };
16 pub const PY38: PythonVersion = PythonVersion { major: 3, minor: 8 };
17 pub const PY39: PythonVersion = PythonVersion { major: 3, minor: 9 };
18 pub const PY310: PythonVersion = PythonVersion {
19 major: 3,
20 minor: 10,
21 };
22 pub const PY311: PythonVersion = PythonVersion {
23 major: 3,
24 minor: 11,
25 };
26 pub const PY312: PythonVersion = PythonVersion {
27 major: 3,
28 minor: 12,
29 };
30 pub const PY313: PythonVersion = PythonVersion {
31 major: 3,
32 minor: 13,
33 };
34 pub const PY314: PythonVersion = PythonVersion {
35 major: 3,
36 minor: 14,
37 };
38 pub const PY315: PythonVersion = PythonVersion {
39 major: 3,
40 minor: 15,
41 };
42
43 pub fn iter() -> impl Iterator<Item = PythonVersion> {
44 [
45 PythonVersion::PY37,
46 PythonVersion::PY38,
47 PythonVersion::PY39,
48 PythonVersion::PY310,
49 PythonVersion::PY311,
50 PythonVersion::PY312,
51 PythonVersion::PY313,
52 PythonVersion::PY314,
53 PythonVersion::PY315,
54 ]
55 .into_iter()
56 }
57
58 pub const fn lowest() -> Self {
60 Self::PY37
61 }
62
63 pub const fn latest() -> Self {
64 Self::PY314
65 }
66
67 pub fn latest_preview() -> Self {
69 let latest_preview = Self::PY315;
70 debug_assert!(latest_preview >= Self::latest());
71 latest_preview
72 }
73
74 pub const fn latest_ty() -> Self {
75 Self::PY314
77 }
78
79 pub const fn as_tuple(self) -> (u8, u8) {
80 (self.major, self.minor)
81 }
82
83 pub fn free_threaded_build_available(self) -> bool {
84 self >= PythonVersion::PY313
85 }
86
87 pub fn supports_pep_701(self) -> bool {
91 self >= Self::PY312
92 }
93
94 pub fn defers_annotations(self) -> bool {
95 self >= Self::PY314
96 }
97}
98
99impl Default for PythonVersion {
100 fn default() -> Self {
101 Self::PY310
102 }
103}
104
105impl From<(u8, u8)> for PythonVersion {
106 fn from(value: (u8, u8)) -> Self {
107 let (major, minor) = value;
108 Self { major, minor }
109 }
110}
111
112impl fmt::Display for PythonVersion {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 let PythonVersion { major, minor } = self;
115 write!(f, "{major}.{minor}")
116 }
117}
118
119#[derive(thiserror::Error, Debug, PartialEq, Eq, Clone)]
120pub enum PythonVersionDeserializationError {
121 #[error("Invalid python version `{0}`: expected `major.minor`")]
122 WrongPeriodNumber(Box<str>),
123 #[error("Invalid major version `{0}`: {1}")]
124 InvalidMajorVersion(Box<str>, #[source] std::num::ParseIntError),
125 #[error("Invalid minor version `{0}`: {1}")]
126 InvalidMinorVersion(Box<str>, #[source] std::num::ParseIntError),
127}
128
129impl TryFrom<(&str, &str)> for PythonVersion {
130 type Error = PythonVersionDeserializationError;
131
132 fn try_from(value: (&str, &str)) -> Result<Self, Self::Error> {
133 let (major, minor) = value;
134 Ok(Self {
135 major: major.parse().map_err(|err| {
136 PythonVersionDeserializationError::InvalidMajorVersion(Box::from(major), err)
137 })?,
138 minor: minor.parse().map_err(|err| {
139 PythonVersionDeserializationError::InvalidMinorVersion(Box::from(minor), err)
140 })?,
141 })
142 }
143}
144
145impl FromStr for PythonVersion {
146 type Err = PythonVersionDeserializationError;
147
148 fn from_str(s: &str) -> Result<Self, Self::Err> {
149 let (major, minor) = s
150 .split_once('.')
151 .ok_or_else(|| PythonVersionDeserializationError::WrongPeriodNumber(Box::from(s)))?;
152
153 Self::try_from((major, minor)).map_err(|err| {
154 if matches!(
156 err,
157 PythonVersionDeserializationError::InvalidMinorVersion(_, _)
158 ) && minor.contains('.')
159 {
160 PythonVersionDeserializationError::WrongPeriodNumber(Box::from(s))
161 } else {
162 err
163 }
164 })
165 }
166}
167
168#[cfg(feature = "serde")]
169mod serde {
170 use super::PythonVersion;
171
172 impl<'de> serde::Deserialize<'de> for PythonVersion {
173 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
174 where
175 D: serde::Deserializer<'de>,
176 {
177 String::deserialize(deserializer)?
178 .parse()
179 .map_err(serde::de::Error::custom)
180 }
181 }
182
183 impl serde::Serialize for PythonVersion {
184 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
185 where
186 S: serde::Serializer,
187 {
188 serializer.serialize_str(&self.to_string())
189 }
190 }
191}
192
193#[cfg(feature = "schemars")]
194mod schemars {
195 use super::PythonVersion;
196 use schemars::{JsonSchema, Schema, SchemaGenerator};
197 use serde_json::Value;
198
199 impl JsonSchema for PythonVersion {
200 fn schema_name() -> std::borrow::Cow<'static, str> {
201 std::borrow::Cow::Borrowed("PythonVersion")
202 }
203
204 fn json_schema(_gen: &mut SchemaGenerator) -> Schema {
205 let mut any_of: Vec<Value> = vec![
206 schemars::json_schema!({
207 "type": "string",
208 "pattern": r"^\d+\.\d+$",
209 })
210 .into(),
211 ];
212
213 for version in Self::iter() {
214 let mut schema = schemars::json_schema!({
215 "const": version.to_string(),
216 });
217 schema.ensure_object().insert(
218 "description".to_string(),
219 Value::String(format!("Python {version}")),
220 );
221 any_of.push(schema.into());
222 }
223
224 let mut schema = Schema::default();
225 schema
226 .ensure_object()
227 .insert("anyOf".to_string(), Value::Array(any_of));
228 schema
229 }
230 }
231}