mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-02 12:59:45 +00:00
parent
439395dadf
commit
615e076beb
4 changed files with 58 additions and 52 deletions
|
|
@ -6,7 +6,7 @@ use tracing::{debug, instrument, warn};
|
|||
use url::Url;
|
||||
|
||||
use uv_pep440::VersionSpecifiers;
|
||||
use uv_pypi_types::{BaseUrl, CoreMetadata, File, Hashes, Yanked};
|
||||
use uv_pypi_types::{BaseUrl, CoreMetadata, Hashes, PypiFile, Yanked};
|
||||
use uv_pypi_types::{HashError, LenientVersionSpecifiers};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
|
|
@ -15,12 +15,12 @@ use uv_redacted::DisplaySafeUrl;
|
|||
pub(crate) struct SimpleHtml {
|
||||
/// The [`BaseUrl`] to which all relative URLs should be resolved.
|
||||
pub(crate) base: BaseUrl,
|
||||
/// The list of [`File`]s available for download sorted by filename.
|
||||
pub(crate) files: Vec<File>,
|
||||
/// The list of [`PypiFile`]s available for download sorted by filename.
|
||||
pub(crate) files: Vec<PypiFile>,
|
||||
}
|
||||
|
||||
impl SimpleHtml {
|
||||
/// Parse the list of [`File`]s from the simple HTML page returned by the given URL.
|
||||
/// Parse the list of [`PypiFile`]s from the simple HTML page returned by the given URL.
|
||||
#[instrument(skip_all, fields(url = % url))]
|
||||
pub(crate) fn parse(text: &str, url: &Url) -> Result<Self, Error> {
|
||||
let dom = tl::parse(text, tl::ParserOptions::default())?;
|
||||
|
|
@ -41,7 +41,7 @@ impl SimpleHtml {
|
|||
));
|
||||
|
||||
// Parse each `<a>` tag, to extract the filename, hash, and URL.
|
||||
let mut files: Vec<File> = dom
|
||||
let mut files: Vec<PypiFile> = dom
|
||||
.nodes()
|
||||
.iter()
|
||||
.filter_map(|node| node.as_tag())
|
||||
|
|
@ -76,10 +76,10 @@ impl SimpleHtml {
|
|||
Ok(Some(url))
|
||||
}
|
||||
|
||||
/// Parse a [`File`] from an `<a>` tag.
|
||||
/// Parse a [`PypiFile`] from an `<a>` tag.
|
||||
///
|
||||
/// Returns `None` if the `<a>` don't doesn't have an `href` attribute.
|
||||
fn parse_anchor(link: &HTMLTag) -> Result<Option<File>, Error> {
|
||||
fn parse_anchor(link: &HTMLTag) -> Result<Option<PypiFile>, Error> {
|
||||
// Extract the href.
|
||||
let Some(href) = link
|
||||
.attributes()
|
||||
|
|
@ -212,7 +212,7 @@ impl SimpleHtml {
|
|||
.map(|upload_time| html_escape::decode_html_entities(upload_time))
|
||||
.and_then(|upload_time| Timestamp::from_str(&upload_time).ok());
|
||||
|
||||
Ok(Some(File {
|
||||
Ok(Some(PypiFile {
|
||||
core_metadata,
|
||||
yanked,
|
||||
requires_python,
|
||||
|
|
@ -296,7 +296,7 @@ mod tests {
|
|||
},
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
PypiFile {
|
||||
core_metadata: None,
|
||||
filename: "Jinja2-3.1.2-py3-none-any.whl",
|
||||
hashes: Hashes {
|
||||
|
|
@ -353,7 +353,7 @@ mod tests {
|
|||
},
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
PypiFile {
|
||||
core_metadata: None,
|
||||
filename: "Jinja2-3.1.2-py3-none-any.whl",
|
||||
hashes: Hashes {
|
||||
|
|
@ -413,7 +413,7 @@ mod tests {
|
|||
},
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
PypiFile {
|
||||
core_metadata: None,
|
||||
filename: "Jinja2-3.1.2-py3-none-any.whl",
|
||||
hashes: Hashes {
|
||||
|
|
@ -470,7 +470,7 @@ mod tests {
|
|||
},
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
PypiFile {
|
||||
core_metadata: None,
|
||||
filename: "Jinja2-3.1.2+233fca715f49-py3-none-any.whl",
|
||||
hashes: Hashes {
|
||||
|
|
@ -527,7 +527,7 @@ mod tests {
|
|||
},
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
PypiFile {
|
||||
core_metadata: None,
|
||||
filename: "Jinja2-3.1.2-py3-none-any.whl",
|
||||
hashes: Hashes {
|
||||
|
|
@ -584,7 +584,7 @@ mod tests {
|
|||
},
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
PypiFile {
|
||||
core_metadata: None,
|
||||
filename: "torchtext-0.17.0+cpu-cp39-cp39-win_amd64.whl",
|
||||
hashes: Hashes {
|
||||
|
|
@ -639,7 +639,7 @@ mod tests {
|
|||
},
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
PypiFile {
|
||||
core_metadata: None,
|
||||
filename: "Jinja2-3.1.2-py3-none-any.whl",
|
||||
hashes: Hashes {
|
||||
|
|
@ -770,7 +770,7 @@ mod tests {
|
|||
},
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
PypiFile {
|
||||
core_metadata: None,
|
||||
filename: "Jinja2-3.1.2-py3-none-any.whl",
|
||||
hashes: Hashes {
|
||||
|
|
@ -825,7 +825,7 @@ mod tests {
|
|||
},
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
PypiFile {
|
||||
core_metadata: None,
|
||||
filename: "Jinja2-3.1.2-py3-none-any.whl",
|
||||
hashes: Hashes {
|
||||
|
|
@ -881,7 +881,7 @@ mod tests {
|
|||
},
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
PypiFile {
|
||||
core_metadata: None,
|
||||
filename: "Jinja2-3.1.2-py3-none-any.whl",
|
||||
hashes: Hashes {
|
||||
|
|
@ -938,7 +938,7 @@ mod tests {
|
|||
},
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
PypiFile {
|
||||
core_metadata: None,
|
||||
filename: "Jinja2-3.1.2-py3-none-any.whl",
|
||||
hashes: Hashes {
|
||||
|
|
@ -1012,7 +1012,7 @@ mod tests {
|
|||
},
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
PypiFile {
|
||||
core_metadata: None,
|
||||
filename: "jaxlib-0.1.52+cuda100-cp36-none-manylinux2010_x86_64.whl",
|
||||
hashes: Hashes {
|
||||
|
|
@ -1028,7 +1028,7 @@ mod tests {
|
|||
url: "https://storage.googleapis.com/jax-releases/cuda100/jaxlib-0.1.52+cuda100-cp36-none-manylinux2010_x86_64.whl",
|
||||
yanked: None,
|
||||
},
|
||||
File {
|
||||
PypiFile {
|
||||
core_metadata: None,
|
||||
filename: "jaxlib-0.1.52+cuda100-cp37-none-manylinux2010_x86_64.whl",
|
||||
hashes: Hashes {
|
||||
|
|
@ -1094,7 +1094,7 @@ mod tests {
|
|||
},
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
PypiFile {
|
||||
core_metadata: None,
|
||||
filename: "Flask-0.1.tar.gz",
|
||||
hashes: Hashes {
|
||||
|
|
@ -1112,7 +1112,7 @@ mod tests {
|
|||
url: "0.1/Flask-0.1.tar.gz",
|
||||
yanked: None,
|
||||
},
|
||||
File {
|
||||
PypiFile {
|
||||
core_metadata: None,
|
||||
filename: "Flask-0.10.1.tar.gz",
|
||||
hashes: Hashes {
|
||||
|
|
@ -1130,7 +1130,7 @@ mod tests {
|
|||
url: "0.10.1/Flask-0.10.1.tar.gz",
|
||||
yanked: None,
|
||||
},
|
||||
File {
|
||||
PypiFile {
|
||||
core_metadata: None,
|
||||
filename: "flask-3.0.1.tar.gz",
|
||||
hashes: Hashes {
|
||||
|
|
@ -1197,7 +1197,7 @@ mod tests {
|
|||
},
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
PypiFile {
|
||||
core_metadata: None,
|
||||
filename: "Jinja2-3.1.2-py3-none-any.whl",
|
||||
hashes: Hashes {
|
||||
|
|
@ -1270,7 +1270,7 @@ mod tests {
|
|||
},
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
PypiFile {
|
||||
core_metadata: Some(
|
||||
Bool(
|
||||
true,
|
||||
|
|
@ -1290,7 +1290,7 @@ mod tests {
|
|||
url: "/whl/Jinja2-3.1.2-py3-none-any.whl",
|
||||
yanked: None,
|
||||
},
|
||||
File {
|
||||
PypiFile {
|
||||
core_metadata: Some(
|
||||
Bool(
|
||||
true,
|
||||
|
|
@ -1310,7 +1310,7 @@ mod tests {
|
|||
url: "/whl/Jinja2-3.1.3-py3-none-any.whl",
|
||||
yanked: None,
|
||||
},
|
||||
File {
|
||||
PypiFile {
|
||||
core_metadata: Some(
|
||||
Bool(
|
||||
false,
|
||||
|
|
@ -1330,7 +1330,7 @@ mod tests {
|
|||
url: "/whl/Jinja2-3.1.4-py3-none-any.whl",
|
||||
yanked: None,
|
||||
},
|
||||
File {
|
||||
PypiFile {
|
||||
core_metadata: Some(
|
||||
Bool(
|
||||
false,
|
||||
|
|
@ -1350,7 +1350,7 @@ mod tests {
|
|||
url: "/whl/Jinja2-3.1.5-py3-none-any.whl",
|
||||
yanked: None,
|
||||
},
|
||||
File {
|
||||
PypiFile {
|
||||
core_metadata: Some(
|
||||
Bool(
|
||||
true,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ use uv_normalize::PackageName;
|
|||
use uv_pep440::Version;
|
||||
use uv_pep508::MarkerEnvironment;
|
||||
use uv_platform_tags::Platform;
|
||||
use uv_pypi_types::{ResolutionMetadata, SimpleJson};
|
||||
use uv_pypi_types::{PypiSimpleDetail, ResolutionMetadata};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_small_str::SmallString;
|
||||
use uv_torch::TorchStrategy;
|
||||
|
|
@ -630,10 +630,10 @@ impl RegistryClient {
|
|||
.bytes()
|
||||
.await
|
||||
.map_err(|err| ErrorKind::from_reqwest(url.clone(), err))?;
|
||||
let data: SimpleJson = serde_json::from_slice(bytes.as_ref())
|
||||
let data: PypiSimpleDetail = serde_json::from_slice(bytes.as_ref())
|
||||
.map_err(|err| Error::from_json_err(err, url.clone()))?;
|
||||
|
||||
SimpleMetadata::from_files(data.files, package_name, &url)
|
||||
SimpleMetadata::from_pypi_files(data.files, package_name, &url)
|
||||
}
|
||||
MediaType::Html => {
|
||||
let text = response
|
||||
|
|
@ -1136,7 +1136,11 @@ impl SimpleMetadata {
|
|||
self.0.iter()
|
||||
}
|
||||
|
||||
fn from_files(files: Vec<uv_pypi_types::File>, package_name: &PackageName, base: &Url) -> Self {
|
||||
fn from_pypi_files(
|
||||
files: Vec<uv_pypi_types::PypiFile>,
|
||||
package_name: &PackageName,
|
||||
base: &Url,
|
||||
) -> Self {
|
||||
let mut map: BTreeMap<Version, VersionFiles> = BTreeMap::default();
|
||||
|
||||
// Convert to a reference-counted string.
|
||||
|
|
@ -1188,7 +1192,7 @@ impl SimpleMetadata {
|
|||
let SimpleHtml { base, files } =
|
||||
SimpleHtml::parse(text, url).map_err(|err| Error::from_html_err(err, url.clone()))?;
|
||||
|
||||
Ok(Self::from_files(files, package_name, base.as_url()))
|
||||
Ok(Self::from_pypi_files(files, package_name, base.as_url()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1261,7 +1265,7 @@ mod tests {
|
|||
|
||||
use url::Url;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_pypi_types::SimpleJson;
|
||||
use uv_pypi_types::PypiSimpleDetail;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
use crate::{SimpleMetadata, SimpleMetadatum, html::SimpleHtml};
|
||||
|
|
@ -1480,9 +1484,9 @@ mod tests {
|
|||
]
|
||||
}
|
||||
"#;
|
||||
let data: SimpleJson = serde_json::from_str(response).unwrap();
|
||||
let data: PypiSimpleDetail = serde_json::from_str(response).unwrap();
|
||||
let base = DisplaySafeUrl::parse("https://pypi.org/simple/pyflyby/").unwrap();
|
||||
let simple_metadata = SimpleMetadata::from_files(
|
||||
let simple_metadata = SimpleMetadata::from_pypi_files(
|
||||
data.files,
|
||||
&PackageName::from_str("pyflyby").unwrap(),
|
||||
&base,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use uv_pypi_types::{CoreMetadata, HashDigests, Yanked};
|
|||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_small_str::SmallString;
|
||||
|
||||
/// Error converting [`uv_pypi_types::File`] to [`distribution_type::File`].
|
||||
/// Error converting [`uv_pypi_types::PypiFile`] to [`distribution_type::File`].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum FileConversionError {
|
||||
#[error("Failed to parse `requires-python`: `{0}`")]
|
||||
|
|
@ -20,7 +20,7 @@ pub enum FileConversionError {
|
|||
Url(String, #[source] url::ParseError),
|
||||
}
|
||||
|
||||
/// Internal analog to [`uv_pypi_types::File`].
|
||||
/// Internal analog to [`uv_pypi_types::PypiFile`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
|
||||
#[rkyv(derive(Debug))]
|
||||
pub struct File {
|
||||
|
|
@ -41,7 +41,7 @@ pub struct File {
|
|||
impl File {
|
||||
/// `TryFrom` instead of `From` to filter out files with invalid requires python version specifiers
|
||||
pub fn try_from(
|
||||
file: uv_pypi_types::File,
|
||||
file: uv_pypi_types::PypiFile,
|
||||
base: &SmallString,
|
||||
) -> Result<Self, FileConversionError> {
|
||||
Ok(Self {
|
||||
|
|
|
|||
|
|
@ -9,18 +9,19 @@ use uv_small_str::SmallString;
|
|||
|
||||
use crate::lenient_requirement::LenientVersionSpecifiers;
|
||||
|
||||
/// A collection of "files" from `PyPI`'s JSON API for a single package.
|
||||
/// A collection of "files" from `PyPI`'s JSON API for a single package, as served by the
|
||||
/// `vnd.pypi.simple.v1` media type.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct SimpleJson {
|
||||
/// The list of [`File`]s available for download sorted by filename.
|
||||
pub struct PypiSimpleDetail {
|
||||
/// The list of [`PypiFile`]s available for download sorted by filename.
|
||||
#[serde(deserialize_with = "sorted_simple_json_files")]
|
||||
pub files: Vec<File>,
|
||||
pub files: Vec<PypiFile>,
|
||||
}
|
||||
|
||||
/// Deserializes a sequence of "simple" files from `PyPI` and ensures that they
|
||||
/// are sorted in a stable order.
|
||||
fn sorted_simple_json_files<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<File>, D::Error> {
|
||||
let mut files = <Vec<File>>::deserialize(d)?;
|
||||
fn sorted_simple_json_files<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<PypiFile>, D::Error> {
|
||||
let mut files = <Vec<PypiFile>>::deserialize(d)?;
|
||||
// While it has not been positively observed, we sort the files
|
||||
// to ensure we have a defined ordering. Otherwise, if we rely on
|
||||
// the API to provide a stable ordering and doesn't, it can lead
|
||||
|
|
@ -33,11 +34,12 @@ fn sorted_simple_json_files<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<File>
|
|||
Ok(files)
|
||||
}
|
||||
|
||||
/// A single (remote) file belonging to a package, either a wheel or a source distribution.
|
||||
/// A single (remote) file belonging to a package, either a wheel or a source distribution, as
|
||||
/// served by the `vnd.pypi.simple.v1` media type.
|
||||
///
|
||||
/// <https://peps.python.org/pep-0691/#project-detail>
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct File {
|
||||
pub struct PypiFile {
|
||||
pub core_metadata: Option<CoreMetadata>,
|
||||
pub filename: SmallString,
|
||||
pub hashes: Hashes,
|
||||
|
|
@ -48,7 +50,7 @@ pub struct File {
|
|||
pub yanked: Option<Box<Yanked>>,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for File {
|
||||
impl<'de> Deserialize<'de> for PypiFile {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
|
|
@ -56,7 +58,7 @@ impl<'de> Deserialize<'de> for File {
|
|||
struct FileVisitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for FileVisitor {
|
||||
type Value = File;
|
||||
type Value = PypiFile;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("a map containing file metadata")
|
||||
|
|
@ -103,7 +105,7 @@ impl<'de> Deserialize<'de> for File {
|
|||
}
|
||||
}
|
||||
|
||||
Ok(File {
|
||||
Ok(PypiFile {
|
||||
core_metadata,
|
||||
filename: filename
|
||||
.ok_or_else(|| serde::de::Error::missing_field("filename"))?,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue