uv_distribution_types/
specified_requirement.rs

1use std::borrow::Cow;
2use std::fmt::{Display, Formatter};
3
4use uv_git_types::GitReference;
5use uv_normalize::ExtraName;
6use uv_pep508::{MarkerEnvironment, MarkerTree, UnnamedRequirement};
7use uv_pypi_types::{Hashes, ParsedUrl};
8
9use crate::{Requirement, RequirementSource, VerbatimParsedUrl};
10
11/// An [`UnresolvedRequirement`] with additional metadata from `requirements.txt`, currently only
12/// hashes but in the future also editable and similar information.
13#[derive(Debug, Clone, Eq, PartialEq, Hash)]
14pub struct NameRequirementSpecification {
15    /// The actual requirement.
16    pub requirement: Requirement,
17    /// Hashes of the downloadable packages.
18    pub hashes: Vec<String>,
19}
20
21/// An [`UnresolvedRequirement`] with additional metadata from `requirements.txt`, currently only
22/// hashes but in the future also editable and similar information.
23#[derive(Debug, Clone, Eq, PartialEq, Hash)]
24pub struct UnresolvedRequirementSpecification {
25    /// The actual requirement.
26    pub requirement: UnresolvedRequirement,
27    /// Hashes of the downloadable packages.
28    pub hashes: Vec<String>,
29}
30
31/// A requirement read from a `requirements.txt` or `pyproject.toml` file.
32///
33/// It is considered unresolved as we still need to query the URL for the `Unnamed` variant to
34/// resolve the requirement name.
35///
36/// Analog to `RequirementsTxtRequirement` but with `distribution_types::Requirement` instead of
37/// `uv_pep508::Requirement`.
38#[derive(Hash, Debug, Clone, Eq, PartialEq)]
39pub enum UnresolvedRequirement {
40    /// The uv-specific superset over PEP 508 requirements specifier incorporating
41    /// `tool.uv.sources`.
42    Named(Requirement),
43    /// A PEP 508-like, direct URL dependency specifier.
44    Unnamed(UnnamedRequirement<VerbatimParsedUrl>),
45}
46
47impl Display for UnresolvedRequirement {
48    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
49        match self {
50            Self::Named(requirement) => write!(f, "{requirement}"),
51            Self::Unnamed(requirement) => write!(f, "{requirement}"),
52        }
53    }
54}
55
56impl UnresolvedRequirement {
57    /// Returns whether the markers apply for the given environment.
58    ///
59    /// When the environment is not given, this treats all marker expressions
60    /// that reference the environment as true. In other words, it does
61    /// environment independent expression evaluation. (Which in turn devolves
62    /// to "only evaluate marker expressions that reference an extra name.")
63    pub fn evaluate_markers(&self, env: Option<&MarkerEnvironment>, extras: &[ExtraName]) -> bool {
64        match self {
65            Self::Named(requirement) => requirement.evaluate_markers(env, extras),
66            Self::Unnamed(requirement) => requirement.evaluate_optional_environment(env, extras),
67        }
68    }
69
70    /// Augment a user-provided requirement by attaching any specification data that was provided
71    /// separately from the requirement itself (e.g., `--branch main`).
72    #[must_use]
73    pub fn augment_requirement(
74        self,
75        rev: Option<&str>,
76        tag: Option<&str>,
77        branch: Option<&str>,
78        marker: Option<MarkerTree>,
79    ) -> Self {
80        #[allow(clippy::manual_map)]
81        let git_reference = if let Some(rev) = rev {
82            Some(GitReference::from_rev(rev.to_string()))
83        } else if let Some(tag) = tag {
84            Some(GitReference::Tag(tag.to_string()))
85        } else if let Some(branch) = branch {
86            Some(GitReference::Branch(branch.to_string()))
87        } else {
88            None
89        };
90
91        match self {
92            Self::Named(mut requirement) => Self::Named(Requirement {
93                marker: marker
94                    .map(|marker| {
95                        requirement.marker.and(marker);
96                        requirement.marker
97                    })
98                    .unwrap_or(requirement.marker),
99                source: match requirement.source {
100                    RequirementSource::Git {
101                        git,
102                        subdirectory,
103                        url,
104                    } => {
105                        let git = if let Some(git_reference) = git_reference {
106                            git.with_reference(git_reference)
107                        } else {
108                            git
109                        };
110                        RequirementSource::Git {
111                            git,
112                            subdirectory,
113                            url,
114                        }
115                    }
116                    _ => requirement.source,
117                },
118                ..requirement
119            }),
120            Self::Unnamed(mut requirement) => Self::Unnamed(UnnamedRequirement {
121                marker: marker
122                    .map(|marker| {
123                        requirement.marker.and(marker);
124                        requirement.marker
125                    })
126                    .unwrap_or(requirement.marker),
127                url: match requirement.url.parsed_url {
128                    ParsedUrl::Git(mut git) => {
129                        if let Some(git_reference) = git_reference {
130                            git.url = git.url.with_reference(git_reference);
131                        }
132                        VerbatimParsedUrl {
133                            parsed_url: ParsedUrl::Git(git),
134                            verbatim: requirement.url.verbatim,
135                        }
136                    }
137                    _ => requirement.url,
138                },
139                ..requirement
140            }),
141        }
142    }
143
144    /// Returns the extras for the requirement.
145    pub fn extras(&self) -> &[ExtraName] {
146        match self {
147            Self::Named(requirement) => &requirement.extras,
148            Self::Unnamed(requirement) => &requirement.extras,
149        }
150    }
151
152    /// Return the version specifier or URL for the requirement.
153    pub fn source(&self) -> Cow<'_, RequirementSource> {
154        match self {
155            Self::Named(requirement) => Cow::Borrowed(&requirement.source),
156            Self::Unnamed(requirement) => Cow::Owned(RequirementSource::from_parsed_url(
157                requirement.url.parsed_url.clone(),
158                requirement.url.verbatim.clone(),
159            )),
160        }
161    }
162
163    /// Returns `true` if the requirement is editable.
164    pub fn is_editable(&self) -> bool {
165        match self {
166            Self::Named(requirement) => requirement.is_editable(),
167            Self::Unnamed(requirement) => requirement.url.is_editable(),
168        }
169    }
170
171    /// Return the hashes of the requirement, as specified in the URL fragment.
172    pub fn hashes(&self) -> Option<Hashes> {
173        match self {
174            Self::Named(requirement) => requirement.hashes(),
175            Self::Unnamed(requirement) => {
176                let fragment = requirement.url.verbatim.fragment()?;
177                Hashes::parse_fragment(fragment).ok()
178            }
179        }
180    }
181}
182
183impl NameRequirementSpecification {
184    /// Return the hashes of the requirement, as specified in the URL fragment.
185    pub fn hashes(&self) -> Option<Hashes> {
186        let RequirementSource::Url { ref url, .. } = self.requirement.source else {
187            return None;
188        };
189        let fragment = url.fragment()?;
190        Hashes::parse_fragment(fragment).ok()
191    }
192}
193
194impl From<Requirement> for UnresolvedRequirementSpecification {
195    fn from(requirement: Requirement) -> Self {
196        Self {
197            requirement: UnresolvedRequirement::Named(requirement),
198            hashes: Vec::new(),
199        }
200    }
201}
202
203impl From<Requirement> for NameRequirementSpecification {
204    fn from(requirement: Requirement) -> Self {
205        Self {
206            requirement,
207            hashes: Vec::new(),
208        }
209    }
210}