mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-01 06:21:13 +00:00
Add constraint dependencies to pyproject.toml (#5248)
Resolves #4467. ## Summary This PR implements the following 1. Add `tool.uv.constraint-dependencies` to pyproject.toml 1. Support to refer `tool.uv.constraint-dependencies` in `uv lock` 1. Support to refer `tool.uv.constraint-dependencies` in `uv pip compile/install` These are analogues of the override features implemented in #3839 and #4369. ## Test Plan Add test.
This commit is contained in:
parent
a917cdba51
commit
32ad3323a1
13 changed files with 243 additions and 1 deletions
|
@ -48,6 +48,7 @@ pub struct Options {
|
|||
)
|
||||
)]
|
||||
pub override_dependencies: Option<Vec<Requirement<VerbatimParsedUrl>>>,
|
||||
pub constraint_dependencies: Option<Vec<Requirement<VerbatimParsedUrl>>>,
|
||||
}
|
||||
|
||||
/// Global settings, relevant to all invocations.
|
||||
|
|
|
@ -104,6 +104,7 @@ pub struct ToolUv {
|
|||
)
|
||||
)]
|
||||
pub override_dependencies: Option<Vec<pep508_rs::Requirement<VerbatimParsedUrl>>>,
|
||||
pub constraint_dependencies: Option<Vec<pep508_rs::Requirement<VerbatimParsedUrl>>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, OptionsMetadata, Default, Debug, Clone, PartialEq, Eq)]
|
||||
|
|
|
@ -254,6 +254,38 @@ impl Workspace {
|
|||
.collect()
|
||||
}
|
||||
|
||||
/// Returns the set of constraints for the workspace.
|
||||
pub fn constraints(&self) -> Vec<Requirement> {
|
||||
let Some(workspace_package) = self
|
||||
.packages
|
||||
.values()
|
||||
.find(|workspace_package| workspace_package.root() == self.install_path())
|
||||
else {
|
||||
return vec![];
|
||||
};
|
||||
|
||||
let Some(constraints) = workspace_package
|
||||
.pyproject_toml()
|
||||
.tool
|
||||
.as_ref()
|
||||
.and_then(|tool| tool.uv.as_ref())
|
||||
.and_then(|uv| uv.constraint_dependencies.as_ref())
|
||||
else {
|
||||
return vec![];
|
||||
};
|
||||
|
||||
constraints
|
||||
.iter()
|
||||
.map(|requirement| {
|
||||
Requirement::from(
|
||||
requirement
|
||||
.clone()
|
||||
.with_origin(RequirementOrigin::Workspace),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The path to the workspace root, the directory containing the top level `pyproject.toml` with
|
||||
/// the `uv.tool.workspace`, or the `pyproject.toml` in an implicit single workspace project.
|
||||
pub fn install_path(&self) -> &PathBuf {
|
||||
|
|
|
@ -48,6 +48,7 @@ pub(crate) async fn pip_compile(
|
|||
requirements: &[RequirementsSource],
|
||||
constraints: &[RequirementsSource],
|
||||
overrides: &[RequirementsSource],
|
||||
constraints_from_workspace: Vec<Requirement>,
|
||||
overrides_from_workspace: Vec<Requirement>,
|
||||
extras: ExtrasSpecification,
|
||||
output_file: Option<&Path>,
|
||||
|
@ -126,6 +127,12 @@ pub(crate) async fn pip_compile(
|
|||
)
|
||||
.await?;
|
||||
|
||||
let constraints = constraints
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(constraints_from_workspace.into_iter())
|
||||
.collect();
|
||||
|
||||
let overrides: Vec<UnresolvedRequirementSpecification> = overrides
|
||||
.iter()
|
||||
.cloned()
|
||||
|
|
|
@ -40,6 +40,7 @@ pub(crate) async fn pip_install(
|
|||
requirements: &[RequirementsSource],
|
||||
constraints: &[RequirementsSource],
|
||||
overrides: &[RequirementsSource],
|
||||
constraints_from_workspace: Vec<Requirement>,
|
||||
overrides_from_workspace: Vec<Requirement>,
|
||||
extras: &ExtrasSpecification,
|
||||
resolution_mode: ResolutionMode,
|
||||
|
@ -104,6 +105,12 @@ pub(crate) async fn pip_install(
|
|||
)
|
||||
.await?;
|
||||
|
||||
let constraints: Vec<Requirement> = constraints
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(constraints_from_workspace.into_iter())
|
||||
.collect();
|
||||
|
||||
let overrides: Vec<UnresolvedRequirementSpecification> = overrides
|
||||
.iter()
|
||||
.cloned()
|
||||
|
|
|
@ -211,7 +211,7 @@ pub(super) async fn do_lock(
|
|||
.into_iter()
|
||||
.map(UnresolvedRequirementSpecification::from)
|
||||
.collect::<Vec<_>>();
|
||||
let constraints = vec![];
|
||||
let constraints = workspace.constraints();
|
||||
let dev = vec![DEV_DEPENDENCIES.clone()];
|
||||
let source_trees = vec![];
|
||||
|
||||
|
|
|
@ -200,6 +200,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
|
|||
&requirements,
|
||||
&constraints,
|
||||
&overrides,
|
||||
args.constraints_from_workspace,
|
||||
args.overrides_from_workspace,
|
||||
args.settings.extras,
|
||||
args.settings.output_file.as_deref(),
|
||||
|
@ -349,6 +350,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
|
|||
&requirements,
|
||||
&constraints,
|
||||
&overrides,
|
||||
args.constraints_from_workspace,
|
||||
args.overrides_from_workspace,
|
||||
&args.settings.extras,
|
||||
args.settings.resolution,
|
||||
|
|
|
@ -763,6 +763,7 @@ pub(crate) struct PipCompileSettings {
|
|||
pub(crate) src_file: Vec<PathBuf>,
|
||||
pub(crate) constraint: Vec<PathBuf>,
|
||||
pub(crate) r#override: Vec<PathBuf>,
|
||||
pub(crate) constraints_from_workspace: Vec<Requirement>,
|
||||
pub(crate) overrides_from_workspace: Vec<Requirement>,
|
||||
pub(crate) refresh: Refresh,
|
||||
pub(crate) settings: PipSettings,
|
||||
|
@ -824,6 +825,20 @@ impl PipCompileSettings {
|
|||
compat_args: _,
|
||||
} = args;
|
||||
|
||||
let constraints_from_workspace = if let Some(configuration) = &filesystem {
|
||||
configuration
|
||||
.constraint_dependencies
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|requirement| {
|
||||
Requirement::from(requirement.with_origin(RequirementOrigin::Workspace))
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
let overrides_from_workspace = if let Some(configuration) = &filesystem {
|
||||
configuration
|
||||
.override_dependencies
|
||||
|
@ -848,6 +863,7 @@ impl PipCompileSettings {
|
|||
.into_iter()
|
||||
.filter_map(Maybe::into_option)
|
||||
.collect(),
|
||||
constraints_from_workspace,
|
||||
overrides_from_workspace,
|
||||
refresh: Refresh::from(refresh),
|
||||
settings: PipSettings::combine(
|
||||
|
@ -988,6 +1004,7 @@ pub(crate) struct PipInstallSettings {
|
|||
pub(crate) constraint: Vec<PathBuf>,
|
||||
pub(crate) r#override: Vec<PathBuf>,
|
||||
pub(crate) dry_run: bool,
|
||||
pub(crate) constraints_from_workspace: Vec<Requirement>,
|
||||
pub(crate) overrides_from_workspace: Vec<Requirement>,
|
||||
pub(crate) refresh: Refresh,
|
||||
pub(crate) settings: PipSettings,
|
||||
|
@ -1036,6 +1053,20 @@ impl PipInstallSettings {
|
|||
compat_args: _,
|
||||
} = args;
|
||||
|
||||
let constraints_from_workspace = if let Some(configuration) = &filesystem {
|
||||
configuration
|
||||
.constraint_dependencies
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|requirement| {
|
||||
Requirement::from(requirement.with_origin(RequirementOrigin::Workspace))
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
let overrides_from_workspace = if let Some(configuration) = &filesystem {
|
||||
configuration
|
||||
.override_dependencies
|
||||
|
@ -1063,6 +1094,7 @@ impl PipInstallSettings {
|
|||
.filter_map(Maybe::into_option)
|
||||
.collect(),
|
||||
dry_run,
|
||||
constraints_from_workspace,
|
||||
overrides_from_workspace,
|
||||
refresh: Refresh::from(refresh),
|
||||
settings: PipSettings::combine(
|
||||
|
|
|
@ -644,6 +644,55 @@ fn lock_project_with_overrides() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Lock a project with a uv.tool.constraint-dependencies.
|
||||
#[test]
|
||||
fn lock_project_with_constraints() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["anyio==3.7.0"]
|
||||
|
||||
[tool.uv]
|
||||
constraint-dependencies = ["idna<3.4"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv lock` is experimental and may change without warning
|
||||
Resolved 4 packages in [TIME]
|
||||
"###);
|
||||
|
||||
// Install the base dependencies from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv sync` is experimental and may change without warning
|
||||
Prepared 4 packages in [TIME]
|
||||
Installed 4 packages in [TIME]
|
||||
+ anyio==3.7.0
|
||||
+ idna==3.3
|
||||
+ project==0.1.0 (from file://[TEMP_DIR]/)
|
||||
+ sniffio==1.3.1
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Lock a project with a dependency that has an extra.
|
||||
#[test]
|
||||
fn lock_dependency_extra() -> Result<()> {
|
||||
|
|
|
@ -3043,6 +3043,50 @@ fn override_dependency_from_pyproject() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Check that `tool.uv.constraint-dependencies` in `pyproject.toml` is respected.
|
||||
#[test]
|
||||
fn constraint_dependency_from_pyproject() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"[project]
|
||||
name = "example"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyio==3.7.0"
|
||||
]
|
||||
|
||||
[tool.uv]
|
||||
constraint-dependencies = [
|
||||
"idna<3.4"
|
||||
]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.pip_compile()
|
||||
.arg("pyproject.toml"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] pyproject.toml
|
||||
anyio==3.7.0
|
||||
# via example (pyproject.toml)
|
||||
idna==3.3
|
||||
# via
|
||||
# -c (workspace)
|
||||
# anyio
|
||||
sniffio==1.3.1
|
||||
# via anyio
|
||||
|
||||
----- stderr -----
|
||||
Resolved 3 packages in [TIME]
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check that `override-dependencies` in `uv.toml` is respected.
|
||||
#[test]
|
||||
fn override_dependency_from_specific_uv_toml() -> Result<()> {
|
||||
|
|
|
@ -2429,6 +2429,46 @@ fn install_constraints_txt() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Check that `tool.uv.constraint-dependencies` in `pyproject.toml` is respected.
|
||||
#[test]
|
||||
fn install_constraints_from_pyproject() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"[project]
|
||||
name = "example"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyio==3.7.0"
|
||||
]
|
||||
|
||||
[tool.uv]
|
||||
constraint-dependencies = [
|
||||
"idna<3.4"
|
||||
]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.pip_install()
|
||||
.arg("-r")
|
||||
.arg("pyproject.toml"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 3 packages in [TIME]
|
||||
Prepared 3 packages in [TIME]
|
||||
Installed 3 packages in [TIME]
|
||||
+ anyio==3.7.0
|
||||
+ idna==3.3
|
||||
+ sniffio==1.3.1
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Install a package from a `requirements.txt` file, with an inline constraint.
|
||||
#[test]
|
||||
fn install_constraints_inline() -> Result<()> {
|
||||
|
|
|
@ -73,6 +73,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
|
|||
],
|
||||
constraint: [],
|
||||
override: [],
|
||||
constraints_from_workspace: [],
|
||||
overrides_from_workspace: [],
|
||||
refresh: None(
|
||||
Timestamp(
|
||||
|
@ -206,6 +207,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
|
|||
],
|
||||
constraint: [],
|
||||
override: [],
|
||||
constraints_from_workspace: [],
|
||||
overrides_from_workspace: [],
|
||||
refresh: None(
|
||||
Timestamp(
|
||||
|
@ -340,6 +342,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
|
|||
],
|
||||
constraint: [],
|
||||
override: [],
|
||||
constraints_from_workspace: [],
|
||||
overrides_from_workspace: [],
|
||||
refresh: None(
|
||||
Timestamp(
|
||||
|
@ -506,6 +509,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
|
|||
],
|
||||
constraint: [],
|
||||
override: [],
|
||||
constraints_from_workspace: [],
|
||||
overrides_from_workspace: [],
|
||||
refresh: None(
|
||||
Timestamp(
|
||||
|
@ -641,6 +645,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
|
|||
],
|
||||
constraint: [],
|
||||
override: [],
|
||||
constraints_from_workspace: [],
|
||||
overrides_from_workspace: [],
|
||||
refresh: None(
|
||||
Timestamp(
|
||||
|
@ -762,6 +767,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
|
|||
],
|
||||
constraint: [],
|
||||
override: [],
|
||||
constraints_from_workspace: [],
|
||||
overrides_from_workspace: [],
|
||||
refresh: None(
|
||||
Timestamp(
|
||||
|
@ -920,6 +926,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
|
|||
],
|
||||
constraint: [],
|
||||
override: [],
|
||||
constraints_from_workspace: [],
|
||||
overrides_from_workspace: [],
|
||||
refresh: None(
|
||||
Timestamp(
|
||||
|
@ -1078,6 +1085,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
|
|||
],
|
||||
constraint: [],
|
||||
override: [],
|
||||
constraints_from_workspace: [],
|
||||
overrides_from_workspace: [],
|
||||
refresh: None(
|
||||
Timestamp(
|
||||
|
@ -1281,6 +1289,7 @@ fn resolve_find_links() -> anyhow::Result<()> {
|
|||
],
|
||||
constraint: [],
|
||||
override: [],
|
||||
constraints_from_workspace: [],
|
||||
overrides_from_workspace: [],
|
||||
refresh: None(
|
||||
Timestamp(
|
||||
|
@ -1438,6 +1447,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
|
|||
],
|
||||
constraint: [],
|
||||
override: [],
|
||||
constraints_from_workspace: [],
|
||||
overrides_from_workspace: [],
|
||||
refresh: None(
|
||||
Timestamp(
|
||||
|
@ -1565,6 +1575,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
|
|||
],
|
||||
constraint: [],
|
||||
override: [],
|
||||
constraints_from_workspace: [],
|
||||
overrides_from_workspace: [],
|
||||
refresh: None(
|
||||
Timestamp(
|
||||
|
@ -1720,6 +1731,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
|
|||
],
|
||||
constraint: [],
|
||||
override: [],
|
||||
constraints_from_workspace: [],
|
||||
overrides_from_workspace: [],
|
||||
refresh: None(
|
||||
Timestamp(
|
||||
|
@ -1899,6 +1911,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
|
|||
],
|
||||
constraint: [],
|
||||
override: [],
|
||||
constraints_from_workspace: [],
|
||||
overrides_from_workspace: [],
|
||||
refresh: None(
|
||||
Timestamp(
|
||||
|
@ -2016,6 +2029,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
|
|||
],
|
||||
constraint: [],
|
||||
override: [],
|
||||
constraints_from_workspace: [],
|
||||
overrides_from_workspace: [],
|
||||
refresh: None(
|
||||
Timestamp(
|
||||
|
@ -2133,6 +2147,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
|
|||
],
|
||||
constraint: [],
|
||||
override: [],
|
||||
constraints_from_workspace: [],
|
||||
overrides_from_workspace: [],
|
||||
refresh: None(
|
||||
Timestamp(
|
||||
|
@ -2252,6 +2267,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
|
|||
],
|
||||
constraint: [],
|
||||
override: [],
|
||||
constraints_from_workspace: [],
|
||||
overrides_from_workspace: [],
|
||||
refresh: None(
|
||||
Timestamp(
|
||||
|
@ -2396,6 +2412,7 @@ fn resolve_poetry_toml() -> anyhow::Result<()> {
|
|||
],
|
||||
constraint: [],
|
||||
override: [],
|
||||
constraints_from_workspace: [],
|
||||
overrides_from_workspace: [],
|
||||
refresh: None(
|
||||
Timestamp(
|
||||
|
@ -2541,6 +2558,7 @@ fn resolve_both() -> anyhow::Result<()> {
|
|||
],
|
||||
constraint: [],
|
||||
override: [],
|
||||
constraints_from_workspace: [],
|
||||
overrides_from_workspace: [],
|
||||
refresh: None(
|
||||
Timestamp(
|
||||
|
|
9
uv.schema.json
generated
9
uv.schema.json
generated
|
@ -29,6 +29,15 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"constraint-dependencies": {
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/Requirement"
|
||||
}
|
||||
},
|
||||
"dev-dependencies": {
|
||||
"description": "PEP 508-style requirements, e.g., `ruff==0.5.0`, or `ruff @ https://...`.",
|
||||
"type": [
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue