Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow uv pip install to fetch requirement file from remote URL #1

Closed
wants to merge 21 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
init
  • Loading branch information
ottaviohartman committed Feb 17, 2024
commit 8e902e937fe3a77f127b8722fa41da163aba3323
53 changes: 45 additions & 8 deletions crates/uv/src/requirements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use distribution_types::{FlatIndexLocation, IndexUrl};
use pep508_rs::Requirement;
use requirements_txt::{EditableRequirement, FindLink, RequirementsTxt};
use tracing::{instrument, Level};
use url::Url;
use uv_fs::Normalized;
use uv_normalize::{ExtraName, PackageName};

Expand All @@ -22,18 +23,26 @@ pub(crate) enum RequirementsSource {
/// An editable path was provided on the command line (e.g., `pip install -e ../flask`).
Editable(String),
/// Dependencies were provided via a `requirements.txt` file (e.g., `pip install -r requirements.txt`).
RequirementsTxt(PathBuf),
RequirementsTxt(RequirementsTxtSource),
/// Dependencies were provided via a `pyproject.toml` file (e.g., `pip-compile pyproject.toml`).
PyprojectToml(PathBuf),
}

#[derive(Debug)]
pub(crate) enum RequirementsTxtSource {
/// A `requirements.txt` file was provided on the command line (e.g., `pip install -r requirements.txt`).
File(PathBuf),
/// A `requirements.txt` file was provided via a URL (e.g., `pip install -r https://example.com/requirements.txt`).
Url(Url),
}

impl RequirementsSource {
/// Parse a [`RequirementsSource`] from a [`PathBuf`].
pub(crate) fn from_path(path: PathBuf) -> Self {
if path.ends_with("pyproject.toml") {
Self::PyprojectToml(path)
} else {
Self::RequirementsTxt(path)
Self::RequirementsTxt(RequirementsTxtSource::File(path))
}
}

Expand All @@ -54,12 +63,28 @@ impl RequirementsSource {
);
let confirmation = confirm::confirm(&prompt, &term, true).unwrap();
if confirmation {
return Self::RequirementsTxt(name.into());
return Self::RequirementsTxt(RequirementsTxtSource::File(name.into()));
}
}
}
}

// If the user provided a URL without `-r` (as in
// `uv pip install https://example.com/requirements.txt`), prompt them to correct it.
if name.starts_with("http://") || name.starts_with("https://") {
let term = Term::stderr();
if term.is_term() {
let prompt = format!(
"`{name}` looks like a URL but was passed as a package name. Did you mean `-r {name}`?"
);
let confirmation = confirm::confirm(&prompt, &term, true).unwrap();
let url = Url::parse(&name).unwrap();
if confirmation {
return Self::RequirementsTxt(RequirementsTxtSource::Url(url));
}
}
}

// If the user provided a path to a local directory without `-e` (as in
// `uv pip install ../flask`), prompt them to correct it.
if name.contains('/') || name.contains('\\') {
Expand All @@ -70,7 +95,7 @@ impl RequirementsSource {
format!("`{name}` looks like a local directory but was passed as a package name. Did you mean `-e {name}`?");
let confirmation = confirm::confirm(&prompt, &term, true).unwrap();
if confirmation {
return Self::RequirementsTxt(name.into());
return Self::RequirementsTxt(RequirementsTxtSource::File(name.into()));
}
}
}
Expand All @@ -84,10 +109,12 @@ impl std::fmt::Display for RequirementsSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Editable(path) => write!(f, "-e {path}"),
Self::RequirementsTxt(path) | Self::PyprojectToml(path) => {
write!(f, "{}", path.display())
}
Self::Package(package) => write!(f, "{package}"),
Self::RequirementsTxt(source) => match source {
RequirementsTxtSource::File(path) => write!(f, "{}", path.display()),
RequirementsTxtSource::Url(url) => write!(f, "{}", url),
},
Self::PyprojectToml(path) => write!(f, "{}", path.display()),
}
}
}
Expand Down Expand Up @@ -175,7 +202,17 @@ impl RequirementsSpecification {
find_links: vec![],
}
}
RequirementsSource::RequirementsTxt(path) => {
RequirementsSource::RequirementsTxt(requirements_txt_source) => {
// TODO: support URL
let path = match requirements_txt_source {
RequirementsTxtSource::File(path) => path,
RequirementsTxtSource::Url(url) => {
return Err(anyhow::anyhow!(
"URL-based requirements files are not yet supported: {}",
url
))
}
};
let requirements_txt = RequirementsTxt::parse(path, std::env::current_dir()?)?;
Self {
project: None,
Expand Down