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-package",
"puffin-resolver", "puffin-resolver",
"puffin-workspace", "puffin-workspace",
"pyproject-toml",
"tempfile", "tempfile",
"thiserror", "thiserror",
"tokio", "tokio",
"toml 0.8.2",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
"tracing-tree", "tracing-tree",

View file

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

View file

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