uv_resolver/
python_requirement.rs

1use std::collections::Bound;
2
3use uv_distribution_types::{RequiresPython, RequiresPythonRange};
4use uv_pep440::Version;
5use uv_pep508::{MarkerEnvironment, MarkerTree};
6use uv_python::{Interpreter, PythonVersion};
7
8#[derive(Debug, Clone, Eq, PartialEq)]
9pub struct PythonRequirement {
10    source: PythonRequirementSource,
11    /// The exact installed version of Python.
12    exact: Version,
13    /// The installed version of Python.
14    installed: RequiresPython,
15    /// The target version of Python; that is, the version of Python for which we are resolving
16    /// dependencies. This is typically the same as the installed version, but may be different
17    /// when specifying an alternate Python version for the resolution.
18    target: RequiresPython,
19}
20
21impl PythonRequirement {
22    /// Create a [`PythonRequirement`] to resolve against both an [`Interpreter`] and a
23    /// [`PythonVersion`].
24    pub fn from_python_version(interpreter: &Interpreter, python_version: &PythonVersion) -> Self {
25        let exact = interpreter.python_full_version().version.clone();
26        let installed = interpreter
27            .python_full_version()
28            .version
29            .only_release()
30            .without_trailing_zeros();
31        let target = python_version
32            .python_full_version()
33            .only_release()
34            .without_trailing_zeros();
35        Self {
36            exact,
37            installed: RequiresPython::greater_than_equal_version(&installed),
38            target: RequiresPython::greater_than_equal_version(&target),
39            source: PythonRequirementSource::PythonVersion,
40        }
41    }
42
43    /// Create a [`PythonRequirement`] to resolve against both an [`Interpreter`] and a
44    /// [`MarkerEnvironment`].
45    pub fn from_requires_python(
46        interpreter: &Interpreter,
47        requires_python: RequiresPython,
48    ) -> Self {
49        Self::from_marker_environment(interpreter.markers(), requires_python)
50    }
51
52    /// Create a [`PythonRequirement`] to resolve against an [`Interpreter`].
53    pub fn from_interpreter(interpreter: &Interpreter) -> Self {
54        let exact = interpreter
55            .python_full_version()
56            .version
57            .clone()
58            .without_trailing_zeros();
59        let installed = interpreter
60            .python_full_version()
61            .version
62            .only_release()
63            .without_trailing_zeros();
64        Self {
65            exact,
66            installed: RequiresPython::greater_than_equal_version(&installed),
67            target: RequiresPython::greater_than_equal_version(&installed),
68            source: PythonRequirementSource::Interpreter,
69        }
70    }
71
72    /// Create a [`PythonRequirement`] from a [`MarkerEnvironment`] and a
73    /// specific `Requires-Python` directive.
74    ///
75    /// This has the same "source" as
76    /// [`PythonRequirement::from_requires_python`], but is useful for
77    /// constructing a `PythonRequirement` without an [`Interpreter`].
78    pub fn from_marker_environment(
79        marker_env: &MarkerEnvironment,
80        requires_python: RequiresPython,
81    ) -> Self {
82        let exact = marker_env
83            .python_full_version()
84            .version
85            .clone()
86            .without_trailing_zeros();
87        let installed = marker_env
88            .python_full_version()
89            .version
90            .only_release()
91            .without_trailing_zeros();
92        Self {
93            exact,
94            installed: RequiresPython::greater_than_equal_version(&installed),
95            target: requires_python,
96            source: PythonRequirementSource::RequiresPython,
97        }
98    }
99
100    /// Narrow the [`PythonRequirement`] to the given version, if it's stricter (i.e., greater)
101    /// than the current `Requires-Python` minimum.
102    ///
103    /// Returns `None` if the given range is not narrower than the current range.
104    pub fn narrow(&self, target: &RequiresPythonRange) -> Option<Self> {
105        Some(Self {
106            exact: self.exact.clone(),
107            installed: self.installed.clone(),
108            target: self.target.narrow(target)?,
109            source: self.source,
110        })
111    }
112
113    /// Split the [`PythonRequirement`] at the given version.
114    ///
115    /// For example, if the current requirement is `>=3.10`, and the split point is `3.11`, then
116    /// the result will be `>=3.10 and <3.11` and `>=3.11`.
117    pub fn split(&self, at: Bound<Version>) -> Option<(Self, Self)> {
118        let (lower, upper) = self.target.split(at)?;
119        Some((
120            Self {
121                exact: self.exact.clone(),
122                installed: self.installed.clone(),
123                target: lower,
124                source: self.source,
125            },
126            Self {
127                exact: self.exact.clone(),
128                installed: self.installed.clone(),
129                target: upper,
130                source: self.source,
131            },
132        ))
133    }
134
135    /// Returns `true` if the minimum version of Python required by the target is greater than the
136    /// installed version.
137    pub fn raises(&self, target: &RequiresPythonRange) -> bool {
138        target.lower() > self.target.range().lower()
139    }
140
141    /// Return the exact version of Python.
142    pub fn exact(&self) -> &Version {
143        &self.exact
144    }
145
146    /// Return the installed version of Python.
147    pub fn installed(&self) -> &RequiresPython {
148        &self.installed
149    }
150
151    /// Return the target version of Python.
152    pub fn target(&self) -> &RequiresPython {
153        &self.target
154    }
155
156    /// Return the source of the [`PythonRequirement`].
157    pub fn source(&self) -> PythonRequirementSource {
158        self.source
159    }
160
161    /// A wrapper around `RequiresPython::simplify_markers`. See its docs for
162    /// more info.
163    ///
164    /// When this `PythonRequirement` isn't `RequiresPython`, the given markers
165    /// are returned unchanged.
166    pub(crate) fn simplify_markers(&self, marker: MarkerTree) -> MarkerTree {
167        self.target.simplify_markers(marker)
168    }
169
170    /// Return a [`MarkerTree`] representing the Python requirement.
171    ///
172    /// See: [`RequiresPython::to_marker_tree`]
173    pub fn to_marker_tree(&self) -> MarkerTree {
174        self.target.to_marker_tree()
175    }
176}
177
178#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Hash, Ord)]
179pub enum PythonRequirementSource {
180    /// `--python-version`
181    PythonVersion,
182    /// `Requires-Python`
183    RequiresPython,
184    /// The discovered Python interpreter.
185    Interpreter,
186}