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