1use std::collections::HashMap;
4
5use anyhow::{Result, anyhow};
6use log::debug;
7use opendal::Operator;
8use serde::Deserialize;
9
10use crate::{fetch_tarball, find_alt_fs, http_client};
11
12pub async fn load(package: &str, version: &str) -> Result<Operator> {
13 let hints = collect_alt_hints(package).await?;
14 for hint in hints {
15 if let Some(fs) = find_alt_fs(&hint).await? {
16 return Ok(fs);
17 }
18 }
19
20 let prefix = package
21 .chars()
22 .next()
23 .ok_or_else(|| anyhow!("empty package name"))?;
24 let url = format!(
25 "https://pypi.io/packages/source/{prefix}/{package}/{package}-{version}.tar.gz"
26 );
27 fetch_tarball(url).await
28}
29
30async fn collect_alt_hints(package: &str) -> Result<Vec<String>> {
31 #[derive(Debug, Deserialize)]
32 struct PypiProjectJson {
33 #[serde(default)]
34 info: PypiProjectInfo,
35 }
36 #[derive(Debug, Deserialize, Default)]
37 struct PypiProjectInfo {
38 #[serde(default)]
39 project_urls: HashMap<String, String>,
40 }
41
42 debug!("Fetching PYPI project information: {package}");
43 let client = http_client()?;
44 let url = format!("https://pypi.org/pypi/{package}/json");
45 let proj_json = client
46 .execute(client.get(&url).build()?)
47 .await?
48 .error_for_status()?
49 .json::<PypiProjectJson>()
50 .await?;
51
52 let mut hints = Vec::new();
53 for (k, v) in proj_json.info.project_urls {
54 debug!(
55 "Found alt hint from PYPI metadata of {package}: {k} -> {v}"
56 );
57 hints.push(v);
58 }
59
60 Ok(hints)
61}