Add support for virtual projects (#6585)

## Summary

The basic idea here is: any project can either be a package, or not
("virtual").

If a project is virtual, we don't build or install it.

A project is virtual if either of the following are true:

- `tool.uv.virtual = true` is set.
- `[build-system]` is absent.

The concept of "virtual projects" only applies to workspace member right
now; it doesn't apply to `path` dependencies which are treated like
arbitrary Python source trees.

TODOs that should be resolved prior to merging:

- [ ] Documentation
- [ ] How do we reconcile this with "virtual workspace roots" which are
a little different -- they omit `[project]` entirely and don't even have
a name?
- [x] `uv init --virtual` should create a virtual project rather than a
virtual workspace.
- [x] Running `uv sync` in a virtual project after `uv init --virtual`
shows `Audited 0 packages in 0.01ms`, which is awkward. (See:
https://github.com/astral-sh/uv/pull/6588.)

Closes https://github.com/astral-sh/uv/issues/6511.
This commit is contained in:
Charlie Marsh 2024-08-27 13:42:46 -04:00 committed by GitHub
parent 6d38d42b41
commit eb14056e9c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 1710 additions and 147 deletions

View file

@ -2099,11 +2099,12 @@ pub struct InitArgs {
#[arg(long)]
pub name: Option<PackageName>,
/// Create a virtual workspace instead of a project.
/// Create a virtual project, rather than a package.
///
/// A virtual workspace does not define project dependencies and cannot be
/// published. Instead, workspace members declare project dependencies.
/// Development dependencies may still be declared.
/// A virtual project is a project that is not intended to be built as a Python package,
/// such as a project that only contains scripts or other application code.
///
/// Virtual projects themselves are not installed into the Python environment.
#[arg(long)]
pub r#virtual: bool,

View file

@ -70,6 +70,10 @@ pub struct Options {
#[serde(default, skip_serializing)]
#[cfg_attr(feature = "schemars", schemars(skip))]
managed: serde::de::IgnoredAny,
#[serde(default, skip_serializing)]
#[cfg_attr(feature = "schemars", schemars(skip))]
r#package: serde::de::IgnoredAny,
}
impl Options {

View file

@ -33,6 +33,10 @@ pub struct PyProjectToml {
/// The raw unserialized document.
#[serde(skip)]
pub raw: String,
/// Used to determine whether a `build-system` is present.
#[serde(default, skip_serializing)]
build_system: Option<serde::de::IgnoredAny>,
}
impl PyProjectToml {
@ -41,6 +45,23 @@ impl PyProjectToml {
let pyproject = toml::from_str(&raw)?;
Ok(PyProjectToml { raw, ..pyproject })
}
/// Returns `true` if the project should be considered a Python package, as opposed to a
/// non-package ("virtual") project.
pub fn is_package(&self) -> bool {
// If `tool.uv.package` is set, defer to that explicit setting.
if let Some(is_package) = self
.tool
.as_ref()
.and_then(|tool| tool.uv.as_ref())
.and_then(|uv| uv.package)
{
return is_package;
}
// Otherwise, a project is assumed to be a package if `build-system` is present.
self.build_system.is_some()
}
}
// Ignore raw document in comparison.
@ -100,6 +121,24 @@ pub struct ToolUv {
"#
)]
pub managed: Option<bool>,
/// Whether the project should be considered a Python package, or a non-package ("virtual")
/// project.
///
/// Packages are built and installed into the virtual environment in editable mode and thus
/// require a build backend, while virtual projects are _not_ built or installed; instead, only
/// their dependencies are included in the virtual environment.
///
/// Creating a package requires that a `build-system` is present in the `pyproject.toml`, and
/// that the project adheres to a structure that adheres to the build backend's expectations
/// (e.g., a `src` layout).
#[option(
default = r#"true"#,
value_type = "bool",
example = r#"
package = false
"#
)]
pub package: Option<bool>,
/// The project's development dependencies. Development dependencies will be installed by
/// default in `uv run` and `uv sync`, but will not appear in the project's published metadata.
#[cfg_attr(

View file

@ -1339,7 +1339,7 @@ impl VirtualProject {
}
}
/// Return the [`PackageName`] of the project, if it's not a virtual workspace.
/// Return the [`PackageName`] of the project, if it's not a virtual workspace root.
pub fn project_name(&self) -> Option<&PackageName> {
match self {
VirtualProject::Project(project) => Some(project.project_name()),
@ -1347,7 +1347,7 @@ impl VirtualProject {
}
}
/// Returns `true` if the project is a virtual workspace.
/// Returns `true` if the project is a virtual workspace root.
pub fn is_virtual(&self) -> bool {
matches!(self, VirtualProject::Virtual(_))
}
@ -1535,6 +1535,7 @@ mod tests {
"exclude": null
},
"managed": null,
"package": null,
"dev-dependencies": null,
"environments": null,
"override-dependencies": null,
@ -1607,6 +1608,7 @@ mod tests {
"exclude": null
},
"managed": null,
"package": null,
"dev-dependencies": null,
"environments": null,
"override-dependencies": null,

View file

@ -15,7 +15,7 @@ use uv_python::{
};
use uv_resolver::RequiresPython;
use uv_workspace::pyproject_mut::{DependencyTarget, PyProjectTomlMut};
use uv_workspace::{check_nested_workspaces, DiscoveryOptions, Workspace, WorkspaceError};
use uv_workspace::{DiscoveryOptions, Workspace, WorkspaceError};
use crate::commands::project::find_requires_python;
use crate::commands::reporters::PythonDownloadReporter;
@ -69,24 +69,21 @@ pub(crate) async fn init(
}
};
if r#virtual {
init_virtual_workspace(&path, no_workspace)?;
} else {
init_project(
&path,
&name,
no_readme,
python,
no_workspace,
python_preference,
python_downloads,
connectivity,
native_tls,
cache,
printer,
)
.await?;
}
init_project(
&path,
&name,
r#virtual,
no_readme,
python,
no_workspace,
python_preference,
python_downloads,
connectivity,
native_tls,
cache,
printer,
)
.await?;
// Create the `README.md` if it does not already exist.
if !no_readme {
@ -126,29 +123,12 @@ pub(crate) async fn init(
Ok(ExitStatus::Success)
}
/// Initialize a virtual workspace at the given path.
fn init_virtual_workspace(path: &Path, no_workspace: bool) -> Result<()> {
// Ensure that we aren't creating a nested workspace.
if !no_workspace {
check_nested_workspaces(path, &DiscoveryOptions::default());
}
// Create the `pyproject.toml`.
let pyproject = indoc::indoc! {r"
[tool.uv.workspace]
members = []
"};
fs_err::create_dir_all(path)?;
fs_err::write(path.join("pyproject.toml"), pyproject)?;
Ok(())
}
/// Initialize a project (and, implicitly, a workspace root) at the given path.
#[allow(clippy::fn_params_excessive_bools)]
async fn init_project(
path: &Path,
name: &PackageName,
r#virtual: bool,
no_readme: bool,
python: Option<String>,
no_workspace: bool,
@ -265,38 +245,56 @@ async fn init_project(
RequiresPython::greater_than_equal_version(&interpreter.python_minor_version())
};
// Create the `pyproject.toml`.
let pyproject = indoc::formatdoc! {r#"
[project]
name = "{name}"
version = "0.1.0"
description = "Add your description here"{readme}
requires-python = "{requires_python}"
dependencies = []
if r#virtual {
// Create the `pyproject.toml`, but omit `[build-system]`.
let pyproject = indoc::formatdoc! {r#"
[project]
name = "{name}"
version = "0.1.0"
description = "Add your description here"{readme}
requires-python = "{requires_python}"
dependencies = []
"#,
readme = if no_readme { "" } else { "\nreadme = \"README.md\"" },
requires_python = requires_python.specifiers(),
};
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
"#,
readme = if no_readme { "" } else { "\nreadme = \"README.md\"" },
requires_python = requires_python.specifiers(),
};
fs_err::create_dir_all(path)?;
fs_err::write(path.join("pyproject.toml"), pyproject)?;
} else {
// Create the `pyproject.toml`.
let pyproject = indoc::formatdoc! {r#"
[project]
name = "{name}"
version = "0.1.0"
description = "Add your description here"{readme}
requires-python = "{requires_python}"
dependencies = []
fs_err::create_dir_all(path)?;
fs_err::write(path.join("pyproject.toml"), pyproject)?;
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
"#,
readme = if no_readme { "" } else { "\nreadme = \"README.md\"" },
requires_python = requires_python.specifiers(),
};
// Create `src/{name}/__init__.py`, if it doesn't exist already.
let src_dir = path.join("src").join(&*name.as_dist_info_name());
let init_py = src_dir.join("__init__.py");
if !init_py.try_exists()? {
fs_err::create_dir_all(&src_dir)?;
fs_err::write(
init_py,
indoc::formatdoc! {r#"
fs_err::create_dir_all(path)?;
fs_err::write(path.join("pyproject.toml"), pyproject)?;
// Create `src/{name}/__init__.py`, if it doesn't exist already.
let src_dir = path.join("src").join(&*name.as_dist_info_name());
let init_py = src_dir.join("__init__.py");
if !init_py.try_exists()? {
fs_err::create_dir_all(&src_dir)?;
fs_err::write(
init_py,
indoc::formatdoc! {r#"
def hello() -> str:
return "Hello from {name}!"
"#},
)?;
)?;
}
}
if let Some(workspace) = workspace {

View file

@ -1,5 +1,8 @@
use anyhow::{Context, Result};
use itertools::Itertools;
use rustc_hash::FxHashSet;
use distribution_types::Name;
use pep508_rs::MarkerTree;
use uv_auth::store_credentials_from_url;
use uv_cache::Cache;
@ -195,6 +198,9 @@ pub(super) async fn do_sync(
// Read the lockfile.
let resolution = lock.to_resolution(project, &markers, tags, extras, &dev)?;
// Always skip virtual projects, which shouldn't be built or installed.
let resolution = apply_no_virtual_project(resolution, project);
// Filter resolution based on install-specific options.
let resolution = install_options.filter_resolution(resolution, project);
@ -289,3 +295,32 @@ pub(super) async fn do_sync(
Ok(())
}
/// Filter out any virtual workspace members.
fn apply_no_virtual_project(
resolution: distribution_types::Resolution,
project: &VirtualProject,
) -> distribution_types::Resolution {
let VirtualProject::Project(project) = project else {
// If the project is _only_ a virtual workspace root, we don't need to filter it out.
return resolution;
};
let virtual_members = project
.workspace()
.packages()
.iter()
.filter_map(|(name, package)| {
// A project is a package if it's explicitly marked as such, _or_ if a build system is
// present.
if package.pyproject_toml().is_package() {
None
} else {
Some(name)
}
})
.collect::<FxHashSet<_>>();
// Remove any virtual members from the resolution.
resolution.filter(|dist| !virtual_members.contains(dist.name()))
}

View file

@ -1110,8 +1110,9 @@ pub fn make_project(dir: &Path, name: &str, body: &str) -> anyhow::Result<()> {
{body}
[build-system]
requires = ["flit_core>=3.8,<4"]
build-backend = "flit_core.buildapi"
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#
};
fs_err::create_dir_all(dir)?;

View file

@ -20,9 +20,12 @@ fn add_registry() -> Result<()> {
[project]
name = "project"
version = "0.1.0"
# ...
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
uv_snapshot!(context.filters(), context.add(&["anyio==3.7.0"]), @r###"
@ -50,11 +53,14 @@ fn add_registry() -> Result<()> {
[project]
name = "project"
version = "0.1.0"
# ...
requires-python = ">=3.12"
dependencies = [
"anyio==3.7.0",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"###
);
});
@ -143,6 +149,10 @@ fn add_git() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["anyio==3.7.0"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
uv_snapshot!(context.filters(), context.lock(), @r###"
@ -212,6 +222,10 @@ fn add_git() -> Result<()> {
"uv-public-pypackage",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv.sources]
uv-public-pypackage = { git = "https://github.com/astral-test/uv-public-pypackage", tag = "0.0.1" }
"###
@ -313,6 +327,10 @@ fn add_git_private_source() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
uv_snapshot!(context.filters(), context.add(&[&format!("uv-private-pypackage @ git+https://{token}@github.com/astral-test/uv-private-pypackage")]), @r###"
@ -343,6 +361,10 @@ fn add_git_private_source() -> Result<()> {
"uv-private-pypackage",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv.sources]
uv-private-pypackage = { git = "https://github.com/astral-test/uv-private-pypackage" }
"###
@ -409,6 +431,10 @@ fn add_git_private_raw() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
uv_snapshot!(context.filters(), context.add(&[&format!("uv-private-pypackage @ git+https://{token}@github.com/astral-test/uv-private-pypackage")]).arg("--raw-sources"), @r###"
@ -443,6 +469,10 @@ fn add_git_private_raw() -> Result<()> {
dependencies = [
"uv-private-pypackage @ git+https://***@github.com/astral-test/uv-private-pypackage",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"###
);
});
@ -504,6 +534,10 @@ fn add_git_error() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
uv_snapshot!(context.filters(), context.lock(), @r###"
@ -562,6 +596,10 @@ fn add_git_raw() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["anyio==3.7.0"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
uv_snapshot!(context.filters(), context.lock(), @r###"
@ -617,6 +655,10 @@ fn add_git_raw() -> Result<()> {
"anyio==3.7.0",
"uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage@0.0.1",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"###
);
});
@ -714,6 +756,10 @@ fn add_git_implicit() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["anyio==3.7.0"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
uv_snapshot!(context.filters(), context.lock(), @r###"
@ -770,6 +816,10 @@ fn add_raw_error() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
// Provide a tag without a Git source.
@ -800,9 +850,12 @@ fn add_unnamed() -> Result<()> {
[project]
name = "project"
version = "0.1.0"
# ...
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
uv_snapshot!(context.filters(), context.add(&["git+https://github.com/astral-test/uv-public-pypackage"]).arg("--tag=0.0.1"), @r###"
@ -828,12 +881,15 @@ fn add_unnamed() -> Result<()> {
[project]
name = "project"
version = "0.1.0"
# ...
requires-python = ">=3.12"
dependencies = [
"uv-public-pypackage",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv.sources]
uv-public-pypackage = { git = "https://github.com/astral-test/uv-public-pypackage", tag = "0.0.1" }
"###
@ -897,6 +953,10 @@ fn add_remove_dev() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
uv_snapshot!(context.filters(), context.add(&["anyio==3.7.0"]).arg("--dev"), @r###"
@ -927,6 +987,10 @@ fn add_remove_dev() -> Result<()> {
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv]
dev-dependencies = [
"anyio==3.7.0",
@ -1049,6 +1113,10 @@ fn add_remove_dev() -> Result<()> {
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv]
dev-dependencies = []
"###
@ -1101,6 +1169,10 @@ fn add_remove_optional() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
uv_snapshot!(context.filters(), context.add(&["anyio==3.7.0"]).arg("--optional=io"), @r###"
@ -1135,6 +1207,10 @@ fn add_remove_optional() -> Result<()> {
io = [
"anyio==3.7.0",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"###
);
});
@ -1254,6 +1330,10 @@ fn add_remove_optional() -> Result<()> {
[project.optional-dependencies]
io = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"###
);
});
@ -1310,6 +1390,10 @@ fn add_remove_workspace() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
let pyproject_toml = context.temp_dir.child("child2/pyproject.toml");
@ -1319,6 +1403,10 @@ fn add_remove_workspace() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
// Adding a workspace package with a mismatched source should error.
@ -1374,6 +1462,10 @@ fn add_remove_workspace() -> Result<()> {
"child2",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv.sources]
child2 = { workspace = true }
"###
@ -1457,6 +1549,10 @@ fn add_remove_workspace() -> Result<()> {
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv.sources]
"###
);
@ -1525,6 +1621,10 @@ fn add_workspace_editable() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
let pyproject_toml = context.temp_dir.child("child2/pyproject.toml");
@ -1534,6 +1634,10 @@ fn add_workspace_editable() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
let child1 = context.temp_dir.join("child1");
@ -1568,6 +1672,10 @@ fn add_workspace_editable() -> Result<()> {
"child2",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv.sources]
child2 = { workspace = true }
"###
@ -1638,6 +1746,10 @@ fn add_path() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
let child = workspace.child("child");
@ -1647,6 +1759,10 @@ fn add_path() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
uv_snapshot!(context.filters(), context.add(&["./child"]).current_dir(workspace.path()), @r###"
@ -1679,6 +1795,10 @@ fn add_path() -> Result<()> {
"child",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv.sources]
child = { path = "child" }
"###
@ -1743,9 +1863,11 @@ fn update() -> Result<()> {
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
"requests==2.31.0"
]
dependencies = ["requests==2.31.0"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
uv_snapshot!(context.filters(), context.lock(), @r###"
@ -1801,6 +1923,10 @@ fn update() -> Result<()> {
dependencies = [
"requests[security]==2.31.0",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"###
);
});
@ -1836,6 +1962,10 @@ fn update() -> Result<()> {
"requests[security]==2.31.0",
"requests[socks,use-chardet-on-py3]>=2.31.0 ; python_full_version >= '3.8'",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"###
);
});
@ -1873,6 +2003,10 @@ fn update() -> Result<()> {
"requests[socks,use-chardet-on-py3]>=2.31.0 ; python_full_version >= '3.8'",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv.sources]
requests = { git = "https://github.com/psf/requests", tag = "v2.32.3" }
"###
@ -2021,9 +2155,11 @@ fn add_update_marker() -> Result<()> {
name = "project"
version = "0.1.0"
requires-python = ">=3.8"
dependencies = [
"requests>=2.30; python_version >= '3.11'"
]
dependencies = ["requests>=2.30; python_version >= '3.11'"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
uv_snapshot!(context.filters(), context.lock(), @r###"
success: true
@ -2080,6 +2216,10 @@ fn add_update_marker() -> Result<()> {
"requests>=2.30; python_version >= '3.11'",
"requests>=2.0,<2.29 ; python_full_version < '3.11'",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"###
);
});
@ -2114,6 +2254,10 @@ fn add_update_marker() -> Result<()> {
"requests>=2.30; python_version >= '3.11'",
"requests>=2.0,<2.20 ; python_full_version < '3.11'",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"###
);
});
@ -2153,6 +2297,10 @@ fn add_update_marker() -> Result<()> {
"requests>=2.0,<2.20 ; python_full_version < '3.11'",
"requests>=2.31 ; python_full_version >= '3.12' and sys_platform == 'win32'",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"###
);
});
@ -2190,6 +2338,10 @@ fn add_update_marker() -> Result<()> {
"requests>=2.31 ; python_full_version >= '3.12' and sys_platform == 'win32'",
"requests>=2.10 ; sys_platform == 'win32'",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"###
);
});
@ -2226,6 +2378,10 @@ fn add_update_marker() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.8"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"###
);
});
@ -2247,6 +2403,10 @@ fn update_source_replace_url() -> Result<()> {
dependencies = [
"requests[security] @ https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl"
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
// Change the source. The existing URL should be removed.
@ -2282,6 +2442,10 @@ fn update_source_replace_url() -> Result<()> {
"requests[security]",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv.sources]
requests = { git = "https://github.com/psf/requests", tag = "v2.32.3" }
"###
@ -2302,9 +2466,11 @@ fn add_inexact() -> Result<()> {
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
"anyio == 3.7.0",
]
dependencies = ["anyio == 3.7.0"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
uv_snapshot!(context.filters(), context.lock(), @r###"
@ -2337,6 +2503,10 @@ fn add_inexact() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
uv_snapshot!(context.filters(), context.add(&["iniconfig==2.0.0"]), @r###"
@ -2367,6 +2537,10 @@ fn add_inexact() -> Result<()> {
dependencies = [
"iniconfig==2.0.0",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"###
);
});
@ -2445,6 +2619,10 @@ fn remove_registry() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["anyio==3.7.0"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
uv_snapshot!(context.filters(), context.lock(), @r###"
@ -2498,6 +2676,10 @@ fn remove_registry() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"###
);
});
@ -2545,11 +2727,12 @@ fn add_preserves_indentation_in_pyproject_toml() -> Result<()> {
[project]
name = "project"
version = "0.1.0"
# ...
requires-python = ">=3.12"
dependencies = [
"anyio==3.7.0"
]
dependencies = ["anyio==3.7.0"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
uv_snapshot!(context.filters(), context.add(&["requests==2.31.0"]), @r###"
@ -2581,12 +2764,15 @@ fn add_preserves_indentation_in_pyproject_toml() -> Result<()> {
[project]
name = "project"
version = "0.1.0"
# ...
requires-python = ">=3.12"
dependencies = [
"anyio==3.7.0",
"requests==2.31.0",
"anyio==3.7.0",
"requests==2.31.0",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"###
);
});
@ -2602,9 +2788,12 @@ fn add_puts_default_indentation_in_pyproject_toml_if_not_observed() -> Result<()
[project]
name = "project"
version = "0.1.0"
# ...
requires-python = ">=3.12"
dependencies = ["anyio==3.7.0"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
uv_snapshot!(context.filters(), context.add(&["requests==2.31.0"]), @r###"
@ -2636,12 +2825,15 @@ fn add_puts_default_indentation_in_pyproject_toml_if_not_observed() -> Result<()
[project]
name = "project"
version = "0.1.0"
# ...
requires-python = ">=3.12"
dependencies = [
"anyio==3.7.0",
"requests==2.31.0",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"###
);
});
@ -2658,9 +2850,12 @@ fn add_frozen() -> Result<()> {
[project]
name = "project"
version = "0.1.0"
# ...
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
uv_snapshot!(context.filters(), context.add(&["anyio==3.7.0"]).arg("--frozen"), @r###"
@ -2681,11 +2876,14 @@ fn add_frozen() -> Result<()> {
[project]
name = "project"
version = "0.1.0"
# ...
requires-python = ">=3.12"
dependencies = [
"anyio==3.7.0",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"###
);
});
@ -2705,9 +2903,12 @@ fn add_no_sync() -> Result<()> {
[project]
name = "project"
version = "0.1.0"
# ...
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
uv_snapshot!(context.filters(), context.add(&["anyio==3.7.0"]).arg("--no-sync"), @r###"
@ -2729,11 +2930,14 @@ fn add_no_sync() -> Result<()> {
[project]
name = "project"
version = "0.1.0"
# ...
requires-python = ">=3.12"
dependencies = [
"anyio==3.7.0",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"###
);
});
@ -2821,9 +3025,12 @@ fn add_error() -> Result<()> {
[project]
name = "project"
version = "0.1.0"
# ...
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
uv_snapshot!(context.filters(), context.add(&["xyz"]), @r###"
@ -2863,6 +3070,10 @@ fn add_lower_bound() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
// Adding `anyio` should include a lower-bound.
@ -2895,6 +3106,10 @@ fn add_lower_bound() -> Result<()> {
dependencies = [
"anyio>=4.3.0",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"###
);
});
@ -2914,6 +3129,10 @@ fn add_lower_bound_existing() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["anyio"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
// Adding `anyio` should _not_ set a lower-bound, since it's already present (even if
@ -2947,6 +3166,10 @@ fn add_lower_bound_existing() -> Result<()> {
dependencies = [
"anyio",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"###
);
});
@ -2966,6 +3189,10 @@ fn add_lower_bound_raw() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["anyio"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
// Adding `anyio` should _not_ set a lower-bound when using `--raw-sources`.
@ -2998,6 +3225,10 @@ fn add_lower_bound_raw() -> Result<()> {
dependencies = [
"anyio",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"###
);
});
@ -3017,6 +3248,10 @@ fn add_lower_bound_dev() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
// Adding `anyio` should include a lower-bound.
@ -3048,6 +3283,10 @@ fn add_lower_bound_dev() -> Result<()> {
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv]
dev-dependencies = [
"anyio>=4.3.0",
@ -3071,6 +3310,10 @@ fn add_lower_bound_optional() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
// Adding `anyio` should include a lower-bound.
@ -3106,6 +3349,10 @@ fn add_lower_bound_optional() -> Result<()> {
io = [
"anyio>=4.3.0",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"###
);
});
@ -3185,6 +3432,10 @@ fn add_lower_bound_local() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
// Adding `torch` should include a lower-bound, but no local segment.
@ -3215,6 +3466,10 @@ fn add_lower_bound_local() -> Result<()> {
dependencies = [
"local-simple-a>=1.2.3",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"###
);
});
@ -3359,9 +3614,12 @@ fn add_repeat() -> Result<()> {
[project]
name = "project"
version = "0.1.0"
# ...
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
uv_snapshot!(context.filters(), context.add(&["anyio"]), @r###"
@ -3389,11 +3647,14 @@ fn add_repeat() -> Result<()> {
[project]
name = "project"
version = "0.1.0"
# ...
requires-python = ">=3.12"
dependencies = [
"anyio>=4.3.0",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"###
);
});
@ -3418,11 +3679,14 @@ fn add_repeat() -> Result<()> {
[project]
name = "project"
version = "0.1.0"
# ...
requires-python = ">=3.12"
dependencies = [
"anyio>=4.3.0",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"###
);
});
@ -3440,9 +3704,12 @@ fn add_requirements_file() -> Result<()> {
[project]
name = "project"
version = "0.1.0"
# ...
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
let requirements_txt = context.temp_dir.child("requirements.txt");
@ -3481,13 +3748,16 @@ fn add_requirements_file() -> Result<()> {
[project]
name = "project"
version = "0.1.0"
# ...
requires-python = ">=3.12"
dependencies = [
"flask==2.3.2",
"anyio",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv.sources]
anyio = { git = "https://github.com/agronholm/anyio.git", rev = "4.4.0" }
"###
@ -3985,6 +4255,10 @@ fn fail_to_add_revert_project() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
// Adding `pytorch==1.0.2` should produce an error
@ -4032,6 +4306,10 @@ fn fail_to_add_revert_project() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"###
);
});

View file

@ -882,9 +882,9 @@ fn init_explicit_workspace() -> Result<()> {
Ok(())
}
/// Run `uv init` from within a virtual workspace.
/// Run `uv init --virtual` to create a virtual project.
#[test]
fn init_virtual_workspace() -> Result<()> {
fn init_virtual_project() -> Result<()> {
let context = TestContext::new("3.12");
let child = context.temp_dir.child("foo");
@ -907,8 +907,13 @@ fn init_virtual_workspace() -> Result<()> {
}, {
assert_snapshot!(
pyproject, @r###"
[tool.uv.workspace]
members = []
[project]
name = "foo"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []
"###
);
});
@ -923,6 +928,56 @@ fn init_virtual_workspace() -> Result<()> {
Initialized project `bar` at `[TEMP_DIR]/foo/bar`
"###);
let pyproject = fs_err::read_to_string(pyproject_toml)?;
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
pyproject, @r###"
[project]
name = "foo"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []
[tool.uv.workspace]
members = ["bar"]
"###
);
});
Ok(())
}
/// Run `uv init` from within a virtual workspace.
#[test]
fn init_virtual_workspace() -> Result<()> {
let context = TestContext::new("3.12");
let child = context.temp_dir.child("foo");
child.create_dir_all()?;
// Create a virtual workspace.
let pyproject_toml = child.child("pyproject.toml");
pyproject_toml.write_str(indoc! {
r"
[tool.uv.workspace]
members = []
",
})?;
uv_snapshot!(context.filters(), context.init().current_dir(&child).arg("bar"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Adding `bar` as member of workspace `[TEMP_DIR]/foo`
Initialized project `bar` at `[TEMP_DIR]/foo/bar`
"###);
let pyproject = fs_err::read_to_string(pyproject_toml)?;
insta::with_settings!({
filters => context.filters(),
@ -957,7 +1012,7 @@ fn init_nested_virtual_workspace() -> Result<()> {
----- stdout -----
----- stderr -----
warning: Nested workspaces are not supported, but outer workspace (`[TEMP_DIR]/`) includes `[TEMP_DIR]/foo`
Adding `foo` as member of workspace `[TEMP_DIR]/`
Initialized workspace `foo` at `[TEMP_DIR]/foo`
"###);
@ -967,8 +1022,13 @@ fn init_nested_virtual_workspace() -> Result<()> {
}, {
assert_snapshot!(
pyproject, @r###"
[tool.uv.workspace]
members = []
[project]
name = "foo"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []
"###
);
});
@ -980,7 +1040,7 @@ fn init_nested_virtual_workspace() -> Result<()> {
assert_snapshot!(
workspace, @r###"
[tool.uv.workspace]
members = []
members = ["foo"]
"###
);
});

File diff suppressed because it is too large Load diff

View file

@ -27,6 +27,10 @@ fn run_with_python_version() -> Result<()> {
"anyio==3.6.0 ; python_version == '3.11'",
"anyio==3.7.0 ; python_version == '3.12'",
]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#
})?;
let test_script = context.temp_dir.child("main.py");
@ -146,6 +150,10 @@ fn run_args() -> Result<()> {
version = "1.0.0"
requires-python = ">=3.8"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#
})?;
@ -201,6 +209,10 @@ fn run_pep723_script() -> Result<()> {
version = "1.0.0"
requires-python = ">=3.8"
dependencies = ["anyio"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#
})?;
@ -363,6 +375,10 @@ fn run_pythonw_script() -> Result<()> {
version = "1.0.0"
requires-python = ">=3.8"
dependencies = ["anyio"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#
})?;
@ -473,6 +489,10 @@ fn run_managed_false() -> Result<()> {
requires-python = ">=3.8"
dependencies = ["anyio"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv]
managed = false
"#
@ -501,6 +521,10 @@ fn run_with() -> Result<()> {
version = "1.0.0"
requires-python = ">=3.8"
dependencies = ["anyio", "sniffio==1.3.1"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#
})?;
@ -597,6 +621,10 @@ fn run_with_editable() -> Result<()> {
version = "1.0.0"
requires-python = ">=3.8"
dependencies = ["anyio", "sniffio==1.3.1"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#
})?;
@ -660,6 +688,10 @@ fn run_with_editable() -> Result<()> {
requires-python = ">=3.8"
dependencies = ["anyio", "sniffio==1.3.1"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv.sources]
anyio = { path = "./src/anyio_local", editable = true }
"#
@ -719,6 +751,10 @@ fn run_locked() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["anyio==3.7.0"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#,
)?;
@ -745,6 +781,10 @@ fn run_locked() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["iniconfig"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#,
)?;
@ -797,6 +837,10 @@ fn run_frozen() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["anyio==3.7.0"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#,
)?;
@ -820,6 +864,10 @@ fn run_frozen() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["iniconfig"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#,
)?;
@ -853,6 +901,10 @@ fn run_empty_requirements_txt() -> Result<()> {
version = "1.0.0"
requires-python = ">=3.8"
dependencies = ["anyio", "sniffio==1.3.1"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#
})?;
@ -909,6 +961,10 @@ fn run_requirements_txt() -> Result<()> {
version = "1.0.0"
requires-python = ">=3.8"
dependencies = ["anyio", "sniffio==1.3.1"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#
})?;
@ -1024,6 +1080,10 @@ fn run_requirements_txt_arguments() -> Result<()> {
version = "1.0.0"
requires-python = ">=3.8"
dependencies = ["typing_extensions"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#
})?;
@ -1069,15 +1129,15 @@ fn run_editable() -> Result<()> {
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(indoc! { r#"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "foo"
version = "1.0.0"
requires-python = ">=3.8"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#
})?;
@ -1136,6 +1196,10 @@ fn run_from_directory() -> Result<()> {
[project.scripts]
main = "main:main"
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#
})?;
let main_script = project_dir.child("main.py");
@ -1180,6 +1244,10 @@ fn run_without_output() -> Result<()> {
version = "1.0.0"
requires-python = ">=3.8"
dependencies = ["anyio", "sniffio==1.3.1"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#
})?;
@ -1219,15 +1287,15 @@ fn run_isolated_python_version() -> Result<()> {
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(indoc! { r#"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "foo"
version = "1.0.0"
requires-python = ">=3.8"
dependencies = ["anyio"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#
})?;
@ -1318,15 +1386,15 @@ fn run_no_project() -> Result<()> {
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(indoc! { r#"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "foo"
version = "1.0.0"
requires-python = ">=3.8"
dependencies = ["anyio"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#
})?;

View file

@ -21,6 +21,10 @@ fn sync() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["iniconfig"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#,
)?;
@ -55,6 +59,10 @@ fn locked() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["anyio==3.7.0"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#,
)?;
@ -81,6 +89,10 @@ fn locked() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["iniconfig"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#,
)?;
@ -115,6 +127,10 @@ fn frozen() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["anyio==3.7.0"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#,
)?;
@ -138,6 +154,10 @@ fn frozen() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["iniconfig"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#,
)?;
@ -212,6 +232,10 @@ fn package() -> Result<()> {
requires-python = ">=3.12"
dependencies = ["child", "anyio>3"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv.sources]
child = { workspace = true }
@ -237,6 +261,10 @@ fn package() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["iniconfig>1"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#,
)?;
@ -312,6 +340,10 @@ fn mixed_requires_python() -> Result<()> {
name = "bird-feeder"
version = "0.1.0"
requires-python = ">=3.8"
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#,
)?;
@ -381,6 +413,10 @@ fn virtual_workspace_dev_dependencies() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["iniconfig>1"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#,
)?;
@ -441,6 +477,10 @@ fn sync_build_isolation() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["source-distribution @ https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#,
)?;
@ -877,6 +917,10 @@ fn sync_environment() -> Result<()> {
requires-python = ">=3.10"
dependencies = ["iniconfig"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv]
environments = ["python_version < '3.11'"]
"#,
@ -916,6 +960,10 @@ fn read_metadata_statically_over_the_cache() -> Result<()> {
requires-python = ">=3.12"
# Python string sorting is the other way round.
dependencies = ["anyio>=4,<5"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#,
)?;
@ -945,6 +993,10 @@ fn no_install_project() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["anyio==3.7.0"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#,
)?;
@ -996,6 +1048,10 @@ fn no_install_workspace() -> Result<()> {
requires-python = ">=3.12"
dependencies = ["anyio==3.7.0", "child"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv.workspace]
members = ["child"]
@ -1015,8 +1071,8 @@ fn no_install_workspace() -> Result<()> {
dependencies = ["iniconfig>1"]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#,
)?;
child
@ -1073,6 +1129,10 @@ fn no_install_package() -> Result<()> {
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["anyio==3.7.0"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#,
)?;

View file

@ -965,6 +965,10 @@ fn workspace_inherit_sources() -> Result<()> {
dependencies = []
requires-python = ">=3.12"
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv.workspace]
members = ["packages/*"]
"#})?;
@ -977,6 +981,10 @@ fn workspace_inherit_sources() -> Result<()> {
name = "leaf"
version = "0.1.0"
dependencies = ["library"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
leaf.child("src/__init__.py").touch()?;
@ -987,6 +995,10 @@ fn workspace_inherit_sources() -> Result<()> {
name = "library"
version = "0.1.0"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
library.child("src/__init__.py").touch()?;
@ -1013,6 +1025,10 @@ fn workspace_inherit_sources() -> Result<()> {
version = "0.1.0"
dependencies = ["library"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv.sources]
library = { path = "../../../library", editable = true }
"#})?;
@ -1036,6 +1052,10 @@ fn workspace_inherit_sources() -> Result<()> {
name = "leaf"
version = "0.1.0"
dependencies = ["library"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
// Update the root to include the source.
@ -1046,6 +1066,10 @@ fn workspace_inherit_sources() -> Result<()> {
dependencies = []
requires-python = ">=3.12"
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv.sources]
library = { path = "../library", editable = true }
@ -1117,6 +1141,10 @@ fn workspace_inherit_sources() -> Result<()> {
dependencies = []
requires-python = ">=3.12"
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv.sources]
library = { path = "../library", editable = true }
@ -1131,9 +1159,12 @@ fn workspace_inherit_sources() -> Result<()> {
version = "0.1.0"
dependencies = ["library"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv.sources]
application = { path = "../application", editable = true }
"#})?;
// Resolving should succeed; the member should still use the root's source, despite defining
@ -1167,6 +1198,10 @@ fn workspace_unsatisfiable_member_dependencies() -> Result<()> {
dependencies = []
requires-python = ">=3.12"
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv.workspace]
members = ["packages/*"]
"#})?;
@ -1179,6 +1214,10 @@ fn workspace_unsatisfiable_member_dependencies() -> Result<()> {
name = "leaf"
version = "0.1.0"
dependencies = ["httpx>9999"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
leaf.child("src/__init__.py").touch()?;
@ -1215,6 +1254,10 @@ fn workspace_unsatisfiable_member_dependencies_conflicting() -> Result<()> {
dependencies = []
requires-python = ">=3.12"
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv.workspace]
members = ["packages/*"]
"#})?;
@ -1227,6 +1270,10 @@ fn workspace_unsatisfiable_member_dependencies_conflicting() -> Result<()> {
name = "foo"
version = "0.1.0"
dependencies = ["anyio==4.1.0"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
foo.child("src/__init__.py").touch()?;
let bar = workspace.child("packages").child("bar");
@ -1235,6 +1282,10 @@ fn workspace_unsatisfiable_member_dependencies_conflicting() -> Result<()> {
name = "bar"
version = "0.1.0"
dependencies = ["anyio==4.2.0"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
bar.child("src/__init__.py").touch()?;
@ -1271,6 +1322,10 @@ fn workspace_unsatisfiable_member_dependencies_conflicting_threeway() -> Result<
dependencies = []
requires-python = ">=3.12"
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv.workspace]
members = ["packages/*"]
"#})?;
@ -1283,6 +1338,10 @@ fn workspace_unsatisfiable_member_dependencies_conflicting_threeway() -> Result<
name = "red"
version = "0.1.0"
dependencies = ["anyio==4.1.0"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
red.child("src/__init__.py").touch()?;
let knot = workspace.child("packages").child("knot");
@ -1291,6 +1350,10 @@ fn workspace_unsatisfiable_member_dependencies_conflicting_threeway() -> Result<
name = "knot"
version = "0.1.0"
dependencies = ["anyio==4.2.0"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
knot.child("src/__init__.py").touch()?;
@ -1302,6 +1365,10 @@ fn workspace_unsatisfiable_member_dependencies_conflicting_threeway() -> Result<
name = "bird"
version = "0.1.0"
dependencies = ["anyio==4.3.0"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
bird.child("src/__init__.py").touch()?;
@ -1338,6 +1405,10 @@ fn workspace_unsatisfiable_member_dependencies_conflicting_extra() -> Result<()>
dependencies = []
requires-python = ">=3.12"
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv.workspace]
members = ["packages/*"]
"#})?;
@ -1350,6 +1421,10 @@ fn workspace_unsatisfiable_member_dependencies_conflicting_extra() -> Result<()>
name = "foo"
version = "0.1.0"
dependencies = ["anyio==4.1.0"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
foo.child("src/__init__.py").touch()?;
let bar = workspace.child("packages").child("bar");
@ -1360,6 +1435,10 @@ fn workspace_unsatisfiable_member_dependencies_conflicting_extra() -> Result<()>
[project.optional-dependencies]
some_extra = ["anyio==4.2.0"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
bar.child("src/__init__.py").touch()?;
@ -1396,6 +1475,10 @@ fn workspace_unsatisfiable_member_dependencies_conflicting_dev() -> Result<()> {
dependencies = []
requires-python = ">=3.12"
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv.workspace]
members = ["packages/*"]
"#})?;
@ -1408,6 +1491,10 @@ fn workspace_unsatisfiable_member_dependencies_conflicting_dev() -> Result<()> {
name = "foo"
version = "0.1.0"
dependencies = ["anyio==4.1.0"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
foo.child("src/__init__.py").touch()?;
let bar = workspace.child("packages").child("bar");
@ -1416,6 +1503,10 @@ fn workspace_unsatisfiable_member_dependencies_conflicting_dev() -> Result<()> {
name = "bar"
version = "0.1.0"
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv]
dev-dependencies = ["anyio==4.2.0"]
"#})?;
@ -1455,6 +1546,10 @@ fn workspace_member_name_shadows_dependencies() -> Result<()> {
dependencies = []
requires-python = ">=3.12"
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv.workspace]
members = ["packages/*"]
"#})?;
@ -1467,6 +1562,10 @@ fn workspace_member_name_shadows_dependencies() -> Result<()> {
name = "foo"
version = "0.1.0"
dependencies = ["anyio==4.1.0"]
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
foo.child("src/__init__.py").touch()?;
@ -1477,6 +1576,10 @@ fn workspace_member_name_shadows_dependencies() -> Result<()> {
name = "anyio"
version = "0.1.0"
dependencies = []
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
"#})?;
anyio.child("src/__init__.py").touch()?;

View file

@ -455,9 +455,11 @@ uv init [OPTIONS] [PATH]
</dd><dt><code>--version</code>, <code>-V</code></dt><dd><p>Display the uv version</p>
</dd><dt><code>--virtual</code></dt><dd><p>Create a virtual workspace instead of a project.</p>
</dd><dt><code>--virtual</code></dt><dd><p>Create a virtual project, rather than a package.</p>
<p>A virtual workspace does not define project dependencies and cannot be published. Instead, workspace members declare project dependencies. Development dependencies may still be declared.</p>
<p>A virtual project is a project that is not intended to be built as a Python package, such as a project that only contains scripts or other application code.</p>
<p>Virtual projects themselves are not installed into the Python environment.</p>
</dd></dl>

View file

@ -890,6 +890,40 @@ requirements of any constituent packages.
---
#### [`package`](#package) {: #package }
Whether the project should be considered a Python package, or a non-package ("virtual")
project.
Packages are built and installed into the virtual environment in editable mode and thus
require a build backend, while virtual projects are _not_ built or installed; instead, only
their dependencies are included in the virtual environment.
Creating a package requires that a `build-system` is present in the `pyproject.toml`, and
that the project adheres to a structure that adheres to the build backend's expectations
(e.g., a `src` layout).
**Default value**: `true`
**Type**: `bool`
**Example usage**:
=== "pyproject.toml"
```toml
[tool.uv]
package = false
```
=== "uv.toml"
```toml
package = false
```
---
#### [`prerelease`](#prerelease) {: #prerelease }
The strategy to use when considering pre-release versions.

7
uv.schema.json generated
View file

@ -274,6 +274,13 @@
"type": "string"
}
},
"package": {
"description": "Whether the project should be considered a Python package, or a non-package (\"virtual\") project.\n\nPackages are built and installed into the virtual environment in editable mode and thus require a build backend, while virtual projects are _not_ built or installed; instead, only their dependencies are included in the virtual environment.\n\nCreating a package requires that a `build-system` is present in the `pyproject.toml`, and that the project adheres to a structure that adheres to the build backend's expectations (e.g., a `src` layout).",
"type": [
"boolean",
"null"
]
},
"pip": {
"anyOf": [
{