diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 2aae67a06aea..87dd97d76b28 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -1843,6 +1843,11 @@ pub struct InitArgs { #[arg(long)] pub no_readme: bool, + /// Avoid discovering the workspace in the current directory or any parent directory. Instead, + /// create a standalone project. + #[arg(long)] + pub no_workspace: bool, + /// The Python interpreter to use to determine the minimum supported Python version. /// /// By default, uv uses the virtual environment in the current working directory or any parent @@ -1916,9 +1921,14 @@ pub struct RunArgs { pub refresh: RefreshArgs, /// Run the command in a specific package in the workspace. - #[arg(long, conflicts_with = "isolated")] + #[arg(long)] pub package: Option, + /// Avoid discovering the project or workspace in the current directory or any parent directory. + /// Instead, run in an isolated, ephemeral environment populated by the `--with` requirements. + #[arg(long, conflicts_with = "package", alias = "no_project")] + pub no_workspace: bool, + /// The Python interpreter to use to build the run environment. /// /// By default, uv uses the virtual environment in the current working directory or any parent diff --git a/crates/uv/src/commands/project/init.rs b/crates/uv/src/commands/project/init.rs index 87d612377dbc..4e975ed39f1f 100644 --- a/crates/uv/src/commands/project/init.rs +++ b/crates/uv/src/commands/project/init.rs @@ -31,7 +31,7 @@ pub(crate) async fn init( r#virtual: bool, no_readme: bool, python: Option, - isolated: bool, + no_workspace: bool, preview: PreviewMode, python_preference: PythonPreference, python_fetch: PythonFetch, @@ -76,14 +76,14 @@ pub(crate) async fn init( }; if r#virtual { - init_virtual_workspace(&path, isolated)?; + init_virtual_workspace(&path, no_workspace)?; } else { init_project( &path, &name, no_readme, python, - isolated, + no_workspace, python_preference, python_fetch, connectivity, @@ -133,9 +133,9 @@ pub(crate) async fn init( } /// Initialize a virtual workspace at the given path. -fn init_virtual_workspace(path: &Path, isolated: bool) -> Result<()> { +fn init_virtual_workspace(path: &Path, no_workspace: bool) -> Result<()> { // Ensure that we aren't creating a nested workspace. - if !isolated { + if !no_workspace { check_nested_workspaces(path, &DiscoveryOptions::default()); } @@ -157,7 +157,7 @@ async fn init_project( name: &PackageName, no_readme: bool, python: Option, - isolated: bool, + no_workspace: bool, python_preference: PythonPreference, python_fetch: PythonFetch, connectivity: Connectivity, @@ -166,7 +166,7 @@ async fn init_project( printer: Printer, ) -> Result<()> { // Discover the current workspace, if it exists. - let workspace = if isolated { + let workspace = if no_workspace { None } else { // Attempt to find a workspace root. diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index 92b9bf4b811a..6cd8d0a4882b 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -45,7 +45,7 @@ pub(crate) async fn run( dev: bool, python: Option, settings: ResolverInstallerSettings, - isolated: bool, + no_workspace: bool, preview: PreviewMode, python_preference: PythonPreference, python_fetch: PythonFetch, @@ -159,8 +159,8 @@ pub(crate) async fn run( // Discover and sync the base environment. let base_interpreter = if let Some(script_interpreter) = script_interpreter { Some(script_interpreter) - } else if isolated { - // package is `None`, isolated and package are marked as conflicting in clap. + } else if no_workspace { + // package is `None` (`no_workspace` and `package` are marked as conflicting in Clap). None } else { let project = if let Some(package) = package { diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index bd1e0eb1d524..cc3f65898df9 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -875,7 +875,7 @@ async fn run_project( args.r#virtual, args.no_readme, args.python, - globals.isolated, + args.no_workspace || globals.isolated, globals.preview, globals.python_preference, globals.python_fetch, @@ -917,7 +917,7 @@ async fn run_project( args.dev, args.python, args.settings, - globals.isolated, + args.no_workspace || globals.isolated, globals.preview, globals.python_preference, globals.python_fetch, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 4861af55fcba..4fcc19b28931 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -155,6 +155,7 @@ pub(crate) struct InitSettings { pub(crate) name: Option, pub(crate) r#virtual: bool, pub(crate) no_readme: bool, + pub(crate) no_workspace: bool, pub(crate) python: Option, } @@ -167,6 +168,7 @@ impl InitSettings { name, r#virtual, no_readme, + no_workspace, python, } = args; @@ -175,6 +177,7 @@ impl InitSettings { name, r#virtual, no_readme, + no_workspace, python, } } @@ -192,6 +195,7 @@ pub(crate) struct RunSettings { pub(crate) with: Vec, pub(crate) with_requirements: Vec, pub(crate) package: Option, + pub(crate) no_workspace: bool, pub(crate) python: Option, pub(crate) refresh: Refresh, pub(crate) settings: ResolverInstallerSettings, @@ -216,6 +220,7 @@ impl RunSettings { build, refresh, package, + no_workspace, python, } = args; @@ -234,6 +239,7 @@ impl RunSettings { .filter_map(Maybe::into_option) .collect(), package, + no_workspace, python, refresh: Refresh::from(refresh), settings: ResolverInstallerSettings::combine( diff --git a/crates/uv/tests/init.rs b/crates/uv/tests/init.rs index c60fc87f7621..55ebd47308bc 100644 --- a/crates/uv/tests/init.rs +++ b/crates/uv/tests/init.rs @@ -558,7 +558,7 @@ fn init_invalid_names() -> Result<()> { } #[test] -fn init_workspace_isolated() -> Result<()> { +fn init_isolated() -> Result<()> { let context = TestContext::new("3.12"); let pyproject_toml = context.temp_dir.child("pyproject.toml"); @@ -602,6 +602,51 @@ fn init_workspace_isolated() -> Result<()> { Ok(()) } +#[test] +fn init_no_workspace() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! { + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + "#, + })?; + + let child = context.temp_dir.join("foo"); + fs_err::create_dir(&child)?; + + uv_snapshot!(context.filters(), context.init().current_dir(&child).arg("--no-workspace"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: `uv init` is experimental and may change without warning + Initialized project `foo` + "###); + + let workspace = fs_err::read_to_string(context.temp_dir.join("pyproject.toml"))?; + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + workspace, @r###" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + "### + ); + }); + + Ok(()) +} + #[test] fn init_project_inside_project() -> Result<()> { let context = TestContext::new("3.12");