Skip to content

Commit

Permalink
Enable projects to opt-out of workspace management (#4565)
Browse files Browse the repository at this point in the history
## Summary

You can now add `managed = false` under `[tool.uv]` in a
`pyproject.toml` to explicitly opt out of the project and workspace
APIs.

If a project sets `managed = false`, we will (1) _not_ discover it as a
workspace root, and (2) _not_ discover it as a workspace member (similar
to using `exclude` in the workspace parent).

Closes #4551.
  • Loading branch information
charliermarsh committed Jul 1, 2024
1 parent be2a67c commit a4417eb
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 0 deletions.
4 changes: 4 additions & 0 deletions crates/uv-distribution/src/pyproject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,11 @@ pub struct Tool {
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ToolUv {
pub sources: Option<BTreeMap<PackageName, Source>>,
/// The workspace definition for the project, if any.
pub workspace: Option<ToolUvWorkspace>,
/// Whether the project is managed by `uv`. If `false`, `uv` will ignore the project when
/// `uv run` is invoked.
pub managed: Option<bool>,
#[cfg_attr(
feature = "schemars",
schemars(
Expand Down
44 changes: 44 additions & 0 deletions crates/uv-distribution/src/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ pub enum WorkspaceError {
MissingProject(PathBuf),
#[error("No workspace found for: `{}`", _0.simplified_display())]
MissingWorkspace(PathBuf),
#[error("The project is marked as unmanaged: `{}`", _0.simplified_display())]
NonWorkspace(PathBuf),
#[error("pyproject.toml section is declared as dynamic, but must be static: `{0}`")]
DynamicNotAllowed(&'static str),
#[error("Failed to find directories for glob: `{0}`")]
Expand Down Expand Up @@ -83,6 +85,21 @@ impl Workspace {
.map_err(WorkspaceError::Normalize)?
.to_path_buf();

// Check if the project is explicitly marked as unmanaged.
if pyproject_toml
.tool
.as_ref()
.and_then(|tool| tool.uv.as_ref())
.and_then(|uv| uv.managed)
== Some(false)
{
debug!(
"Project `{}` is marked as unmanaged",
project_path.simplified_display()
);
return Err(WorkspaceError::NonWorkspace(project_path));
}

// Check if the current project is also an explicit workspace root.
let explicit_root = pyproject_toml
.tool
Expand Down Expand Up @@ -326,6 +343,21 @@ impl Workspace {
let pyproject_toml = PyProjectToml::from_string(contents)
.map_err(|err| WorkspaceError::Toml(pyproject_path, Box::new(err)))?;

// Check if the current project is explicitly marked as unmanaged.
if pyproject_toml
.tool
.as_ref()
.and_then(|tool| tool.uv.as_ref())
.and_then(|uv| uv.managed)
== Some(false)
{
debug!(
"Project `{}` is marked as unmanaged; omitting from workspace members",
pyproject_toml.project.as_ref().unwrap().name
);
continue;
}

// Extract the package name.
let Some(project) = pyproject_toml.project.clone() else {
return Err(WorkspaceError::MissingProject(member_root));
Expand Down Expand Up @@ -586,6 +618,18 @@ impl ProjectWorkspace {
.map_err(WorkspaceError::Normalize)?
.to_path_buf();

// Check if workspaces are explicitly disabled for the project.
if project_pyproject_toml
.tool
.as_ref()
.and_then(|tool| tool.uv.as_ref())
.and_then(|uv| uv.managed)
== Some(false)
{
debug!("Project `{}` is marked as unmanaged", project.name);
return Err(WorkspaceError::NonWorkspace(project_path));
}

// Check if the current project is also an explicit workspace root.
let mut workspace = project_pyproject_toml
.tool
Expand Down
1 change: 1 addition & 0 deletions crates/uv/src/commands/project/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ pub(crate) async fn run(
match VirtualProject::discover(&std::env::current_dir()?, None).await {
Ok(project) => Some(project),
Err(WorkspaceError::MissingPyprojectToml) => None,
Err(WorkspaceError::NonWorkspace(_)) => None,
Err(err) => return Err(err.into()),
}
};
Expand Down
31 changes: 31 additions & 0 deletions crates/uv/tests/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,3 +284,34 @@ fn run_script() -> Result<()> {

Ok(())
}

/// With `managed = false`, we should avoid installing the project itself.
#[test]
fn run_managed_false() -> Result<()> {
let context = TestContext::new("3.12");

let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(indoc! { r#"
[project]
name = "foo"
version = "1.0.0"
requires-python = ">=3.8"
dependencies = ["anyio"]
[tool.uv]
managed = false
"#
})?;

uv_snapshot!(context.filters(), context.run().arg("python").arg("--version"), @r###"
success: true
exit_code: 0
----- stdout -----
Python 3.12.[X]
----- stderr -----
warning: `uv run` is experimental and may change without warning.
"###);

Ok(())
}
15 changes: 15 additions & 0 deletions crates/uv/tests/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,21 @@ fn test_albatross_project_in_excluded() {
);

context.assert_file(current_dir.join("check_installed_bird_feeder.py"));

let current_dir = workspaces_dir()
.join("albatross-project-in-excluded")
.join("packages")
.join("seeds");
uv_snapshot!(context.filters(), install_workspace(&context, &current_dir), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to download and build: `seeds @ file://[WORKSPACE]/scripts/workspaces/albatross-project-in-excluded/packages/seeds`
Caused by: The project is marked as unmanaged: `[WORKSPACE]/scripts/workspaces/albatross-project-in-excluded/packages/seeds`
"###
);
}

#[test]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[project]
name = "seeds"
version = "1.0.0"
requires-python = ">=3.12"
dependencies = ["idna==3.6"]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.uv]
managed = false
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import idna


def seeds():
print("sunflower")
8 changes: 8 additions & 0 deletions uv.schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit a4417eb

Please sign in to comment.