Skip to main content

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