Report project name instead of root when using pyproject.toml files (#295)

Part of https://github.com/astral-sh/puffin/issues/214

Adds a `project: Option<PackageName>` to the `Manifest`, `Resolver`, and
`RequirementsSpecification`.
To populate an optional `name` for `PubGubPackage::Root`.

I'll work on removing the version number next.

Should we consider using the parent directory name when a
`pyproject.toml` file is not present?
This commit is contained in:
Zanie Blue 2023-11-03 10:22:10 -05:00 committed by GitHub
parent e008c43f29
commit e1382cc747
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 123 additions and 9 deletions

View file

@ -44,6 +44,7 @@ pub(crate) async fn pip_compile(
// Read all requirements from the provided sources.
let RequirementsSpecification {
project,
requirements,
constraints,
extras: used_extras,
@ -84,6 +85,7 @@ pub(crate) async fn pip_compile(
preferences,
resolution_mode,
prerelease_mode,
project,
);
// Detect the current Python interpreter.

View file

@ -33,6 +33,7 @@ pub(crate) async fn pip_sync(
) -> Result<ExitStatus> {
// Read all requirements from the provided sources.
let RequirementsSpecification {
project: _,
requirements,
constraints: _,
extras: _,

View file

@ -22,6 +22,7 @@ pub(crate) async fn pip_uninstall(
// Read all requirements from the provided sources.
let RequirementsSpecification {
project: _,
requirements,
constraints: _,
extras: _,

View file

@ -8,7 +8,7 @@ use anyhow::{Context, Result};
use fs_err as fs;
use pep508_rs::Requirement;
use puffin_normalize::ExtraName;
use puffin_normalize::{ExtraName, PackageName};
use puffin_package::requirements_txt::RequirementsTxt;
#[derive(Debug)]
@ -58,6 +58,8 @@ impl ExtrasSpecification<'_> {
#[derive(Debug, Default)]
pub(crate) struct RequirementsSpecification {
/// The name of the project specifying requirements.
pub(crate) project: Option<PackageName>,
/// The requirements for the project.
pub(crate) requirements: Vec<Requirement>,
/// The constraints for the project.
@ -77,6 +79,7 @@ impl RequirementsSpecification {
let requirement = Requirement::from_str(name)
.with_context(|| format!("Failed to parse `{name}`"))?;
Self {
project: None,
requirements: vec![requirement],
constraints: vec![],
extras: HashSet::new(),
@ -85,6 +88,7 @@ impl RequirementsSpecification {
RequirementsSource::RequirementsTxt(path) => {
let requirements_txt = RequirementsTxt::parse(path, std::env::current_dir()?)?;
Self {
project: None,
requirements: requirements_txt
.requirements
.into_iter()
@ -100,6 +104,7 @@ impl RequirementsSpecification {
.with_context(|| format!("Failed to read `{}`", path.display()))?;
let mut used_extras = HashSet::new();
let mut requirements = Vec::new();
let mut project_name = None;
if let Some(project) = pyproject_toml.project {
requirements.extend(project.dependencies.unwrap_or_default());
// Include any optional dependencies specified in `extras`
@ -114,9 +119,12 @@ impl RequirementsSpecification {
}
}
}
// Parse the project name
project_name = Some(PackageName::new(project.name));
}
Self {
project: project_name,
requirements,
constraints: vec![],
extras: used_extras,
@ -141,6 +149,11 @@ impl RequirementsSpecification {
spec.requirements.extend(source.requirements);
spec.constraints.extend(source.constraints);
spec.extras.extend(source.extras);
// Use the first project name discovered
if spec.project.is_none() {
spec.project = source.project;
}
}
// Read all constraints, treating both requirements _and_ constraints as constraints.

View file

@ -1047,3 +1047,51 @@ optional-dependencies.bar = [
Ok(())
}
/// Compile requirements that cannot be solved due to conflict in a `pyproject.toml` fil;e.
#[test]
fn compile_unsolvable_requirements() -> Result<()> {
let temp_dir = assert_fs::TempDir::new()?;
let cache_dir = assert_fs::TempDir::new()?;
let venv = temp_dir.child(".venv");
Command::new(get_cargo_bin(BIN_NAME))
.arg("venv")
.arg(venv.as_os_str())
.arg("--cache-dir")
.arg(cache_dir.path())
.current_dir(&temp_dir)
.assert()
.success();
venv.assert(predicates::path::is_dir());
let pyproject_toml = temp_dir.child("pyproject.toml");
pyproject_toml.touch()?;
pyproject_toml.write_str(
r#"[build-system]
requires = ["setuptools", "wheel"]
[project]
name = "my-project"
dependencies = ["django==5.0b1", "django==5.0a1"]
"#,
)?;
insta::with_settings!({
filters => vec![
(r"\d(ms|s)", "[TIME]"),
(r"# .* pip-compile", "# [BIN_PATH] pip-compile"),
(r"--cache-dir .*", "--cache-dir [CACHE_DIR]"),
]
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.arg("pip-compile")
.arg("pyproject.toml")
.arg("--cache-dir")
.arg(cache_dir.path())
.env("VIRTUAL_ENV", venv.as_os_str())
.current_dir(&temp_dir));
});
Ok(())
}

View file

@ -0,0 +1,20 @@
---
source: crates/puffin-cli/tests/pip_compile.rs
info:
program: puffin
args:
- pip-compile
- pyproject.toml
- "--cache-dir"
- /var/folders/bc/qlsk3t6x7c9fhhbvvcg68k9c0000gp/T/.tmpfuaPhl
env:
VIRTUAL_ENV: /var/folders/bc/qlsk3t6x7c9fhhbvvcg68k9c0000gp/T/.tmpLxUB5I/.venv
---
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ my-project 0a0.dev0 depends on django ∅