Accept dependencies from pyproject.toml (#141)

Doesn't support extras yet. It's also supported for `pip uninstall`,
which `pip` itself doesn't support, but whatever.

Closes #127.
This commit is contained in:
Charlie Marsh 2023-10-19 14:42:05 -04:00 committed by GitHub
parent 385345807c
commit ba181eacdd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 52 additions and 20 deletions

2
Cargo.lock generated
View file

@ -2028,9 +2028,11 @@ dependencies = [
"puffin-package",
"puffin-resolver",
"puffin-workspace",
"pyproject-toml",
"tempfile",
"thiserror",
"tokio",
"toml 0.8.2",
"tracing",
"tracing-subscriber",
"tracing-tree",

View file

@ -33,9 +33,11 @@ indicatif = { workspace = true }
itertools = { workspace = true }
miette = { workspace = true, features = ["fancy"] }
owo-colors = { workspace = true }
pyproject-toml = { workspace = true }
tempfile = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }
toml = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
tracing-tree = { workspace = true }

View file

@ -2,7 +2,7 @@ use std::path::PathBuf;
use std::str::FromStr;
use anyhow::{bail, Result};
use itertools::Either;
use fs_err as fs;
use pep508_rs::Requirement;
use puffin_package::requirements_txt::RequirementsTxt;
@ -12,7 +12,9 @@ pub(crate) enum RequirementsSource {
/// A dependency was provided on the command line (e.g., `pip install flask`).
Name(String),
/// Dependencies were provided via a `requirements.txt` file (e.g., `pip install -r requirements.txt`).
Path(PathBuf),
RequirementsTxt(PathBuf),
/// Dependencies were provided via a `pyproject.toml` file (e.g., `pip-compile pyproject.toml`).
PyprojectToml(PathBuf),
}
impl From<String> for RequirementsSource {
@ -23,30 +25,56 @@ impl From<String> for RequirementsSource {
impl From<PathBuf> for RequirementsSource {
fn from(path: PathBuf) -> Self {
Self::Path(path)
if path.ends_with("pyproject.toml") {
Self::PyprojectToml(path)
} else {
Self::RequirementsTxt(path)
}
}
}
impl RequirementsSource {
/// Return an iterator over the requirements in this source.
pub(crate) fn requirements(&self) -> Result<impl Iterator<Item = Requirement>> {
match self {
Self::Name(name) => {
let requirement = Requirement::from_str(name)?;
Ok(Either::Left(std::iter::once(requirement)))
let iter_name = if let Self::Name(name) = self {
let requirement = Requirement::from_str(name)?;
Some(std::iter::once(requirement))
} else {
None
};
let iter_requirements_txt = if let Self::RequirementsTxt(path) = self {
let requirements_txt = RequirementsTxt::parse(path, std::env::current_dir()?)?;
if !requirements_txt.constraints.is_empty() {
bail!("Constraints in requirements files are not supported");
}
Self::Path(path) => {
let requirements_txt = RequirementsTxt::parse(path, std::env::current_dir()?)?;
if !requirements_txt.constraints.is_empty() {
bail!("Constraints in requirements files are not supported");
}
Ok(Either::Right(
requirements_txt
.requirements
.into_iter()
.map(|entry| entry.requirement),
))
}
}
Some(
requirements_txt
.requirements
.into_iter()
.map(|entry| entry.requirement),
)
} else {
None
};
let iter_pyproject_toml = if let Self::PyprojectToml(path) = self {
let pyproject_toml =
toml::from_str::<pyproject_toml::PyProjectToml>(&fs::read_to_string(path)?)?;
Some(
pyproject_toml
.project
.into_iter()
.flat_map(|project| project.dependencies.into_iter().flatten()),
)
} else {
None
};
Ok(iter_name
.into_iter()
.flatten()
.chain(iter_requirements_txt.into_iter().flatten())
.chain(iter_pyproject_toml.into_iter().flatten()))
}
}