Add an HTTP cache (and --no-cache argument) (#14)

Closes https://github.com/astral-sh/puffin/issues/3.
This commit is contained in:
Charlie Marsh 2023-10-05 19:14:05 -04:00 committed by GitHub
parent 1063d8c150
commit 2d6266b167
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 31 additions and 23 deletions

View file

@ -10,5 +10,3 @@ documentation = "https://astral.sh"
repository = "https://github.com/astral-sh/puffin" repository = "https://github.com/astral-sh/puffin"
authors = ["Astral Software Inc. <hey@astral.sh>"] authors = ["Astral Software Inc. <hey@astral.sh>"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
[workspace.dependencies]

View file

@ -23,3 +23,4 @@ pep440_rs = { version = "0.3.12" }
tracing = { version = "0.1.37" } tracing = { version = "0.1.37" }
tracing-tree = { version = "0.2.5" } tracing-tree = { version = "0.2.5" }
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
directories = "5.0.1"

View file

@ -29,7 +29,7 @@ enum Response {
Version(Metadata21, Requirement), Version(Metadata21, Requirement),
} }
pub(crate) async fn install(src: &Path) -> Result<ExitStatus> { pub(crate) async fn install(src: &Path, cache: Option<&Path>) -> Result<ExitStatus> {
// Read the `requirements.txt` from disk. // Read the `requirements.txt` from disk.
let requirements_txt = std::fs::read_to_string(src)?; let requirements_txt = std::fs::read_to_string(src)?;
@ -44,8 +44,13 @@ pub(crate) async fn install(src: &Path) -> Result<ExitStatus> {
); );
// Instantiate a client. // Instantiate a client.
let pypi_client = PypiClientBuilder::default().build(); let pypi_client = {
let proxy_client = PypiClientBuilder::default().build(); let mut pypi_client = PypiClientBuilder::default();
if let Some(cache) = cache {
pypi_client = pypi_client.cache(cache);
}
pypi_client.build()
};
// A channel to fetch package metadata (e.g., given `flask`, fetch all versions) and version // A channel to fetch package metadata (e.g., given `flask`, fetch all versions) and version
// metadata (e.g., given `flask==1.0.0`, fetch the metadata for that version). // metadata (e.g., given `flask==1.0.0`, fetch the metadata for that version).
@ -60,7 +65,7 @@ pub(crate) async fn install(src: &Path) -> Result<ExitStatus> {
.map_ok(move |metadata| Response::Package(metadata, requirement)), .map_ok(move |metadata| Response::Package(metadata, requirement)),
), ),
Request::Version(requirement, file) => Either::Right( Request::Version(requirement, file) => Either::Right(
proxy_client pypi_client
.file(file) .file(file)
.map_ok(move |metadata| Response::Version(metadata, requirement)), .map_ok(move |metadata| Response::Version(metadata, requirement)),
), ),

View file

@ -3,6 +3,7 @@ use std::process::ExitCode;
use clap::{Args, Parser, Subcommand}; use clap::{Args, Parser, Subcommand};
use colored::Colorize; use colored::Colorize;
use directories::ProjectDirs;
use crate::commands::ExitStatus; use crate::commands::ExitStatus;
@ -27,6 +28,10 @@ enum Commands {
struct InstallArgs { struct InstallArgs {
/// Path to the `requirements.text` file to install. /// Path to the `requirements.text` file to install.
src: PathBuf, src: PathBuf,
/// Avoid reading from or writing to the cache.
#[arg(long)]
no_cache: bool,
} }
#[async_std::main] #[async_std::main]
@ -35,8 +40,18 @@ async fn main() -> ExitCode {
let _ = logging::setup_logging(); let _ = logging::setup_logging();
let dirs = ProjectDirs::from("", "", "puffin");
let result = match &cli.command { let result = match &cli.command {
Commands::Install(install) => commands::install(&install.src).await, Commands::Install(install) => {
commands::install(
&install.src,
dirs.as_ref()
.map(directories::ProjectDirs::cache_dir)
.filter(|_| !install.no_cache),
)
.await
}
}; };
match result { match result {

View file

@ -16,8 +16,6 @@ impl PypiClient {
&self, &self,
package_name: impl AsRef<str>, package_name: impl AsRef<str>,
) -> Result<SimpleJson, PypiClientError> { ) -> Result<SimpleJson, PypiClientError> {
let start = std::time::Instant::now();
// Format the URL for PyPI. // Format the URL for PyPI.
let mut url = self.registry.join("simple")?; let mut url = self.registry.join("simple")?;
url.path_segments_mut() url.path_segments_mut()
@ -34,12 +32,8 @@ impl PypiClient {
// Fetch from the registry. // Fetch from the registry.
let text = self.simple_impl(&package_name, &url).await?; let text = self.simple_impl(&package_name, &url).await?;
let payload = serde_json::from_str(&text) serde_json::from_str(&text)
.map_err(move |e| PypiClientError::from_json_err(e, String::new())); .map_err(move |e| PypiClientError::from_json_err(e, String::new()))
trace!("fetched metadata for {} in {:?}", url, start.elapsed());
payload
} }
async fn simple_impl( async fn simple_impl(
@ -69,8 +63,6 @@ impl PypiClient {
} }
pub async fn file(&self, file: File) -> Result<Metadata21, PypiClientError> { pub async fn file(&self, file: File) -> Result<Metadata21, PypiClientError> {
let start = std::time::Instant::now();
// Send to the proxy. // Send to the proxy.
let url = self.proxy.join( let url = self.proxy.join(
file.url file.url
@ -82,11 +74,7 @@ impl PypiClient {
// Fetch from the registry. // Fetch from the registry.
let text = self.file_impl(&file.filename, &url).await?; let text = self.file_impl(&file.filename, &url).await?;
let payload = Metadata21::parse(text.as_bytes()).map_err(std::convert::Into::into); Metadata21::parse(text.as_bytes()).map_err(std::convert::Into::into)
trace!("fetched file {} in {:?}", url, start.elapsed());
payload
} }
async fn file_impl( async fn file_impl(

View file

@ -16,7 +16,8 @@ serde = { version = "1.0.188" }
thiserror = { version = "1.0.49" } thiserror = { version = "1.0.49" }
[dev-dependencies] [dev-dependencies]
criterion = "0.5.1" criterion = { version = "0.5.1" }
insta = { version = "1.33.0" }
[[bench]] [[bench]]
name = "parser" name = "parser"