libpfu_source/
pypi.rs

1//! pypi handler.
2
3use 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}