Skip to content

Commit

Permalink
Disallow pyproject.toml in pip sync and pip uninstall
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Mar 26, 2024
1 parent 5270624 commit 50dea9c
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 141 deletions.
46 changes: 34 additions & 12 deletions crates/uv-requirements/src/sources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,22 +39,44 @@ impl RequirementsSource {
}
}

/// Parse a [`RequirementsSource`] from a constraints file.
pub fn from_constraints_file(path: PathBuf) -> Self {
if path.ends_with("pyproject.toml") {
warn_user!(
"The file `{}` appears to be a `pyproject.toml` file, but constraints must be specified in `requirements.txt` format.", path.user_display()
);
/// Parse a [`RequirementsSource`] from a `requirements.txt` file.
pub fn from_requirements_txt(path: PathBuf) -> Self {
for filename in ["pyproject.toml", "setup.py", "setup.cfg"] {
if path.ends_with(filename) {
warn_user!(
"The file `{}` appears to be a `{}` file, but requirements must be specified in `requirements.txt` format.",
path.user_display(),
filename
);
}
}
Self::RequirementsTxt(path)
}

/// Parse a [`RequirementsSource`] from an overrides file.
pub fn from_overrides_file(path: PathBuf) -> Self {
if path.ends_with("pyproject.toml") {
warn_user!(
"The file `{}` appears to be a `pyproject.toml` file, but overrides must be specified in `requirements.txt` format.", path.user_display()
);
/// Parse a [`RequirementsSource`] from a `constraints.txt` file.
pub fn from_constraints_txt(path: PathBuf) -> Self {
for filename in ["pyproject.toml", "setup.py", "setup.cfg"] {
if path.ends_with(filename) {
warn_user!(
"The file `{}` appears to be a `{}` file, but constraints must be specified in `requirements.txt` format.",
path.user_display(),
filename
);
}
}
Self::RequirementsTxt(path)
}

/// Parse a [`RequirementsSource`] from an `overrides.txt` file.
pub fn from_overrides_txt(path: PathBuf) -> Self {
for filename in ["pyproject.toml", "setup.py", "setup.cfg"] {
if path.ends_with(filename) {
warn_user!(
"The file `{}` appears to be a `{}` file, but overrides must be specified in `requirements.txt` format.",
path.user_display(),
filename
);
}
}
Self::RequirementsTxt(path)
}
Expand Down
8 changes: 4 additions & 4 deletions crates/uv/src/commands/pip_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ use uv_installer::{
is_dynamic, Downloader, NoBinary, Plan, Planner, Reinstall, ResolvedEditable, SitePackages,
};
use uv_interpreter::{Interpreter, PythonEnvironment};
use uv_requirements::{
ExtrasSpecification, NamedRequirementsResolver, RequirementsSource, RequirementsSpecification,
SourceTreeResolver,
};
use uv_resolver::InMemoryIndex;
use uv_traits::{BuildIsolation, ConfigSettings, InFlight, NoBuild, SetupPyStrategy};
use uv_warnings::warn_user;
Expand All @@ -31,10 +35,6 @@ use crate::commands::reporters::{
};
use crate::commands::{compile_bytecode, elapsed, ChangeEvent, ChangeEventKind, ExitStatus};
use crate::printer::Printer;
use uv_requirements::{
ExtrasSpecification, NamedRequirementsResolver, RequirementsSource, RequirementsSpecification,
SourceTreeResolver,
};

/// Install a set of locked requirements into the current Python environment.
#[allow(clippy::too_many_arguments, clippy::fn_params_excessive_bools)]
Expand Down
10 changes: 5 additions & 5 deletions crates/uv/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1469,12 +1469,12 @@ async fn run() -> Result<ExitStatus> {
let constraints = args
.constraint
.into_iter()
.map(RequirementsSource::from_constraints_file)
.map(RequirementsSource::from_constraints_txt)
.collect::<Vec<_>>();
let overrides = args
.r#override
.into_iter()
.map(RequirementsSource::from_overrides_file)
.map(RequirementsSource::from_overrides_txt)
.collect::<Vec<_>>();
let index_urls = IndexLocations::new(
args.index_url.and_then(Maybe::into_option),
Expand Down Expand Up @@ -1624,12 +1624,12 @@ async fn run() -> Result<ExitStatus> {
let constraints = args
.constraint
.into_iter()
.map(RequirementsSource::from_constraints_file)
.map(RequirementsSource::from_constraints_txt)
.collect::<Vec<_>>();
let overrides = args
.r#override
.into_iter()
.map(RequirementsSource::from_overrides_file)
.map(RequirementsSource::from_overrides_txt)
.collect::<Vec<_>>();
let index_urls = IndexLocations::new(
args.index_url.and_then(Maybe::into_option),
Expand Down Expand Up @@ -1714,7 +1714,7 @@ async fn run() -> Result<ExitStatus> {
.chain(
args.requirement
.into_iter()
.map(RequirementsSource::from_requirements_file),
.map(RequirementsSource::from_requirements_txt),
)
.collect::<Vec<_>>();
commands::pip_uninstall(
Expand Down
110 changes: 110 additions & 0 deletions crates/uv/tests/pip_install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,116 @@ fn empty_requirements_txt() -> Result<()> {
Ok(())
}

#[test]
fn missing_pyproject_toml() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.touch()?;

uv_snapshot!(command(&context)
.arg("-r")
.arg("pyproject.toml"), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: failed to read from file `pyproject.toml`
Caused by: No such file or directory (os error 2)
"###
);

Ok(())
}

#[test]
fn invalid_pyproject_toml_syntax() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str("123 - 456")?;

uv_snapshot!(command(&context)
.arg("-r")
.arg("pyproject.toml"), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to parse `pyproject.toml`
Caused by: TOML parse error at line 1, column 5
|
1 | 123 - 456
| ^
expected `.`, `=`
"###
);

Ok(())
}

#[test]
fn invalid_pyproject_toml_schema() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str("[project]")?;

uv_snapshot!(command(&context)
.arg("-r")
.arg("pyproject.toml"), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to parse `pyproject.toml`
Caused by: TOML parse error at line 1, column 1
|
1 | [project]
| ^^^^^^^^^
missing field `name`
"###
);

Ok(())
}

#[test]
fn invalid_pyproject_toml_requirement() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"[project]
name = "project"
dependencies = ["flask==1.0.x"]
"#,
)?;

uv_snapshot!(command(&context)
.arg("-r")
.arg("pyproject.toml"), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to parse `pyproject.toml`
Caused by: TOML parse error at line 3, column 16
|
3 | dependencies = ["flask==1.0.x"]
| ^^^^^^^^^^^^^^^^
after parsing 1.0, found ".x" after it, which is not part of a valid version
flask==1.0.x
^^^^^^^
"###
);

Ok(())
}

#[test]
fn no_solution() {
let context = TestContext::new("3.12");
Expand Down
120 changes: 0 additions & 120 deletions crates/uv/tests/pip_uninstall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,126 +149,6 @@ fn invalid_requirements_txt_requirement() -> Result<()> {
Ok(())
}

#[test]
fn missing_pyproject_toml() -> Result<()> {
let temp_dir = assert_fs::TempDir::new()?;

uv_snapshot!(Command::new(get_bin())
.arg("pip")
.arg("uninstall")
.arg("-r")
.arg("pyproject.toml")
.current_dir(&temp_dir), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: failed to read from file `pyproject.toml`
Caused by: No such file or directory (os error 2)
"###
);

Ok(())
}

#[test]
fn invalid_pyproject_toml_syntax() -> Result<()> {
let temp_dir = assert_fs::TempDir::new()?;
let pyproject_toml = temp_dir.child("pyproject.toml");
pyproject_toml.touch()?;
pyproject_toml.write_str("123 - 456")?;

uv_snapshot!(Command::new(get_bin())
.arg("pip")
.arg("uninstall")
.arg("-r")
.arg("pyproject.toml")
.current_dir(&temp_dir), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to parse `pyproject.toml`
Caused by: TOML parse error at line 1, column 5
|
1 | 123 - 456
| ^
expected `.`, `=`
"###);

Ok(())
}

#[test]
fn invalid_pyproject_toml_schema() -> Result<()> {
let temp_dir = assert_fs::TempDir::new()?;
let pyproject_toml = temp_dir.child("pyproject.toml");
pyproject_toml.touch()?;
pyproject_toml.write_str("[project]")?;

uv_snapshot!(Command::new(get_bin())
.arg("pip")
.arg("uninstall")
.arg("-r")
.arg("pyproject.toml")
.current_dir(&temp_dir), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to parse `pyproject.toml`
Caused by: TOML parse error at line 1, column 1
|
1 | [project]
| ^^^^^^^^^
missing field `name`
"###);

Ok(())
}

#[test]
fn invalid_pyproject_toml_requirement() -> Result<()> {
let temp_dir = assert_fs::TempDir::new()?;
let pyproject_toml = temp_dir.child("pyproject.toml");
pyproject_toml.touch()?;
pyproject_toml.write_str(
r#"[project]
name = "project"
dependencies = ["flask==1.0.x"]
"#,
)?;

uv_snapshot!(Command::new(get_bin())
.arg("pip")
.arg("uninstall")
.arg("-r")
.arg("pyproject.toml")
.current_dir(&temp_dir), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to parse `pyproject.toml`
Caused by: TOML parse error at line 3, column 16
|
3 | dependencies = ["flask==1.0.x"]
| ^^^^^^^^^^^^^^^^
after parsing 1.0, found ".x" after it, which is not part of a valid version
flask==1.0.x
^^^^^^^
"###);

Ok(())
}

#[test]
fn uninstall() -> Result<()> {
let context = TestContext::new("3.12");
Expand Down

0 comments on commit 50dea9c

Please sign in to comment.