Support constraints in requirements.in files (#212)

Closes #172.
This commit is contained in:
Charlie Marsh 2023-10-26 17:41:02 -07:00 committed by GitHub
parent 58011f98b6
commit 8b83385763
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 476 additions and 89 deletions

View file

@ -16,12 +16,12 @@ use platform_tags::Tags;
use puffin_client::RegistryClientBuilder;
use puffin_dispatch::BuildDispatch;
use puffin_interpreter::Virtualenv;
use puffin_resolver::{Manifest, ResolutionMode};
use puffin_resolver::{ResolutionManifest, ResolutionMode};
use crate::commands::{elapsed, ExitStatus};
use crate::index_urls::IndexUrls;
use crate::printer::Printer;
use crate::requirements::RequirementsSource;
use crate::requirements::{RequirementsSource, RequirementsSpecification};
const VERSION: &str = env!("CARGO_PKG_VERSION");
@ -40,29 +40,23 @@ pub(crate) async fn pip_compile(
let start = std::time::Instant::now();
// Read all requirements from the provided sources.
let requirements = requirements
.iter()
.map(RequirementsSource::requirements)
.flatten_ok()
.collect::<Result<Vec<Requirement>>>()?;
let constraints = constraints
.iter()
.map(RequirementsSource::requirements)
.flatten_ok()
.collect::<Result<Vec<Requirement>>>()?;
let RequirementsSpecification {
requirements,
constraints,
} = RequirementsSpecification::try_from_sources(requirements, constraints)?;
let preferences: Vec<Requirement> = output_file
.filter(|_| upgrade_mode.is_prefer_pinned())
.filter(|output_file| output_file.exists())
.map(Path::to_path_buf)
.map(RequirementsSource::from)
.as_ref()
.map(RequirementsSource::requirements)
.map(RequirementsSpecification::try_from_source)
.transpose()?
.map(Iterator::collect)
.map(|spec| spec.requirements)
.unwrap_or_default();
// Create a manifest of the requirements.
let manifest = Manifest::new(requirements, constraints, preferences, resolution_mode);
let manifest = ResolutionManifest::new(requirements, constraints, preferences, resolution_mode);
// Detect the current Python interpreter.
let platform = Platform::current()?;

View file

@ -20,7 +20,7 @@ use crate::commands::reporters::{
use crate::commands::{elapsed, ExitStatus};
use crate::index_urls::IndexUrls;
use crate::printer::Printer;
use crate::requirements::RequirementsSource;
use crate::requirements::{RequirementsSource, RequirementsSpecification};
/// Install a set of locked requirements into the current Python environment.
pub(crate) async fn pip_sync(
@ -31,11 +31,10 @@ pub(crate) async fn pip_sync(
mut printer: Printer,
) -> Result<ExitStatus> {
// Read all requirements from the provided sources.
let requirements = sources
.iter()
.map(RequirementsSource::requirements)
.flatten_ok()
.collect::<Result<Vec<Requirement>>>()?;
let RequirementsSpecification {
requirements,
constraints: _,
} = RequirementsSpecification::try_from_sources(sources, &[])?;
if requirements.is_empty() {
writeln!(printer, "No requirements found")?;

View file

@ -3,17 +3,15 @@ use std::path::Path;
use anyhow::Result;
use colored::Colorize;
use itertools::Itertools;
use tracing::debug;
use pep508_rs::Requirement;
use platform_host::Platform;
use puffin_interpreter::Virtualenv;
use puffin_package::package_name::PackageName;
use crate::commands::{elapsed, ExitStatus};
use crate::printer::Printer;
use crate::requirements::RequirementsSource;
use crate::requirements::{RequirementsSource, RequirementsSpecification};
/// Uninstall packages from the current environment.
pub(crate) async fn pip_uninstall(
@ -24,11 +22,10 @@ pub(crate) async fn pip_uninstall(
let start = std::time::Instant::now();
// Read all requirements from the provided sources.
let requirements = sources
.iter()
.map(RequirementsSource::requirements)
.flatten_ok()
.collect::<Result<Vec<Requirement>>>()?;
let RequirementsSpecification {
requirements,
constraints: _,
} = RequirementsSpecification::try_from_sources(sources, &[])?;
// Detect the current Python interpreter.
let platform = Platform::current()?;

View file

@ -3,7 +3,7 @@
use std::path::PathBuf;
use std::str::FromStr;
use anyhow::{bail, Context, Result};
use anyhow::{Context, Result};
use fs_err as fs;
use pep508_rs::Requirement;
@ -35,50 +35,77 @@ impl From<PathBuf> for RequirementsSource {
}
}
impl RequirementsSource {
/// Return an iterator over the requirements in this source.
pub(crate) fn requirements(&self) -> Result<impl Iterator<Item = Requirement>> {
let iter_name = if let Self::Name(name) = self {
let requirement =
Requirement::from_str(name).with_context(|| format!("Failed to parse `{name}`"))?;
Some(std::iter::once(requirement))
} else {
None
};
#[derive(Debug, Default)]
pub(crate) struct RequirementsSpecification {
/// The requirements for the project.
pub(crate) requirements: Vec<Requirement>,
/// The constraints for the project.
pub(crate) constraints: Vec<Requirement>,
}
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");
impl RequirementsSpecification {
/// Read the requirements and constraints from a source.
pub(crate) fn try_from_source(source: &RequirementsSource) -> Result<Self> {
Ok(match source {
RequirementsSource::Name(name) => {
let requirement = Requirement::from_str(name)
.with_context(|| format!("Failed to parse `{name}`"))?;
Self {
requirements: vec![requirement],
constraints: vec![],
}
}
Some(
requirements_txt
.requirements
.into_iter()
.map(|entry| entry.requirement),
)
} else {
None
};
let iter_pyproject_toml = if let Self::PyprojectToml(path) = self {
let contents = fs::read_to_string(path)?;
let pyproject_toml = toml::from_str::<pyproject_toml::PyProjectToml>(&contents)
.with_context(|| format!("Failed to read `{}`", path.display()))?;
Some(
pyproject_toml
RequirementsSource::RequirementsTxt(path) => {
let requirements_txt = RequirementsTxt::parse(path, std::env::current_dir()?)?;
Self {
requirements: requirements_txt
.requirements
.into_iter()
.map(|entry| entry.requirement)
.collect(),
constraints: requirements_txt.constraints.into_iter().collect(),
}
}
RequirementsSource::PyprojectToml(path) => {
let contents = fs::read_to_string(path)?;
let pyproject_toml = toml::from_str::<pyproject_toml::PyProjectToml>(&contents)
.with_context(|| format!("Failed to read `{}`", path.display()))?;
let requirements = pyproject_toml
.project
.into_iter()
.flat_map(|project| project.dependencies.into_iter().flatten()),
)
} else {
None
};
.flat_map(|project| project.dependencies.into_iter().flatten())
.collect();
Self {
requirements,
constraints: vec![],
}
}
})
}
Ok(iter_name
.into_iter()
.flatten()
.chain(iter_requirements_txt.into_iter().flatten())
.chain(iter_pyproject_toml.into_iter().flatten()))
/// Read the combined requirements and constraints from a set of sources.
pub(crate) fn try_from_sources(
requirements: &[RequirementsSource],
constraints: &[RequirementsSource],
) -> Result<Self> {
let mut spec = Self::default();
// Read all requirements, and keep track of all requirements _and_ constraints.
// A `requirements.txt` can contain a `-c constraints.txt` directive within it, so reading
// a requirements file can also add constraints.
for source in requirements {
let source = Self::try_from_source(source)?;
spec.requirements.extend(source.requirements);
spec.constraints.extend(source.constraints);
}
// Read all constraints, treating both requirements _and_ constraints as constraints.
for source in constraints {
let source = Self::try_from_source(source)?;
spec.constraints.extend(source.requirements);
spec.constraints.extend(source.constraints);
}
Ok(spec)
}
}

View file

@ -0,0 +1,229 @@
#![cfg(all(feature = "python", feature = "pypi"))]
use std::process::Command;
use anyhow::Result;
use assert_cmd::prelude::*;
use assert_fs::prelude::*;
use insta_cmd::_macro_support::insta;
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
const BIN_NAME: &str = "puffin";
#[test]
fn missing_requirements_in() -> Result<()> {
let temp_dir = assert_fs::TempDir::new()?;
let cache_dir = assert_fs::TempDir::new()?;
let requirements_in = temp_dir.child("requirements.in");
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.arg("pip-compile")
.arg("requirements.in")
.arg("--cache-dir")
.arg(cache_dir.path())
.current_dir(&temp_dir));
requirements_in.assert(predicates::path::missing());
Ok(())
}
#[test]
fn missing_venv() -> Result<()> {
let temp_dir = assert_fs::TempDir::new()?;
let cache_dir = assert_fs::TempDir::new()?;
let venv = temp_dir.child(".venv");
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.arg("pip-compile")
.arg("requirements.in")
.arg("--cache-dir")
.arg(cache_dir.path())
.env("VIRTUAL_ENV", venv.as_os_str())
.current_dir(&temp_dir));
venv.assert(predicates::path::missing());
Ok(())
}
/// Resolve a specific version of Django from a `requirements.in` file.
#[test]
fn compile_requirements_in() -> 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 requirements_in = temp_dir.child("requirements.in");
requirements_in.touch()?;
requirements_in.write_str("django==5.0b1")?;
insta::with_settings!({
filters => vec![
(r"\d+ms", "[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("requirements.in")
.arg("--cache-dir")
.arg(cache_dir.path())
.env("VIRTUAL_ENV", venv.as_os_str())
.current_dir(&temp_dir));
});
Ok(())
}
/// Resolve a specific version of Django from a `pyproject.toml` file.
#[test]
fn compile_pyproject_toml() -> 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 = "project"
dependencies = [
"django==5.0b1",
]
"#,
)?;
insta::with_settings!({
filters => vec![
(r"\d+ms", "[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(())
}
/// Resolve a package from a `requirements.in` file, with a `constraints.txt` file.
#[test]
fn compile_constraints_txt() -> 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 requirements_in = temp_dir.child("requirements.in");
requirements_in.touch()?;
requirements_in.write_str("django==5.0b1")?;
let constraints_txt = temp_dir.child("constraints.txt");
constraints_txt.touch()?;
constraints_txt.write_str("sqlparse<0.4.4")?;
insta::with_settings!({
filters => vec![
(r"\d+ms", "[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("requirements.in")
.arg("--constraint")
.arg("constraints.txt")
.arg("--cache-dir")
.arg(cache_dir.path())
.env("VIRTUAL_ENV", venv.as_os_str())
.current_dir(&temp_dir));
});
Ok(())
}
/// Resolve a package from a `requirements.in` file, with an inline constraint.
#[test]
fn compile_constraints_inline() -> 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 requirements_in = temp_dir.child("requirements.in");
requirements_in.touch()?;
requirements_in.write_str("django==5.0b1")?;
requirements_in.write_str("-c constraints.txt")?;
let constraints_txt = temp_dir.child("constraints.txt");
constraints_txt.touch()?;
constraints_txt.write_str("sqlparse<0.4.4")?;
insta::with_settings!({
filters => vec![
(r"\d+ms", "[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("requirements.in")
.arg("--cache-dir")
.arg(cache_dir.path())
.env("VIRTUAL_ENV", venv.as_os_str())
.current_dir(&temp_dir));
});
Ok(())
}

View file

@ -0,0 +1,21 @@
---
source: crates/puffin-cli/tests/pip_compile.rs
info:
program: puffin
args:
- pip-compile
- requirements.in
- "--cache-dir"
- /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmp5R6H5M
env:
VIRTUAL_ENV: /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpIdL1TH/.venv
---
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by Puffin v0.0.1 via the following command:
# [BIN_PATH] pip-compile requirements.in --cache-dir [CACHE_DIR]
----- stderr -----
Resolved 0 packages in [TIME]

View file

@ -0,0 +1,28 @@
---
source: crates/puffin-cli/tests/pip_compile.rs
info:
program: puffin
args:
- pip-compile
- requirements.in
- "--constraint"
- constraints.txt
- "--cache-dir"
- /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpvjdHOb
env:
VIRTUAL_ENV: /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpSAaWi3/.venv
---
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by Puffin v0.0.1 via the following command:
# [BIN_PATH] pip-compile requirements.in --constraint constraints.txt --cache-dir [CACHE_DIR]
asgiref==3.7.2
# via django
django==5.0b1
sqlparse==0.4.3
# via django
----- stderr -----
Resolved 3 packages in [TIME]

View file

@ -0,0 +1,26 @@
---
source: crates/puffin-cli/tests/pip_compile.rs
info:
program: puffin
args:
- pip-compile
- pyproject.toml
- "--cache-dir"
- /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpOOTFwj
env:
VIRTUAL_ENV: /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpU0VXyY/.venv
---
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by Puffin v0.0.1 via the following command:
# [BIN_PATH] pip-compile pyproject.toml --cache-dir [CACHE_DIR]
asgiref==3.7.2
# via django
django==5.0b1
sqlparse==0.4.4
# via django
----- stderr -----
Resolved 3 packages in [TIME]

View file

@ -0,0 +1,26 @@
---
source: crates/puffin-cli/tests/pip_compile.rs
info:
program: puffin
args:
- pip-compile
- requirements.in
- "--cache-dir"
- /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpxF1zFY
env:
VIRTUAL_ENV: /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmp33QZdv/.venv
---
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by Puffin v0.0.1 via the following command:
# [BIN_PATH] pip-compile requirements.in --cache-dir [CACHE_DIR]
asgiref==3.7.2
# via django
django==5.0b1
sqlparse==0.4.4
# via django
----- stderr -----
Resolved 3 packages in [TIME]

View file

@ -0,0 +1,18 @@
---
source: crates/puffin-cli/tests/pip_compile.rs
info:
program: puffin
args:
- pip-compile
- requirements.in
- "--cache-dir"
- /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpQVD5K4
---
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: failed to open file `requirements.in`
Caused by: No such file or directory (os error 2)

View file

@ -0,0 +1,20 @@
---
source: crates/puffin-cli/tests/pip_compile.rs
info:
program: puffin
args:
- pip-compile
- requirements.in
- "--cache-dir"
- /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmptw6jFz
env:
VIRTUAL_ENV: /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpDn1YGM/.venv
---
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: failed to open file `requirements.in`
Caused by: No such file or directory (os error 2)

View file

@ -19,7 +19,7 @@ use puffin_installer::{
Downloader, Installer, PartitionedRequirements, RemoteDistribution, Unzipper,
};
use puffin_interpreter::{InterpreterInfo, Virtualenv};
use puffin_resolver::{Manifest, ResolutionMode, Resolver, WheelFinder};
use puffin_resolver::{ResolutionManifest, ResolutionMode, Resolver, WheelFinder};
use puffin_traits::BuildContext;
/// The main implementation of [`BuildContext`], used by the CLI, see [`BuildContext`]
@ -71,7 +71,7 @@ impl BuildContext for BuildDispatch {
self.interpreter_info.simple_version(),
)?;
let resolver = Resolver::new(
Manifest::new(
ResolutionManifest::new(
requirements.to_vec(),
Vec::default(),
Vec::default(),

View file

@ -168,7 +168,9 @@ impl RequirementsTxt {
end,
}
})?;
// Here we add both to constraints
// Treat any nested requirements or constraints as constraints. This differs
// from `pip`, which seems to treat `-r` requirements in constraints files as
// _requirements_, but we don't want to support that.
data.constraints.extend(
sub_constraints
.requirements

View file

@ -1,6 +1,6 @@
pub use error::ResolveError;
pub use resolution::{Graph, PinnedPackage};
pub use resolver::{Manifest, Resolver};
pub use resolver::{ResolutionManifest, Resolver};
pub use selector::ResolutionMode;
pub use source_distribution::BuiltSourceDistributionCache;
pub use wheel_finder::{Reporter, WheelFinder};

View file

@ -41,14 +41,14 @@ use crate::BuiltSourceDistributionCache;
/// A manifest of requirements, constraints, and preferences.
#[derive(Debug)]
pub struct Manifest {
pub struct ResolutionManifest {
requirements: Vec<Requirement>,
constraints: Vec<Requirement>,
preferences: Vec<Requirement>,
mode: ResolutionMode,
}
impl Manifest {
impl ResolutionManifest {
pub fn new(
requirements: Vec<Requirement>,
constraints: Vec<Requirement>,
@ -78,7 +78,7 @@ pub struct Resolver<'a, Context: BuildContext + Sync> {
impl<'a, Context: BuildContext + Sync> Resolver<'a, Context> {
/// Initialize a new resolver.
pub fn new(
manifest: Manifest,
manifest: ResolutionManifest,
markers: &'a MarkerEnvironment,
tags: &'a Tags,
client: &'a RegistryClient,

View file

@ -16,7 +16,7 @@ use platform_host::{Arch, Os, Platform};
use platform_tags::Tags;
use puffin_client::RegistryClientBuilder;
use puffin_interpreter::{InterpreterInfo, Virtualenv};
use puffin_resolver::{Manifest, ResolutionMode, Resolver};
use puffin_resolver::{ResolutionManifest, ResolutionMode, Resolver};
use puffin_traits::BuildContext;
struct DummyContext;
@ -64,7 +64,7 @@ async fn pylint() -> Result<()> {
let client = RegistryClientBuilder::default().build();
let manifest = Manifest::new(
let manifest = ResolutionManifest::new(
vec![Requirement::from_str("pylint==2.3.0").unwrap()],
vec![],
vec![],
@ -85,7 +85,7 @@ async fn black() -> Result<()> {
let client = RegistryClientBuilder::default().build();
let manifest = Manifest::new(
let manifest = ResolutionManifest::new(
vec![Requirement::from_str("black<=23.9.1").unwrap()],
vec![],
vec![],
@ -106,7 +106,7 @@ async fn black_colorama() -> Result<()> {
let client = RegistryClientBuilder::default().build();
let manifest = Manifest::new(
let manifest = ResolutionManifest::new(
vec![Requirement::from_str("black[colorama]<=23.9.1").unwrap()],
vec![],
vec![],
@ -127,7 +127,7 @@ async fn black_python_310() -> Result<()> {
let client = RegistryClientBuilder::default().build();
let manifest = Manifest::new(
let manifest = ResolutionManifest::new(
vec![Requirement::from_str("black<=23.9.1").unwrap()],
vec![],
vec![],
@ -150,7 +150,7 @@ async fn black_mypy_extensions() -> Result<()> {
let client = RegistryClientBuilder::default().build();
let manifest = Manifest::new(
let manifest = ResolutionManifest::new(
vec![Requirement::from_str("black<=23.9.1").unwrap()],
vec![Requirement::from_str("mypy-extensions<1").unwrap()],
vec![],
@ -173,7 +173,7 @@ async fn black_mypy_extensions_extra() -> Result<()> {
let client = RegistryClientBuilder::default().build();
let manifest = Manifest::new(
let manifest = ResolutionManifest::new(
vec![Requirement::from_str("black<=23.9.1").unwrap()],
vec![Requirement::from_str("mypy-extensions[extra]<1").unwrap()],
vec![],
@ -196,7 +196,7 @@ async fn black_flake8() -> Result<()> {
let client = RegistryClientBuilder::default().build();
let manifest = Manifest::new(
let manifest = ResolutionManifest::new(
vec![Requirement::from_str("black<=23.9.1").unwrap()],
vec![Requirement::from_str("flake8<1").unwrap()],
vec![],
@ -217,7 +217,7 @@ async fn black_lowest() -> Result<()> {
let client = RegistryClientBuilder::default().build();
let manifest = Manifest::new(
let manifest = ResolutionManifest::new(
vec![Requirement::from_str("black>21").unwrap()],
vec![],
vec![],
@ -238,7 +238,7 @@ async fn black_lowest_direct() -> Result<()> {
let client = RegistryClientBuilder::default().build();
let manifest = Manifest::new(
let manifest = ResolutionManifest::new(
vec![Requirement::from_str("black>21").unwrap()],
vec![],
vec![],
@ -259,7 +259,7 @@ async fn black_respect_preference() -> Result<()> {
let client = RegistryClientBuilder::default().build();
let manifest = Manifest::new(
let manifest = ResolutionManifest::new(
vec![Requirement::from_str("black<=23.9.1").unwrap()],
vec![],
vec![Requirement::from_str("black==23.9.0").unwrap()],
@ -280,7 +280,7 @@ async fn black_ignore_preference() -> Result<()> {
let client = RegistryClientBuilder::default().build();
let manifest = Manifest::new(
let manifest = ResolutionManifest::new(
vec![Requirement::from_str("black<=23.9.1").unwrap()],
vec![],
vec![Requirement::from_str("black==23.9.2").unwrap()],