mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Read persistent configuration from non-workspace pyproject.toml
(#4526)
## Summary If the user puts their configuration in a `pyproject.toml` that _isn't_ a valid workspace root (e.g., it's a Poetry file), we won't discover it, because we only look in `uv.toml` files in that case. I think this is somewhat debatable... We could choose to _require_ `uv.toml` there, but as a user I'd probably expect it to work? Closes https://github.com/astral-sh/uv/issues/4521.
This commit is contained in:
parent
5641f3a5d9
commit
a81742c06b
6 changed files with 169 additions and 14 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -4999,6 +4999,7 @@ dependencies = [
|
||||||
"uv-normalize",
|
"uv-normalize",
|
||||||
"uv-resolver",
|
"uv-resolver",
|
||||||
"uv-toolchain",
|
"uv-toolchain",
|
||||||
|
"uv-warnings",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -19,10 +19,11 @@ pep508_rs = { workspace = true }
|
||||||
pypi-types = { workspace = true }
|
pypi-types = { workspace = true }
|
||||||
uv-configuration = { workspace = true, features = ["schemars"] }
|
uv-configuration = { workspace = true, features = ["schemars"] }
|
||||||
uv-fs = { workspace = true }
|
uv-fs = { workspace = true }
|
||||||
|
uv-macros = { workspace = true }
|
||||||
uv-normalize = { workspace = true, features = ["schemars"] }
|
uv-normalize = { workspace = true, features = ["schemars"] }
|
||||||
uv-resolver = { workspace = true, features = ["schemars"] }
|
uv-resolver = { workspace = true, features = ["schemars"] }
|
||||||
uv-toolchain = { workspace = true, features = ["schemars"] }
|
uv-toolchain = { workspace = true, features = ["schemars"] }
|
||||||
uv-macros = { workspace = true }
|
uv-warnings = { workspace = true }
|
||||||
|
|
||||||
dirs-sys = { workspace = true }
|
dirs-sys = { workspace = true }
|
||||||
fs-err = { workspace = true }
|
fs-err = { workspace = true }
|
||||||
|
|
|
@ -4,6 +4,7 @@ use std::path::{Path, PathBuf};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use uv_fs::Simplified;
|
use uv_fs::Simplified;
|
||||||
|
use uv_warnings::warn_user;
|
||||||
|
|
||||||
pub use crate::combine::*;
|
pub use crate::combine::*;
|
||||||
pub use crate::settings::*;
|
pub use crate::settings::*;
|
||||||
|
@ -57,22 +58,25 @@ impl FilesystemOptions {
|
||||||
|
|
||||||
/// Find the [`FilesystemOptions`] for the given path.
|
/// Find the [`FilesystemOptions`] for the given path.
|
||||||
///
|
///
|
||||||
/// The search starts at the given path and goes up the directory tree until a `uv.toml` file is
|
/// The search starts at the given path and goes up the directory tree until a `uv.toml` file or
|
||||||
/// found.
|
/// `pyproject.toml` file is found.
|
||||||
pub fn find(path: impl AsRef<Path>) -> Result<Option<Self>, Error> {
|
pub fn find(path: impl AsRef<Path>) -> Result<Option<Self>, Error> {
|
||||||
for ancestor in path.as_ref().ancestors() {
|
for ancestor in path.as_ref().ancestors() {
|
||||||
// Read a `uv.toml` file in the current directory.
|
match Self::from_directory(ancestor) {
|
||||||
let path = ancestor.join("uv.toml");
|
Ok(Some(options)) => {
|
||||||
match fs_err::read_to_string(&path) {
|
return Ok(Some(options));
|
||||||
Ok(content) => {
|
}
|
||||||
let options: Options = toml::from_str(&content)
|
Ok(None) => {
|
||||||
.map_err(|err| Error::UvToml(path.user_display().to_string(), err))?;
|
// Continue traversing the directory tree.
|
||||||
|
}
|
||||||
debug!("Found workspace configuration at `{}`", path.display());
|
Err(Error::PyprojectToml(file, _err)) => {
|
||||||
return Ok(Some(Self(options)));
|
// If we see an invalid `pyproject.toml`, warn but continue.
|
||||||
|
warn_user!("Failed to parse `{file}` during settings discovery; skipping...");
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
// Otherwise, warn and stop.
|
||||||
|
return Err(err);
|
||||||
}
|
}
|
||||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
|
|
||||||
Err(err) => return Err(err.into()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
@ -103,9 +107,17 @@ impl FilesystemOptions {
|
||||||
let pyproject: PyProjectToml = toml::from_str(&content)
|
let pyproject: PyProjectToml = toml::from_str(&content)
|
||||||
.map_err(|err| Error::PyprojectToml(path.user_display().to_string(), err))?;
|
.map_err(|err| Error::PyprojectToml(path.user_display().to_string(), err))?;
|
||||||
let Some(tool) = pyproject.tool else {
|
let Some(tool) = pyproject.tool else {
|
||||||
|
debug!(
|
||||||
|
"Skipping `pyproject.toml` in `{}` (no `[tool]` section)",
|
||||||
|
dir.as_ref().display()
|
||||||
|
);
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
let Some(options) = tool.uv else {
|
let Some(options) = tool.uv else {
|
||||||
|
debug!(
|
||||||
|
"Skipping `pyproject.toml` in `{}` (no `[tool.uv]` section)",
|
||||||
|
dir.as_ref().display()
|
||||||
|
);
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3204,6 +3204,7 @@ fn override_dependency_from_workspace_invalid_syntax() -> Result<()> {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
|
warning: Failed to parse `pyproject.toml` during settings discovery; skipping...
|
||||||
error: Failed to parse: `pyproject.toml`
|
error: Failed to parse: `pyproject.toml`
|
||||||
Caused by: TOML parse error at line 9, column 29
|
Caused by: TOML parse error at line 9, column 29
|
||||||
|
|
|
|
||||||
|
|
|
@ -143,6 +143,7 @@ fn invalid_pyproject_toml_syntax() -> Result<()> {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
|
warning: Failed to parse `pyproject.toml` during settings discovery; skipping...
|
||||||
error: Failed to parse: `pyproject.toml`
|
error: Failed to parse: `pyproject.toml`
|
||||||
Caused by: TOML parse error at line 1, column 5
|
Caused by: TOML parse error at line 1, column 5
|
||||||
|
|
|
|
||||||
|
|
|
@ -2246,3 +2246,142 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read from a `pyproject.toml` file in the current directory. In this case, the `pyproject.toml`
|
||||||
|
/// file uses the Poetry schema.
|
||||||
|
#[test]
|
||||||
|
#[cfg_attr(
|
||||||
|
windows,
|
||||||
|
ignore = "Configuration tests are not yet supported on Windows"
|
||||||
|
)]
|
||||||
|
fn resolve_poetry_toml() -> anyhow::Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
|
// Write a `uv.toml` file to the directory.
|
||||||
|
let config = context.temp_dir.child("pyproject.toml");
|
||||||
|
config.write_str(indoc::indoc! {r#"
|
||||||
|
[tool.poetry]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.10"
|
||||||
|
rich = "^13.7.1"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
|
[tool.uv.pip]
|
||||||
|
resolution = "lowest-direct"
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
let requirements_in = context.temp_dir.child("requirements.in");
|
||||||
|
requirements_in.write_str("anyio>3.0.0")?;
|
||||||
|
|
||||||
|
// Resolution should use the lowest direct version, and generate hashes.
|
||||||
|
uv_snapshot!(context.filters(), command(&context)
|
||||||
|
.arg("--show-settings")
|
||||||
|
.arg("requirements.in"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
GlobalSettings {
|
||||||
|
quiet: false,
|
||||||
|
verbose: 0,
|
||||||
|
color: Auto,
|
||||||
|
native_tls: false,
|
||||||
|
connectivity: Online,
|
||||||
|
isolated: false,
|
||||||
|
show_settings: true,
|
||||||
|
preview: Disabled,
|
||||||
|
toolchain_preference: OnlySystem,
|
||||||
|
}
|
||||||
|
CacheSettings {
|
||||||
|
no_cache: false,
|
||||||
|
cache_dir: Some(
|
||||||
|
"[CACHE_DIR]/",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
PipCompileSettings {
|
||||||
|
src_file: [
|
||||||
|
"requirements.in",
|
||||||
|
],
|
||||||
|
constraint: [],
|
||||||
|
override: [],
|
||||||
|
overrides_from_workspace: [],
|
||||||
|
refresh: None(
|
||||||
|
Timestamp(
|
||||||
|
SystemTime {
|
||||||
|
tv_sec: [TIME],
|
||||||
|
tv_nsec: [TIME],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
settings: PipSettings {
|
||||||
|
index_locations: IndexLocations {
|
||||||
|
index: None,
|
||||||
|
extra_index: [],
|
||||||
|
flat_index: [],
|
||||||
|
no_index: false,
|
||||||
|
},
|
||||||
|
python: None,
|
||||||
|
system: false,
|
||||||
|
extras: None,
|
||||||
|
break_system_packages: false,
|
||||||
|
target: None,
|
||||||
|
prefix: None,
|
||||||
|
index_strategy: FirstIndex,
|
||||||
|
keyring_provider: Disabled,
|
||||||
|
no_build_isolation: false,
|
||||||
|
build_options: BuildOptions {
|
||||||
|
no_binary: None,
|
||||||
|
no_build: None,
|
||||||
|
},
|
||||||
|
strict: false,
|
||||||
|
dependency_mode: Transitive,
|
||||||
|
resolution: LowestDirect,
|
||||||
|
prerelease: IfNecessaryOrExplicit,
|
||||||
|
output_file: None,
|
||||||
|
no_strip_extras: false,
|
||||||
|
no_annotate: false,
|
||||||
|
no_header: false,
|
||||||
|
custom_compile_command: None,
|
||||||
|
generate_hashes: false,
|
||||||
|
setup_py: Pep517,
|
||||||
|
config_setting: ConfigSettings(
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
python_version: None,
|
||||||
|
python_platform: None,
|
||||||
|
exclude_newer: Some(
|
||||||
|
ExcludeNewer(
|
||||||
|
2024-03-25T00:00:00Z,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
no_emit_package: [],
|
||||||
|
emit_index_url: false,
|
||||||
|
emit_find_links: false,
|
||||||
|
emit_build_options: false,
|
||||||
|
emit_marker_expression: false,
|
||||||
|
emit_index_annotation: false,
|
||||||
|
annotation_style: Split,
|
||||||
|
link_mode: Clone,
|
||||||
|
compile_bytecode: false,
|
||||||
|
require_hashes: false,
|
||||||
|
upgrade: None,
|
||||||
|
reinstall: None,
|
||||||
|
concurrency: Concurrency {
|
||||||
|
downloads: 50,
|
||||||
|
builds: 16,
|
||||||
|
installs: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue