Allow --project to refer to a pyproject.toml directly and reduce to a warning on other files (#18513)
Closes https://github.com/astral-sh/uv/issues/18508 This unintentionally regressed in https://github.com/astral-sh/uv/pull/17714 as it appeared that `--project <file>` always failed but it actually succeeds if the file is has an ancestor directory with a `pyproject.toml`. This also removes the warning for `--project [path/]pyproject.toml` as I think that's a fine use-case. --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
+17
-6
@@ -88,6 +88,9 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
||||
}
|
||||
|
||||
// Determine the project directory.
|
||||
//
|
||||
// If `--project` points to a `pyproject.toml` file, resolve to its parent directory,
|
||||
// since downstream code (e.g., `FilesystemOptions::find`) expects a directory.
|
||||
let project_dir = cli
|
||||
.top_level
|
||||
.global_args
|
||||
@@ -96,6 +99,14 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
||||
.map(std::path::absolute)
|
||||
.transpose()?
|
||||
.map(uv_fs::normalize_path_buf)
|
||||
.map(|path| {
|
||||
path.file_name()
|
||||
.filter(|name| *name == "pyproject.toml")
|
||||
.filter(|_| path.is_file())
|
||||
.and_then(|_| path.parent())
|
||||
.map(Path::to_path_buf)
|
||||
.unwrap_or(path)
|
||||
})
|
||||
.map(Cow::Owned)
|
||||
.unwrap_or_else(|| Cow::Borrowed(&*CWD));
|
||||
|
||||
@@ -133,18 +144,18 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
||||
project_path.user_display()
|
||||
);
|
||||
} else if !project_dir.is_dir() {
|
||||
// On Unix, this always fails downstream (e.g., "Not a directory" when
|
||||
// trying to read `uv.toml`), so we bail with a clear error message.
|
||||
// On Windows, this is currently non-fatal, so we only error with the
|
||||
// preview flag.
|
||||
if cfg!(unix) || preview.is_enabled(PreviewFeature::ProjectDirectoryMustExist) {
|
||||
// `--project path/to/pyproject.toml` is resolved to its parent above,
|
||||
// so this only triggers for other file types (see #18508).
|
||||
if preview.is_enabled(PreviewFeature::ProjectDirectoryMustExist) {
|
||||
bail!(
|
||||
"Project path `{}` is not a directory",
|
||||
project_path.user_display()
|
||||
);
|
||||
}
|
||||
warn_user_once!(
|
||||
"Project path `{}` is not a directory. Use `--preview-features project-directory-must-exist` to error on this.",
|
||||
"Project path `{}` is not a directory. \
|
||||
This will become an error in a future release. \
|
||||
Use `--preview-features project-directory-must-exist` to error on this now.",
|
||||
project_path.user_display()
|
||||
);
|
||||
}
|
||||
|
||||
+149
-30
@@ -6617,46 +6617,165 @@ fn run_project_not_found_uv_preview_env() {
|
||||
");
|
||||
}
|
||||
|
||||
/// Using `--project` with a file path should error on Unix (it fails downstream anyway).
|
||||
/// When `--project` points to a `pyproject.toml` file, resolve to its parent directory without
|
||||
/// warning.
|
||||
///
|
||||
/// See: <https://github.com/astral-sh/uv/issues/18508>
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn run_project_is_file() -> Result<()> {
|
||||
fn run_project_pyproject_toml_file() -> Result<()> {
|
||||
let context = uv_test::test_context!("3.12");
|
||||
|
||||
// Create a file instead of a directory.
|
||||
let file_path = context.temp_dir.child("not-a-directory");
|
||||
let project_dir = context.temp_dir.child("project");
|
||||
project_dir.create_dir_all()?;
|
||||
|
||||
let pyproject_toml = project_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(indoc! { r#"
|
||||
[project]
|
||||
name = "foo"
|
||||
version = "1.0.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
"#
|
||||
})?;
|
||||
|
||||
// Passing `--project project/pyproject.toml` should resolve to the parent directory without warning.
|
||||
uv_snapshot!(context.filters(), context.run()
|
||||
.arg("--project")
|
||||
.arg(project_dir.join("pyproject.toml"))
|
||||
.env_remove(EnvVars::VIRTUAL_ENV)
|
||||
.arg("--")
|
||||
.arg("python")
|
||||
.arg("--version"), @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Python 3.12.[X]
|
||||
|
||||
----- stderr -----
|
||||
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
Creating virtual environment at: project/.venv
|
||||
Resolved 1 package in [TIME]
|
||||
Checked in [TIME]
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Using `--project` with a non-`pyproject.toml` file should warn.
|
||||
#[test]
|
||||
fn run_project_non_pyproject_file() -> Result<()> {
|
||||
let context = uv_test::test_context!("3.12");
|
||||
|
||||
let project_dir = context.temp_dir.child("project");
|
||||
project_dir.create_dir_all()?;
|
||||
|
||||
let pyproject_toml = project_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(indoc! { r#"
|
||||
[project]
|
||||
name = "foo"
|
||||
version = "1.0.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
"#
|
||||
})?;
|
||||
|
||||
let other_file = project_dir.child("README.md");
|
||||
other_file.write_str("")?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.run()
|
||||
.arg("--project")
|
||||
.arg(other_file.path())
|
||||
.env_remove(EnvVars::VIRTUAL_ENV)
|
||||
.arg("--")
|
||||
.arg("python")
|
||||
.arg("--version"), @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Python 3.12.[X]
|
||||
|
||||
----- stderr -----
|
||||
warning: Project path `project/README.md` is not a directory. This will become an error in a future release. Use `--preview-features project-directory-must-exist` to error on this now.
|
||||
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
Creating virtual environment at: project/.venv
|
||||
Resolved 1 package in [TIME]
|
||||
Checked in [TIME]
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Using `--project` with a nested non-`pyproject.toml` file should warn. Workspace discovery
|
||||
/// walks ancestors to find the `pyproject.toml`.
|
||||
#[test]
|
||||
fn run_project_nested_file() -> Result<()> {
|
||||
let context = uv_test::test_context!("3.12");
|
||||
|
||||
let project_dir = context.temp_dir.child("project");
|
||||
project_dir.create_dir_all()?;
|
||||
|
||||
let pyproject_toml = project_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(indoc! { r#"
|
||||
[project]
|
||||
name = "foo"
|
||||
version = "1.0.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
"#
|
||||
})?;
|
||||
|
||||
let subdir = project_dir.child("subdir");
|
||||
subdir.create_dir_all()?;
|
||||
let nested_file = subdir.child("somefile");
|
||||
nested_file.write_str("")?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.run()
|
||||
.arg("--project")
|
||||
.arg(nested_file.path())
|
||||
.env_remove(EnvVars::VIRTUAL_ENV)
|
||||
.arg("--")
|
||||
.arg("python")
|
||||
.arg("--version"), @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Python 3.12.[X]
|
||||
|
||||
----- stderr -----
|
||||
warning: Project path `project/subdir/somefile` is not a directory. This will become an error in a future release. Use `--preview-features project-directory-must-exist` to error on this now.
|
||||
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
Creating virtual environment at: project/.venv
|
||||
Resolved 1 package in [TIME]
|
||||
Checked in [TIME]
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Using `--project` with a file that has no ancestor project should warn, then fail downstream.
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn run_project_file_no_ancestor_project() -> Result<()> {
|
||||
let context = uv_test::test_context!("3.12");
|
||||
|
||||
let isolated_dir = context.temp_dir.child("isolated");
|
||||
isolated_dir.create_dir_all()?;
|
||||
let file_path = isolated_dir.child("somefile");
|
||||
file_path.write_str("")?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.run().arg("--project").arg(file_path.path()).arg("python").arg("-c").arg("print('hello')"), @"
|
||||
uv_snapshot!(context.filters(), context.run()
|
||||
.arg("--project")
|
||||
.arg(file_path.path())
|
||||
.arg("--")
|
||||
.arg("python")
|
||||
.arg("--version"), @"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Project path `not-a-directory` is not a directory
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Using `--project` with a file path should warn on Windows (where it's currently non-fatal).
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn run_project_is_file() -> Result<()> {
|
||||
let context = uv_test::test_context!("3.12");
|
||||
|
||||
// Create a file instead of a directory.
|
||||
let file_path = context.temp_dir.child("not-a-directory");
|
||||
file_path.write_str("")?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.run().arg("--project").arg(file_path.path()).arg("python").arg("-c").arg("print('hello')"), @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
hello
|
||||
|
||||
----- stderr -----
|
||||
warning: Project path `not-a-directory` is not a directory. Use `--preview-features project-directory-must-exist` to error on this.
|
||||
warning: Project path `isolated/somefile` is not a directory. This will become an error in a future release. Use `--preview-features project-directory-must-exist` to error on this now.
|
||||
error: failed to open file `[TEMP_DIR]/isolated/somefile/uv.toml`: Not a directory (os error 20)
|
||||
");
|
||||
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user