mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-09 18:12:07 +00:00
Add --app
and --lib
options to uv init
(#6689)
Changes the `uv init` experience with a focus on working for more use-cases out of the box. - Adds `--app` and `--lib` options to control the created project style - Changes the default from a library with `src/` and a build backend (`--lib`) to an application that is not packaged (`--app`) - Hides the `--virtual` option and replaces it with `--package` and `--no-package` - `--no-package` is not allowed with `--lib` right now, but it could be in the future once we understand a use-case - Creates a runnable project - Applications have a `hello.py` file which you can run with `uv run hello.py` - Packaged applications, e.g., `uv init --app --package` create a package and script entrypoint, which you can run with `uv run hello` - Libraries provide a demo API function, e.g., `uv run python -c "import name; print(name.hello())"` — this is unchanged Closes #6471
This commit is contained in:
parent
8d466db080
commit
bc5b069a61
7 changed files with 726 additions and 120 deletions
|
@ -2101,13 +2101,47 @@ pub struct InitArgs {
|
|||
|
||||
/// Create a virtual project, rather than a package.
|
||||
///
|
||||
/// 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)]
|
||||
/// This option is deprecated and will be removed in a future release.
|
||||
#[arg(long, hide = true, conflicts_with = "package")]
|
||||
pub r#virtual: bool,
|
||||
|
||||
/// Set up the project to be built as a Python package.
|
||||
///
|
||||
/// Defines a `[build-system]` for the project.
|
||||
///
|
||||
/// This is the default behavior when using `--lib`.
|
||||
///
|
||||
/// When using `--app`, this will include a `[project.scripts]` entrypoint and use a `src/`
|
||||
/// project structure.
|
||||
#[arg(long, overrides_with = "no_package")]
|
||||
pub r#package: bool,
|
||||
|
||||
/// Do not set up the project to be built as a Python package.
|
||||
///
|
||||
/// Does not include a `[build-system]` for the project.
|
||||
///
|
||||
/// This is the default behavior when using `--app`.
|
||||
#[arg(long, overrides_with = "package", conflicts_with = "lib")]
|
||||
pub r#no_package: bool,
|
||||
|
||||
/// Create a project for an application.
|
||||
///
|
||||
/// This is the default behavior if `--lib` is not requested.
|
||||
///
|
||||
/// This project kind is for web servers, scripts, and command-line interfaces.
|
||||
///
|
||||
/// By default, an application is not intended to be built and distributed as a Python package.
|
||||
/// The `--package` option can be used to create an application that is distributable, e.g., if
|
||||
/// you want to distribute a command-line interface via PyPI.
|
||||
#[arg(long, alias = "application", conflicts_with = "lib")]
|
||||
pub r#app: bool,
|
||||
|
||||
/// Create a project for a library.
|
||||
///
|
||||
/// A library is a project that is intended to be built and distributed as a Python package.
|
||||
#[arg(long, alias = "library", conflicts_with = "app")]
|
||||
pub r#lib: bool,
|
||||
|
||||
/// Do not create a `README.md` file.
|
||||
#[arg(long)]
|
||||
pub no_readme: bool,
|
||||
|
|
|
@ -19,7 +19,7 @@ pub(crate) use pip::sync::pip_sync;
|
|||
pub(crate) use pip::tree::pip_tree;
|
||||
pub(crate) use pip::uninstall::pip_uninstall;
|
||||
pub(crate) use project::add::add;
|
||||
pub(crate) use project::init::init;
|
||||
pub(crate) use project::init::{init, InitProjectKind};
|
||||
pub(crate) use project::lock::lock;
|
||||
pub(crate) use project::remove::remove;
|
||||
pub(crate) use project::run::{run, RunCommand};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::fmt::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use owo_colors::OwoColorize;
|
||||
use pep440_rs::Version;
|
||||
use pep508_rs::PackageName;
|
||||
|
@ -27,7 +27,8 @@ use crate::printer::Printer;
|
|||
pub(crate) async fn init(
|
||||
explicit_path: Option<String>,
|
||||
name: Option<PackageName>,
|
||||
r#virtual: bool,
|
||||
package: bool,
|
||||
project_kind: InitProjectKind,
|
||||
no_readme: bool,
|
||||
python: Option<String>,
|
||||
no_workspace: bool,
|
||||
|
@ -72,7 +73,8 @@ pub(crate) async fn init(
|
|||
init_project(
|
||||
&path,
|
||||
&name,
|
||||
r#virtual,
|
||||
package,
|
||||
project_kind,
|
||||
no_readme,
|
||||
python,
|
||||
no_workspace,
|
||||
|
@ -93,16 +95,10 @@ pub(crate) async fn init(
|
|||
}
|
||||
}
|
||||
|
||||
let project = if r#virtual { "workspace" } else { "project" };
|
||||
match explicit_path {
|
||||
// Initialized a project in the current directory.
|
||||
None => {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"Initialized {} `{}`",
|
||||
project,
|
||||
name.cyan()
|
||||
)?;
|
||||
writeln!(printer.stderr(), "Initialized project `{}`", name.cyan())?;
|
||||
}
|
||||
// Initialized a project in the given directory.
|
||||
Some(path) => {
|
||||
|
@ -112,8 +108,7 @@ pub(crate) async fn init(
|
|||
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"Initialized {} `{}` at `{}`",
|
||||
project,
|
||||
"Initialized project `{}` at `{}`",
|
||||
name.cyan(),
|
||||
path.display().cyan()
|
||||
)?;
|
||||
|
@ -128,7 +123,8 @@ pub(crate) async fn init(
|
|||
async fn init_project(
|
||||
path: &Path,
|
||||
name: &PackageName,
|
||||
r#virtual: bool,
|
||||
package: bool,
|
||||
project_kind: InitProjectKind,
|
||||
no_readme: bool,
|
||||
python: Option<String>,
|
||||
no_workspace: bool,
|
||||
|
@ -245,57 +241,7 @@ async fn init_project(
|
|||
RequiresPython::greater_than_equal_version(&interpreter.python_minor_version())
|
||||
};
|
||||
|
||||
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(),
|
||||
};
|
||||
|
||||
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 = []
|
||||
|
||||
[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)?;
|
||||
|
||||
// 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}!"
|
||||
"#},
|
||||
)?;
|
||||
}
|
||||
}
|
||||
project_kind.init(name, path, &requires_python, no_readme, package)?;
|
||||
|
||||
if let Some(workspace) = workspace {
|
||||
if workspace.excludes(path)? {
|
||||
|
@ -339,3 +285,171 @@ async fn init_project(
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) enum InitProjectKind {
|
||||
#[default]
|
||||
Application,
|
||||
Library,
|
||||
}
|
||||
|
||||
impl InitProjectKind {
|
||||
/// Initialize this project kind at the target path.
|
||||
fn init(
|
||||
&self,
|
||||
name: &PackageName,
|
||||
path: &Path,
|
||||
requires_python: &RequiresPython,
|
||||
no_readme: bool,
|
||||
package: bool,
|
||||
) -> Result<()> {
|
||||
match self {
|
||||
InitProjectKind::Application => {
|
||||
init_application(name, path, requires_python, no_readme, package)
|
||||
}
|
||||
InitProjectKind::Library => {
|
||||
init_library(name, path, requires_python, no_readme, package)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether or not this project kind is packaged by default.
|
||||
pub(crate) fn packaged_by_default(&self) -> bool {
|
||||
matches!(self, InitProjectKind::Library)
|
||||
}
|
||||
}
|
||||
|
||||
fn init_application(
|
||||
name: &PackageName,
|
||||
path: &Path,
|
||||
requires_python: &RequiresPython,
|
||||
no_readme: bool,
|
||||
package: bool,
|
||||
) -> Result<()> {
|
||||
// Create the `pyproject.toml`
|
||||
let mut pyproject = pyproject_project(name, requires_python, no_readme);
|
||||
|
||||
// Include additional project configuration for packaged applications
|
||||
if package {
|
||||
// Since it'll be packaged, we can add a `[project.scripts]` entry
|
||||
pyproject.push('\n');
|
||||
pyproject.push_str(&pyproject_project_scripts(name, "hello", "hello"));
|
||||
|
||||
// Add a build system
|
||||
pyproject.push('\n');
|
||||
pyproject.push_str(pyproject_build_system());
|
||||
}
|
||||
|
||||
fs_err::create_dir_all(path)?;
|
||||
|
||||
// Create the source structure.
|
||||
if package {
|
||||
// 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():
|
||||
print("Hello from {name}!")
|
||||
"#},
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
// Create `hello.py` if it doesn't exist
|
||||
// TODO(zanieb): Only create `hello.py` if there are no other Python files?
|
||||
let hello_py = path.join("hello.py");
|
||||
if !hello_py.try_exists()? {
|
||||
fs_err::write(
|
||||
path.join("hello.py"),
|
||||
indoc::formatdoc! {r#"
|
||||
def main():
|
||||
print("Hello from {name}!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
"#},
|
||||
)?;
|
||||
}
|
||||
}
|
||||
fs_err::write(path.join("pyproject.toml"), pyproject)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_library(
|
||||
name: &PackageName,
|
||||
path: &Path,
|
||||
requires_python: &RequiresPython,
|
||||
no_readme: bool,
|
||||
package: bool,
|
||||
) -> Result<()> {
|
||||
if !package {
|
||||
return Err(anyhow!("Library projects must be packaged"));
|
||||
}
|
||||
|
||||
// Create the `pyproject.toml`
|
||||
let mut pyproject = pyproject_project(name, requires_python, no_readme);
|
||||
|
||||
// Always include a build system if the project is packaged.
|
||||
pyproject.push('\n');
|
||||
pyproject.push_str(pyproject_build_system());
|
||||
|
||||
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}!"
|
||||
"#},
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generate the `[project]` section of a `pyproject.toml`.
|
||||
fn pyproject_project(
|
||||
name: &PackageName,
|
||||
requires_python: &RequiresPython,
|
||||
no_readme: bool,
|
||||
) -> String {
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate the `[build-system]` section of a `pyproject.toml`.
|
||||
fn pyproject_build_system() -> &'static str {
|
||||
indoc::indoc! {r#"
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
"#}
|
||||
}
|
||||
|
||||
/// Generate the `[project.scripts]` section of a `pyproject.toml`.
|
||||
fn pyproject_project_scripts(package: &PackageName, executable_name: &str, target: &str) -> String {
|
||||
let module_name = package.as_dist_info_name();
|
||||
indoc::formatdoc! {r#"
|
||||
[project.scripts]
|
||||
{executable_name} = "{module_name}:{target}"
|
||||
"#}
|
||||
}
|
||||
|
|
|
@ -1025,7 +1025,8 @@ async fn run_project(
|
|||
commands::init(
|
||||
args.path,
|
||||
args.name,
|
||||
args.r#virtual,
|
||||
args.package,
|
||||
args.kind,
|
||||
args.no_readme,
|
||||
args.python,
|
||||
args.no_workspace,
|
||||
|
|
|
@ -35,8 +35,8 @@ use uv_settings::{
|
|||
use uv_warnings::warn_user_once;
|
||||
use uv_workspace::pyproject::DependencyType;
|
||||
|
||||
use crate::commands::pip::operations::Modifications;
|
||||
use crate::commands::ToolRunCommand;
|
||||
use crate::commands::{pip::operations::Modifications, InitProjectKind};
|
||||
|
||||
/// The resolved global settings to use for any invocation of the CLI.
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
|
@ -154,7 +154,8 @@ impl CacheSettings {
|
|||
pub(crate) struct InitSettings {
|
||||
pub(crate) path: Option<String>,
|
||||
pub(crate) name: Option<PackageName>,
|
||||
pub(crate) r#virtual: bool,
|
||||
pub(crate) package: bool,
|
||||
pub(crate) kind: InitProjectKind,
|
||||
pub(crate) no_readme: bool,
|
||||
pub(crate) no_workspace: bool,
|
||||
pub(crate) python: Option<String>,
|
||||
|
@ -168,15 +169,29 @@ impl InitSettings {
|
|||
path,
|
||||
name,
|
||||
r#virtual,
|
||||
package,
|
||||
no_package,
|
||||
app,
|
||||
lib,
|
||||
no_readme,
|
||||
no_workspace,
|
||||
python,
|
||||
} = args;
|
||||
|
||||
let kind = match (app, lib) {
|
||||
(true, false) => InitProjectKind::Application,
|
||||
(false, true) => InitProjectKind::Library,
|
||||
(false, false) => InitProjectKind::default(),
|
||||
(true, true) => unreachable!("`app` and `lib` are mutually exclusive"),
|
||||
};
|
||||
|
||||
let package = flag(package || r#virtual, no_package).unwrap_or(kind.packaged_by_default());
|
||||
|
||||
Self {
|
||||
path,
|
||||
name,
|
||||
r#virtual,
|
||||
package,
|
||||
kind,
|
||||
no_readme,
|
||||
no_workspace,
|
||||
python,
|
||||
|
|
|
@ -9,6 +9,7 @@ use common::{uv_snapshot, TestContext};
|
|||
|
||||
mod common;
|
||||
|
||||
/// See [`init_application`] and [`init_library`] for more coverage.
|
||||
#[test]
|
||||
fn init() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
@ -23,9 +24,308 @@ fn init() -> Result<()> {
|
|||
"###);
|
||||
|
||||
let pyproject = fs_err::read_to_string(context.temp_dir.join("foo/pyproject.toml"))?;
|
||||
let init_py = fs_err::read_to_string(context.temp_dir.join("foo/src/foo/__init__.py"))?;
|
||||
let _ = fs_err::read_to_string(context.temp_dir.join("foo/README.md")).unwrap();
|
||||
|
||||
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 = []
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
// Run `uv lock` in the new project.
|
||||
uv_snapshot!(context.filters(), context.lock().current_dir(context.temp_dir.join("foo")), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
Resolved 1 package in [TIME]
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run `uv init --app` to create an application project
|
||||
#[test]
|
||||
fn init_application() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let child = context.temp_dir.child("foo");
|
||||
child.create_dir_all()?;
|
||||
|
||||
let pyproject_toml = child.join("pyproject.toml");
|
||||
let hello_py = child.join("hello.py");
|
||||
|
||||
uv_snapshot!(context.filters(), context.init().current_dir(&child).arg("--app"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Initialized project `foo`
|
||||
"###);
|
||||
|
||||
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 = []
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
let hello = fs_err::read_to_string(hello_py)?;
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
hello, @r###"
|
||||
def main():
|
||||
print("Hello from foo!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
uv_snapshot!(context.filters(), context.run().current_dir(&child).arg("hello.py"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Hello from foo!
|
||||
|
||||
----- stderr -----
|
||||
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
Creating virtualenv at: .venv
|
||||
Resolved 1 package in [TIME]
|
||||
Audited in [TIME]
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// When `hello.py` already exists, we don't create it again
|
||||
#[test]
|
||||
fn init_application_hello_exists() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let child = context.temp_dir.child("foo");
|
||||
child.create_dir_all()?;
|
||||
|
||||
let pyproject_toml = child.join("pyproject.toml");
|
||||
let hello_py = child.child("hello.py");
|
||||
hello_py.touch()?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.init().current_dir(&child).arg("--app"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Initialized project `foo`
|
||||
"###);
|
||||
|
||||
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 = []
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
let hello = fs_err::read_to_string(hello_py)?;
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
hello, @""
|
||||
);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// When other Python files already exists, we still create `hello.py`
|
||||
#[test]
|
||||
fn init_application_other_python_exists() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let child = context.temp_dir.child("foo");
|
||||
child.create_dir_all()?;
|
||||
|
||||
let pyproject_toml = child.join("pyproject.toml");
|
||||
let hello_py = child.join("hello.py");
|
||||
let other_py = child.child("foo.py");
|
||||
other_py.touch()?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.init().current_dir(&child).arg("--app"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Initialized project `foo`
|
||||
"###);
|
||||
|
||||
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 = []
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
let hello = fs_err::read_to_string(hello_py)?;
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
hello, @r###"
|
||||
def main():
|
||||
print("Hello from foo!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run `uv init --app --package` to create a packaged application project
|
||||
#[test]
|
||||
fn init_application_package() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let child = context.temp_dir.child("foo");
|
||||
child.create_dir_all()?;
|
||||
|
||||
let pyproject_toml = child.join("pyproject.toml");
|
||||
let init_py = child.join("src").join("foo").join("__init__.py");
|
||||
|
||||
uv_snapshot!(context.filters(), context.init().current_dir(&child).arg("--app").arg("--package"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Initialized project `foo`
|
||||
"###);
|
||||
|
||||
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 = []
|
||||
|
||||
[project.scripts]
|
||||
hello = "foo:hello"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
let init = fs_err::read_to_string(init_py)?;
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
init, @r###"
|
||||
def hello():
|
||||
print("Hello from foo!")
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
uv_snapshot!(context.filters(), context.run().current_dir(&child).arg("hello"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Hello from foo!
|
||||
|
||||
----- stderr -----
|
||||
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
Creating virtualenv at: .venv
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ foo==0.1.0 (from file://[TEMP_DIR]/foo)
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run `uv init --lib` to create an library project
|
||||
#[test]
|
||||
fn init_library() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let child = context.temp_dir.child("foo");
|
||||
child.create_dir_all()?;
|
||||
|
||||
let pyproject_toml = child.join("pyproject.toml");
|
||||
let init_py = child.join("src").join("foo").join("__init__.py");
|
||||
|
||||
uv_snapshot!(context.filters(), context.init().current_dir(&child).arg("--lib"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Initialized project `foo`
|
||||
"###);
|
||||
|
||||
let pyproject = fs_err::read_to_string(&pyproject_toml)?;
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
|
@ -46,26 +346,55 @@ fn init() -> Result<()> {
|
|||
);
|
||||
});
|
||||
|
||||
let init = fs_err::read_to_string(init_py)?;
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
init_py, @r###"
|
||||
init, @r###"
|
||||
def hello() -> str:
|
||||
return "Hello from foo!"
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
// Run `uv lock` in the new project.
|
||||
uv_snapshot!(context.filters(), context.lock().current_dir(context.temp_dir.join("foo")), @r###"
|
||||
uv_snapshot!(context.filters(), context.run().current_dir(&child).arg("python").arg("-c").arg("import foo; print(foo.hello())"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Hello from foo!
|
||||
|
||||
----- stderr -----
|
||||
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
Creating virtualenv at: .venv
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ foo==0.1.0 (from file://[TEMP_DIR]/foo)
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Using `uv init --lib --no-package` isn't allowed
|
||||
#[test]
|
||||
fn init_library_no_package() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let child = context.temp_dir.child("foo");
|
||||
child.create_dir_all()?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.init().current_dir(&child).arg("--lib").arg("--no-package"), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: the argument '--lib' cannot be used with '--no-package'
|
||||
|
||||
Usage: uv init --cache-dir [CACHE_DIR] --lib [PATH]
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
|
@ -117,10 +446,6 @@ fn init_no_readme() -> Result<()> {
|
|||
description = "Add your description here"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
@ -129,13 +454,13 @@ fn init_no_readme() -> Result<()> {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn init_current_dir() -> Result<()> {
|
||||
fn init_library_current_dir() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let dir = context.temp_dir.join("foo");
|
||||
fs_err::create_dir(&dir)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.init().current_dir(&dir), @r###"
|
||||
uv_snapshot!(context.filters(), context.init().arg("--lib").current_dir(&dir), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -193,6 +518,69 @@ fn init_current_dir() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn init_application_current_dir() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let dir = context.temp_dir.join("foo");
|
||||
fs_err::create_dir(&dir)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.init().arg("--app").current_dir(&dir), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Initialized project `foo`
|
||||
"###);
|
||||
|
||||
let pyproject = fs_err::read_to_string(dir.join("pyproject.toml"))?;
|
||||
let hello_py = fs_err::read_to_string(dir.join("hello.py"))?;
|
||||
|
||||
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 = []
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
hello_py, @r###"
|
||||
def main():
|
||||
print("Hello from foo!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
// Run `uv lock` in the new project.
|
||||
uv_snapshot!(context.filters(), context.lock().current_dir(&dir), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
Resolved 1 package in [TIME]
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn init_dot_args() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
@ -200,7 +588,7 @@ fn init_dot_args() -> Result<()> {
|
|||
let dir = context.temp_dir.join("foo");
|
||||
fs_err::create_dir(&dir)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.init().current_dir(&dir).arg("."), @r###"
|
||||
uv_snapshot!(context.filters(), context.init().current_dir(&dir).arg(".").arg("--lib"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -276,7 +664,7 @@ fn init_workspace() -> Result<()> {
|
|||
let child = context.temp_dir.join("foo");
|
||||
fs_err::create_dir(&child)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.init().current_dir(&child), @r###"
|
||||
uv_snapshot!(context.filters(), context.init().arg("--lib").current_dir(&child), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -370,7 +758,7 @@ fn init_workspace_relative_sub_package() -> Result<()> {
|
|||
|
||||
let child = context.temp_dir.join("foo");
|
||||
|
||||
uv_snapshot!(context.filters(), context.init().current_dir(&context.temp_dir).arg("foo"), @r###"
|
||||
uv_snapshot!(context.filters(), context.init().arg("--lib").current_dir(&context.temp_dir).arg("foo"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -465,7 +853,7 @@ fn init_workspace_outside() -> Result<()> {
|
|||
let child = context.temp_dir.join("foo");
|
||||
|
||||
// Run `uv init <path>` outside the workspace.
|
||||
uv_snapshot!(context.filters(), context.init().current_dir(&context.home_dir).arg(&child), @r###"
|
||||
uv_snapshot!(context.filters(), context.init().arg("--lib").current_dir(&context.home_dir).arg(&child), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -543,11 +931,11 @@ fn init_workspace_outside() -> Result<()> {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn init_invalid_names() -> Result<()> {
|
||||
fn init_normalized_names() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
// `foo-bar` normalized to `foo_bar`.
|
||||
uv_snapshot!(context.filters(), context.init().current_dir(&context.temp_dir).arg("foo-bar"), @r###"
|
||||
// `foo-bar` module is normalized to `foo_bar`.
|
||||
uv_snapshot!(context.filters(), context.init().current_dir(&context.temp_dir).arg("foo-bar").arg("--lib"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -580,6 +968,39 @@ fn init_invalid_names() -> Result<()> {
|
|||
);
|
||||
});
|
||||
|
||||
// `foo-bar` module is normalized to `foo_bar`.
|
||||
uv_snapshot!(context.filters(), context.init().current_dir(&context.temp_dir).arg("foo-bar").arg("--app"), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Project is already initialized in `[TEMP_DIR]/foo-bar`
|
||||
"###);
|
||||
|
||||
let child = context.temp_dir.child("foo-bar");
|
||||
let pyproject = fs_err::read_to_string(child.join("pyproject.toml"))?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
pyproject, @r###"
|
||||
[project]
|
||||
name = "foo-bar"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
// "bar baz" is not allowed.
|
||||
uv_snapshot!(context.filters(), context.init().current_dir(&context.temp_dir).arg("bar baz"), @r###"
|
||||
success: false
|
||||
|
@ -744,10 +1165,6 @@ fn init_no_workspace_warning() -> Result<()> {
|
|||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
@ -822,10 +1239,6 @@ fn init_project_inside_project() -> Result<()> {
|
|||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
@ -898,7 +1311,7 @@ fn init_virtual_project() -> Result<()> {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Initialized workspace `foo`
|
||||
Initialized project `foo`
|
||||
"###);
|
||||
|
||||
let pyproject = fs_err::read_to_string(&pyproject_toml)?;
|
||||
|
@ -914,6 +1327,13 @@ fn init_virtual_project() -> Result<()> {
|
|||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[project.scripts]
|
||||
hello = "foo:hello"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
@ -942,6 +1362,13 @@ fn init_virtual_project() -> Result<()> {
|
|||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[project.scripts]
|
||||
hello = "foo:hello"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = ["bar"]
|
||||
"###
|
||||
|
@ -1013,7 +1440,7 @@ fn init_nested_virtual_workspace() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
Adding `foo` as member of workspace `[TEMP_DIR]/`
|
||||
Initialized workspace `foo` at `[TEMP_DIR]/foo`
|
||||
Initialized project `foo` at `[TEMP_DIR]/foo`
|
||||
"###);
|
||||
|
||||
let pyproject = fs_err::read_to_string(context.temp_dir.join("foo").join("pyproject.toml"))?;
|
||||
|
@ -1029,6 +1456,13 @@ fn init_nested_virtual_workspace() -> Result<()> {
|
|||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[project.scripts]
|
||||
hello = "foo:hello"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
@ -1177,10 +1611,6 @@ fn init_requires_python_workspace() -> Result<()> {
|
|||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = []
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
@ -1230,10 +1660,6 @@ fn init_requires_python_version() -> Result<()> {
|
|||
readme = "README.md"
|
||||
requires-python = ">=3.8"
|
||||
dependencies = []
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
@ -1284,10 +1710,6 @@ fn init_requires_python_specifiers() -> Result<()> {
|
|||
readme = "README.md"
|
||||
requires-python = "==3.8.*"
|
||||
dependencies = []
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
|
|
@ -372,7 +372,15 @@ uv init [OPTIONS] [PATH]
|
|||
|
||||
<h3 class="cli-reference">Options</h3>
|
||||
|
||||
<dl class="cli-reference"><dt><code>--cache-dir</code> <i>cache-dir</i></dt><dd><p>Path to the cache directory.</p>
|
||||
<dl class="cli-reference"><dt><code>--app</code></dt><dd><p>Create a project for an application.</p>
|
||||
|
||||
<p>This is the default behavior if <code>--lib</code> is not requested.</p>
|
||||
|
||||
<p>This project kind is for web servers, scripts, and command-line interfaces.</p>
|
||||
|
||||
<p>By default, an application is not intended to be built and distributed as a Python package. The <code>--package</code> option can be used to create an application that is distributable, e.g., if you want to distribute a command-line interface via PyPI.</p>
|
||||
|
||||
</dd><dt><code>--cache-dir</code> <i>cache-dir</i></dt><dd><p>Path to the cache directory.</p>
|
||||
|
||||
<p>Defaults to <code>$HOME/Library/Caches/uv</code> on macOS, <code>$XDG_CACHE_HOME/uv</code> or <code>$HOME/.cache/uv</code> on Linux, and <code>%LOCALAPPDATA%\uv\cache</code> on Windows.</p>
|
||||
|
||||
|
@ -394,6 +402,10 @@ uv init [OPTIONS] [PATH]
|
|||
|
||||
</dd><dt><code>--help</code>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
|
||||
|
||||
</dd><dt><code>--lib</code></dt><dd><p>Create a project for a library.</p>
|
||||
|
||||
<p>A library is a project that is intended to be built and distributed as a Python package.</p>
|
||||
|
||||
</dd><dt><code>--name</code> <i>name</i></dt><dd><p>The name of the project.</p>
|
||||
|
||||
<p>Defaults to the name of the directory.</p>
|
||||
|
@ -410,6 +422,12 @@ uv init [OPTIONS] [PATH]
|
|||
|
||||
<p>Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.</p>
|
||||
|
||||
</dd><dt><code>--no-package</code></dt><dd><p>Do not set up the project to be built as a Python package.</p>
|
||||
|
||||
<p>Does not include a <code>[build-system]</code> for the project.</p>
|
||||
|
||||
<p>This is the default behavior when using <code>--app</code>.</p>
|
||||
|
||||
</dd><dt><code>--no-progress</code></dt><dd><p>Hide all progress outputs.</p>
|
||||
|
||||
<p>For example, spinners or progress bars.</p>
|
||||
|
@ -428,6 +446,14 @@ uv init [OPTIONS] [PATH]
|
|||
|
||||
<p>When disabled, uv will only use locally cached data and locally available files.</p>
|
||||
|
||||
</dd><dt><code>--package</code></dt><dd><p>Set up the project to be built as a Python package.</p>
|
||||
|
||||
<p>Defines a <code>[build-system]</code> for the project.</p>
|
||||
|
||||
<p>This is the default behavior when using <code>--lib</code>.</p>
|
||||
|
||||
<p>When using <code>--app</code>, this will include a <code>[project.scripts]</code> entrypoint and use a <code>src/</code> project structure.</p>
|
||||
|
||||
</dd><dt><code>--python</code>, <code>-p</code> <i>python</i></dt><dd><p>The Python interpreter to use to determine the minimum supported Python version.</p>
|
||||
|
||||
<p>See <a href="#uv-python">uv python</a> to view supported request formats.</p>
|
||||
|
@ -455,12 +481,6 @@ 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 project, rather than a package.</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>
|
||||
|
||||
## uv add
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue