Skip to content

Commit 1cab0d9

Browse files
committed
Add explicit index support
1 parent 3b21e91 commit 1cab0d9

File tree

51 files changed

+3416
-613
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+3416
-613
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
use crate::{IndexUrl, IndexUrlError};
2+
use std::str::FromStr;
3+
use thiserror::Error;
4+
use url::Url;
5+
6+
#[derive(Debug, Clone, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
7+
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
8+
pub struct Index {
9+
/// The name of the index.
10+
///
11+
/// Index names can be used to reference indexes elsewhere in the configuration. For example,
12+
/// you can pin a package to a specific index by name:
13+
///
14+
/// ```toml
15+
/// [[tool.uv.index]]
16+
/// name = "pytorch"
17+
/// url = "https://download.pytorch.org/whl/cu121"
18+
///
19+
/// [tool.uv.sources]
20+
/// torch = { index = "pytorch" }
21+
/// ```
22+
pub name: Option<String>,
23+
/// The URL of the index.
24+
///
25+
/// Expects to receive a URL (e.g., `https://pypi.org/simple`) or a local path.
26+
pub url: IndexUrl,
27+
/// Mark the index as explicit.
28+
///
29+
/// Explicit indexes will _only_ be used when explicitly enabled via a `[tool.uv.sources]`
30+
/// definition, as in:
31+
///
32+
/// ```toml
33+
/// [[tool.uv.index]]
34+
/// name = "pytorch"
35+
/// url = "https://download.pytorch.org/whl/cu121"
36+
/// explicit = true
37+
///
38+
/// [tool.uv.sources]
39+
/// torch = { index = "pytorch" }
40+
/// ```
41+
#[serde(default)]
42+
pub explicit: bool,
43+
/// Mark the index as the default index.
44+
///
45+
/// By default, uv uses PyPI as the default index, such that even if additional indexes are
46+
/// defined via `[[tool.uv.index]]`, PyPI will still be used as a fallback for packages that
47+
/// aren't found elsewhere. To disable the PyPI default, set `default = true` on at least one
48+
/// other index.
49+
///
50+
/// Marking an index as default will move it to the front of the list of indexes, such that it
51+
/// is given the highest priority when resolving packages.
52+
#[serde(default)]
53+
pub default: bool,
54+
// /// The type of the index.
55+
// ///
56+
// /// Indexes can either be PEP 503-compliant (i.e., a registry implementing the Simple API) or
57+
// /// structured as a flat list of distributions (e.g., `--find-links`). In both cases, indexes
58+
// /// can point to either local or remote resources.
59+
// #[serde(default)]
60+
// pub r#type: IndexKind,
61+
}
62+
63+
// #[derive(
64+
// Default, Debug, Copy, Clone, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize,
65+
// )]
66+
// #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
67+
// pub enum IndexKind {
68+
// /// A PEP 503 and/or PEP 691-compliant index.
69+
// #[default]
70+
// Simple,
71+
// /// An index containing a list of links to distributions (e.g., `--find-links`).
72+
// Flat,
73+
// }
74+
75+
impl Index {
76+
/// Initialize an [`Index`] from a pip-style `--index-url`.
77+
pub fn from_index_url(url: IndexUrl) -> Self {
78+
Self {
79+
url,
80+
name: None,
81+
explicit: false,
82+
default: true,
83+
}
84+
}
85+
86+
/// Initialize an [`Index`] from a pip-style `--extra-index-url`.
87+
pub fn from_extra_index_url(url: IndexUrl) -> Self {
88+
Self {
89+
url,
90+
name: None,
91+
explicit: false,
92+
default: false,
93+
}
94+
}
95+
96+
/// Return the [`IndexUrl`] of the index.
97+
pub fn url(&self) -> &IndexUrl {
98+
&self.url
99+
}
100+
101+
/// Return the raw [`URL`] of the index.
102+
pub fn raw_url(&self) -> &Url {
103+
self.url.url()
104+
}
105+
}
106+
107+
impl FromStr for Index {
108+
type Err = IndexSourceError;
109+
110+
fn from_str(s: &str) -> Result<Self, Self::Err> {
111+
// Determine whether the source is prefixed with a name, as in `name=https://pypi.org/simple`.
112+
if let Some((name, url)) = s.split_once('=') {
113+
if name.is_empty() {
114+
return Err(IndexSourceError::EmptyName);
115+
}
116+
117+
if name.chars().all(char::is_alphanumeric) {
118+
let url = IndexUrl::from_str(url)?;
119+
return Ok(Self {
120+
name: Some(name.to_string()),
121+
url,
122+
explicit: false,
123+
default: false,
124+
});
125+
}
126+
}
127+
128+
// Otherwise, assume the source is a URL.
129+
let url = IndexUrl::from_str(s)?;
130+
Ok(Self {
131+
name: None,
132+
url,
133+
explicit: false,
134+
default: false,
135+
})
136+
}
137+
}
138+
139+
/// An error that can occur when parsing an [`Index`].
140+
#[derive(Error, Debug)]
141+
pub enum IndexSourceError {
142+
#[error(transparent)]
143+
Url(#[from] IndexUrlError),
144+
#[error("Index included a name, but the name was empty")]
145+
EmptyName,
146+
}

0 commit comments

Comments
 (0)