mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-13 09:12:32 +00:00
Support --with-requirements script.py and -r script.py to include inline dependency metadata from another script (#12763)
## Summary Closes #6542 ## Test Plan `cargo test`
This commit is contained in:
parent
e136a51f3d
commit
6eefde28e7
10 changed files with 410 additions and 9 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -6189,6 +6189,7 @@ dependencies = [
|
||||||
"uv-redacted",
|
"uv-redacted",
|
||||||
"uv-requirements-txt",
|
"uv-requirements-txt",
|
||||||
"uv-resolver",
|
"uv-resolver",
|
||||||
|
"uv-scripts",
|
||||||
"uv-types",
|
"uv-types",
|
||||||
"uv-warnings",
|
"uv-warnings",
|
||||||
"uv-workspace",
|
"uv-workspace",
|
||||||
|
|
|
||||||
|
|
@ -1836,7 +1836,7 @@ pub struct PipInstallArgs {
|
||||||
#[arg(group = "sources")]
|
#[arg(group = "sources")]
|
||||||
pub package: Vec<String>,
|
pub package: Vec<String>,
|
||||||
|
|
||||||
/// Install all packages listed in the given `requirements.txt` or `pylock.toml` files.
|
/// Install all packages listed in the given `requirements.txt`, PEP 723 scripts, or `pylock.toml` files.
|
||||||
///
|
///
|
||||||
/// If a `pyproject.toml`, `setup.py`, or `setup.cfg` file is provided, uv will extract the
|
/// If a `pyproject.toml`, `setup.py`, or `setup.cfg` file is provided, uv will extract the
|
||||||
/// requirements for the relevant project.
|
/// requirements for the relevant project.
|
||||||
|
|
@ -3205,7 +3205,8 @@ pub struct RunArgs {
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub with_editable: Vec<comma::CommaSeparatedRequirements>,
|
pub with_editable: Vec<comma::CommaSeparatedRequirements>,
|
||||||
|
|
||||||
/// Run with all packages listed in the given `requirements.txt` files.
|
/// Run with all packages listed in the given `requirements.txt` files or PEP 723 Python
|
||||||
|
/// scripts.
|
||||||
///
|
///
|
||||||
/// The same environment semantics as `--with` apply.
|
/// The same environment semantics as `--with` apply.
|
||||||
///
|
///
|
||||||
|
|
@ -4575,7 +4576,8 @@ pub struct ToolRunArgs {
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub with_editable: Vec<comma::CommaSeparatedRequirements>,
|
pub with_editable: Vec<comma::CommaSeparatedRequirements>,
|
||||||
|
|
||||||
/// Run with all packages listed in the given `requirements.txt` files.
|
/// Run with all packages listed in the given `requirements.txt` files or PEP 723 Python
|
||||||
|
/// scripts.
|
||||||
#[arg(long, value_delimiter = ',', value_parser = parse_maybe_file_path)]
|
#[arg(long, value_delimiter = ',', value_parser = parse_maybe_file_path)]
|
||||||
pub with_requirements: Vec<Maybe<PathBuf>>,
|
pub with_requirements: Vec<Maybe<PathBuf>>,
|
||||||
|
|
||||||
|
|
@ -4705,7 +4707,8 @@ pub struct ToolInstallArgs {
|
||||||
#[arg(short = 'w', long)]
|
#[arg(short = 'w', long)]
|
||||||
pub with: Vec<comma::CommaSeparatedRequirements>,
|
pub with: Vec<comma::CommaSeparatedRequirements>,
|
||||||
|
|
||||||
/// Include all requirements listed in the given `requirements.txt` files.
|
/// Run with all packages listed in the given `requirements.txt` files or PEP 723 Python
|
||||||
|
/// scripts.
|
||||||
#[arg(long, value_delimiter = ',', value_parser = parse_maybe_file_path)]
|
#[arg(long, value_delimiter = ',', value_parser = parse_maybe_file_path)]
|
||||||
pub with_requirements: Vec<Maybe<PathBuf>>,
|
pub with_requirements: Vec<Maybe<PathBuf>>,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ uv-pypi-types = { workspace = true }
|
||||||
uv-redacted = { workspace = true }
|
uv-redacted = { workspace = true }
|
||||||
uv-requirements-txt = { workspace = true, features = ["http"] }
|
uv-requirements-txt = { workspace = true, features = ["http"] }
|
||||||
uv-resolver = { workspace = true, features = ["clap"] }
|
uv-resolver = { workspace = true, features = ["clap"] }
|
||||||
|
uv-scripts = { workspace = true }
|
||||||
uv-types = { workspace = true }
|
uv-types = { workspace = true }
|
||||||
uv-warnings = { workspace = true }
|
uv-warnings = { workspace = true }
|
||||||
uv-workspace = { workspace = true }
|
uv-workspace = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ pub enum RequirementsSource {
|
||||||
Package(RequirementsTxtRequirement),
|
Package(RequirementsTxtRequirement),
|
||||||
/// An editable path was provided on the command line (e.g., `pip install -e ../flask`).
|
/// An editable path was provided on the command line (e.g., `pip install -e ../flask`).
|
||||||
Editable(RequirementsTxtRequirement),
|
Editable(RequirementsTxtRequirement),
|
||||||
|
/// Dependencies were provided via a PEP 723 script.
|
||||||
|
Pep723Script(PathBuf),
|
||||||
/// Dependencies were provided via a `pylock.toml` file.
|
/// Dependencies were provided via a `pylock.toml` file.
|
||||||
PylockToml(PathBuf),
|
PylockToml(PathBuf),
|
||||||
/// Dependencies were provided via a `requirements.txt` file (e.g., `pip install -r requirements.txt`).
|
/// Dependencies were provided via a `requirements.txt` file (e.g., `pip install -r requirements.txt`).
|
||||||
|
|
@ -44,6 +46,12 @@ impl RequirementsSource {
|
||||||
.is_some_and(|file_name| file_name.to_str().is_some_and(is_pylock_toml))
|
.is_some_and(|file_name| file_name.to_str().is_some_and(is_pylock_toml))
|
||||||
{
|
{
|
||||||
Ok(Self::PylockToml(path))
|
Ok(Self::PylockToml(path))
|
||||||
|
} else if path
|
||||||
|
.extension()
|
||||||
|
.is_some_and(|ext| ext.eq_ignore_ascii_case("py") || ext.eq_ignore_ascii_case("pyw"))
|
||||||
|
{
|
||||||
|
// TODO(blueraft): Support scripts without an extension.
|
||||||
|
Ok(Self::Pep723Script(path))
|
||||||
} else if path
|
} else if path
|
||||||
.extension()
|
.extension()
|
||||||
.is_some_and(|ext| ext.eq_ignore_ascii_case("toml"))
|
.is_some_and(|ext| ext.eq_ignore_ascii_case("toml"))
|
||||||
|
|
@ -290,6 +298,7 @@ impl std::fmt::Display for RequirementsSource {
|
||||||
Self::Editable(path) => write!(f, "-e {path:?}"),
|
Self::Editable(path) => write!(f, "-e {path:?}"),
|
||||||
Self::PylockToml(path)
|
Self::PylockToml(path)
|
||||||
| Self::RequirementsTxt(path)
|
| Self::RequirementsTxt(path)
|
||||||
|
| Self::Pep723Script(path)
|
||||||
| Self::PyprojectToml(path)
|
| Self::PyprojectToml(path)
|
||||||
| Self::SetupPy(path)
|
| Self::SetupPy(path)
|
||||||
| Self::SetupCfg(path)
|
| Self::SetupCfg(path)
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ use tracing::instrument;
|
||||||
use uv_cache_key::CanonicalUrl;
|
use uv_cache_key::CanonicalUrl;
|
||||||
use uv_client::BaseClientBuilder;
|
use uv_client::BaseClientBuilder;
|
||||||
use uv_configuration::{DependencyGroups, NoBinary, NoBuild};
|
use uv_configuration::{DependencyGroups, NoBinary, NoBuild};
|
||||||
use uv_distribution_types::Requirement;
|
use uv_distribution_types::{Index, Requirement};
|
||||||
use uv_distribution_types::{
|
use uv_distribution_types::{
|
||||||
IndexUrl, NameRequirementSpecification, UnresolvedRequirement,
|
IndexUrl, NameRequirementSpecification, UnresolvedRequirement,
|
||||||
UnresolvedRequirementSpecification,
|
UnresolvedRequirementSpecification,
|
||||||
|
|
@ -45,6 +45,7 @@ use uv_distribution_types::{
|
||||||
use uv_fs::{CWD, Simplified};
|
use uv_fs::{CWD, Simplified};
|
||||||
use uv_normalize::{ExtraName, PackageName, PipGroupName};
|
use uv_normalize::{ExtraName, PackageName, PipGroupName};
|
||||||
use uv_requirements_txt::{RequirementsTxt, RequirementsTxtRequirement};
|
use uv_requirements_txt::{RequirementsTxt, RequirementsTxtRequirement};
|
||||||
|
use uv_scripts::{Pep723Error, Pep723Item, Pep723Script};
|
||||||
use uv_warnings::warn_user;
|
use uv_warnings::warn_user;
|
||||||
use uv_workspace::pyproject::PyProjectToml;
|
use uv_workspace::pyproject::PyProjectToml;
|
||||||
|
|
||||||
|
|
@ -181,6 +182,125 @@ impl RequirementsSpecification {
|
||||||
..Self::default()
|
..Self::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
RequirementsSource::Pep723Script(path) => {
|
||||||
|
let script = match Pep723Script::read(&path).await {
|
||||||
|
Ok(Some(script)) => Pep723Item::Script(script),
|
||||||
|
Ok(None) => {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"`{}` does not contain inline script metadata",
|
||||||
|
path.user_display(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Err(Pep723Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"Failed to read `{}` (not found)",
|
||||||
|
path.user_display(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let metadata = script.metadata();
|
||||||
|
|
||||||
|
let requirements = metadata
|
||||||
|
.dependencies
|
||||||
|
.as_ref()
|
||||||
|
.map(|dependencies| {
|
||||||
|
dependencies
|
||||||
|
.iter()
|
||||||
|
.map(|dependency| {
|
||||||
|
UnresolvedRequirementSpecification::from(Requirement::from(
|
||||||
|
dependency.to_owned(),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.collect::<Vec<UnresolvedRequirementSpecification>>()
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
if let Some(tool_uv) = metadata.tool.as_ref().and_then(|tool| tool.uv.as_ref()) {
|
||||||
|
let constraints = tool_uv
|
||||||
|
.constraint_dependencies
|
||||||
|
.as_ref()
|
||||||
|
.map(|dependencies| {
|
||||||
|
dependencies
|
||||||
|
.iter()
|
||||||
|
.map(|dependency| {
|
||||||
|
NameRequirementSpecification::from(Requirement::from(
|
||||||
|
dependency.to_owned(),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.collect::<Vec<NameRequirementSpecification>>()
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let overrides = tool_uv
|
||||||
|
.override_dependencies
|
||||||
|
.as_ref()
|
||||||
|
.map(|dependencies| {
|
||||||
|
dependencies
|
||||||
|
.iter()
|
||||||
|
.map(|dependency| {
|
||||||
|
UnresolvedRequirementSpecification::from(Requirement::from(
|
||||||
|
dependency.to_owned(),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.collect::<Vec<UnresolvedRequirementSpecification>>()
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
requirements,
|
||||||
|
constraints,
|
||||||
|
overrides,
|
||||||
|
index_url: tool_uv
|
||||||
|
.top_level
|
||||||
|
.index_url
|
||||||
|
.as_ref()
|
||||||
|
.map(|index| Index::from(index.clone()).url),
|
||||||
|
extra_index_urls: tool_uv
|
||||||
|
.top_level
|
||||||
|
.extra_index_url
|
||||||
|
.as_ref()
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|urls| {
|
||||||
|
urls.iter().map(|index| Index::from(index.clone()).url)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
no_index: tool_uv.top_level.no_index.unwrap_or_default(),
|
||||||
|
find_links: tool_uv
|
||||||
|
.top_level
|
||||||
|
.find_links
|
||||||
|
.as_ref()
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|urls| {
|
||||||
|
urls.iter().map(|index| Index::from(index.clone()).url)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
no_binary: NoBinary::from_args(
|
||||||
|
tool_uv.top_level.no_binary,
|
||||||
|
tool_uv
|
||||||
|
.top_level
|
||||||
|
.no_binary_package
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default(),
|
||||||
|
),
|
||||||
|
no_build: NoBuild::from_args(
|
||||||
|
tool_uv.top_level.no_build,
|
||||||
|
tool_uv
|
||||||
|
.top_level
|
||||||
|
.no_build_package
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default(),
|
||||||
|
),
|
||||||
|
..Self::default()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Self {
|
||||||
|
requirements,
|
||||||
|
..Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
RequirementsSource::SetupPy(path) | RequirementsSource::SetupCfg(path) => {
|
RequirementsSource::SetupPy(path) | RequirementsSource::SetupCfg(path) => {
|
||||||
if !path.is_file() {
|
if !path.is_file() {
|
||||||
return Err(anyhow::anyhow!("File not found: `{}`", path.user_display()));
|
return Err(anyhow::anyhow!("File not found: `{}`", path.user_display()));
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,9 @@ pub(crate) async fn add(
|
||||||
RequirementsSource::SetupPy(_) => {
|
RequirementsSource::SetupPy(_) => {
|
||||||
bail!("Adding requirements from a `setup.py` is not supported in `uv add`");
|
bail!("Adding requirements from a `setup.py` is not supported in `uv add`");
|
||||||
}
|
}
|
||||||
|
RequirementsSource::Pep723Script(_) => {
|
||||||
|
bail!("Adding requirements from a PEP 723 script is not supported in `uv add`");
|
||||||
|
}
|
||||||
RequirementsSource::SetupCfg(_) => {
|
RequirementsSource::SetupCfg(_) => {
|
||||||
bail!("Adding requirements from a `setup.cfg` is not supported in `uv add`");
|
bail!("Adding requirements from a `setup.cfg` is not supported in `uv add`");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -757,6 +757,71 @@ werkzeug==3.0.1
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn install_with_dependencies_from_script() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
let script = context.temp_dir.child("script.py");
|
||||||
|
script.write_str(indoc! {r#"
|
||||||
|
# /// script
|
||||||
|
# requires-python = ">=3.11"
|
||||||
|
# dependencies = [
|
||||||
|
# "anyio",
|
||||||
|
# ]
|
||||||
|
# ///
|
||||||
|
|
||||||
|
import anyio
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.pip_install()
|
||||||
|
.arg("-r")
|
||||||
|
.arg("script.py")
|
||||||
|
.arg("--strict"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 3 packages in [TIME]
|
||||||
|
Prepared 3 packages in [TIME]
|
||||||
|
Installed 3 packages in [TIME]
|
||||||
|
+ anyio==4.3.0
|
||||||
|
+ idna==3.6
|
||||||
|
+ sniffio==1.3.1
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update the script file.
|
||||||
|
script.write_str(indoc! {r#"
|
||||||
|
# /// script
|
||||||
|
# requires-python = ">=3.11"
|
||||||
|
# dependencies = [
|
||||||
|
# "anyio",
|
||||||
|
# "iniconfig",
|
||||||
|
# ]
|
||||||
|
# ///
|
||||||
|
|
||||||
|
import anyio
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.pip_install()
|
||||||
|
.arg("-r")
|
||||||
|
.arg("script.py")
|
||||||
|
.arg("--strict"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 4 packages in [TIME]
|
||||||
|
Prepared 1 package in [TIME]
|
||||||
|
Installed 1 package in [TIME]
|
||||||
|
+ iniconfig==2.0.0
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Install a `pyproject.toml` file with a `poetry` section.
|
/// Install a `pyproject.toml` file with a `poetry` section.
|
||||||
#[test]
|
#[test]
|
||||||
fn install_pyproject_toml_poetry() -> Result<()> {
|
fn install_pyproject_toml_poetry() -> Result<()> {
|
||||||
|
|
|
||||||
|
|
@ -2026,6 +2026,131 @@ fn tool_install_unnamed_with() {
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tool_install_with_dependencies_from_script() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12")
|
||||||
|
.with_filtered_counts()
|
||||||
|
.with_filtered_exe_suffix();
|
||||||
|
let tool_dir = context.temp_dir.child("tools");
|
||||||
|
let bin_dir = context.temp_dir.child("bin");
|
||||||
|
|
||||||
|
let script = context.temp_dir.child("script.py");
|
||||||
|
script.write_str(indoc! {r#"
|
||||||
|
# /// script
|
||||||
|
# requires-python = ">=3.11"
|
||||||
|
# dependencies = [
|
||||||
|
# "anyio",
|
||||||
|
# ]
|
||||||
|
# ///
|
||||||
|
|
||||||
|
import anyio
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
// script dependencies (anyio) are now installed.
|
||||||
|
uv_snapshot!(context.filters(), context.tool_install()
|
||||||
|
.arg("--with-requirements")
|
||||||
|
.arg("script.py")
|
||||||
|
.arg("black")
|
||||||
|
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
|
||||||
|
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str())
|
||||||
|
.env(EnvVars::PATH, bin_dir.as_os_str()), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved [N] packages in [TIME]
|
||||||
|
Prepared [N] packages in [TIME]
|
||||||
|
Installed [N] packages in [TIME]
|
||||||
|
+ anyio==4.3.0
|
||||||
|
+ black==24.3.0
|
||||||
|
+ click==8.1.7
|
||||||
|
+ idna==3.6
|
||||||
|
+ mypy-extensions==1.0.0
|
||||||
|
+ packaging==24.0
|
||||||
|
+ pathspec==0.12.1
|
||||||
|
+ platformdirs==4.2.0
|
||||||
|
+ sniffio==1.3.1
|
||||||
|
Installed 2 executables: black, blackd
|
||||||
|
");
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
// We should have a tool receipt
|
||||||
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
|
||||||
|
[tool]
|
||||||
|
requirements = [
|
||||||
|
{ name = "black" },
|
||||||
|
{ name = "anyio" },
|
||||||
|
]
|
||||||
|
entrypoints = [
|
||||||
|
{ name = "black", install-path = "[TEMP_DIR]/bin/black", from = "black" },
|
||||||
|
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd", from = "black" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.options]
|
||||||
|
exclude-newer = "2024-03-25T00:00:00Z"
|
||||||
|
"#);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update the script file.
|
||||||
|
script.write_str(indoc! {r#"
|
||||||
|
# /// script
|
||||||
|
# requires-python = ">=3.11"
|
||||||
|
# dependencies = [
|
||||||
|
# "anyio",
|
||||||
|
# "iniconfig",
|
||||||
|
# ]
|
||||||
|
# ///
|
||||||
|
|
||||||
|
import anyio
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
// Install `black`
|
||||||
|
uv_snapshot!(context.filters(), context.tool_install()
|
||||||
|
.arg("black")
|
||||||
|
.arg("--with-requirements")
|
||||||
|
.arg("script.py")
|
||||||
|
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
|
||||||
|
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str())
|
||||||
|
.env(EnvVars::PATH, bin_dir.as_os_str()), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved [N] packages in [TIME]
|
||||||
|
Prepared [N] packages in [TIME]
|
||||||
|
Installed [N] packages in [TIME]
|
||||||
|
+ iniconfig==2.0.0
|
||||||
|
Installed 2 executables: black, blackd
|
||||||
|
");
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
// We should have a tool receipt
|
||||||
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
|
||||||
|
[tool]
|
||||||
|
requirements = [
|
||||||
|
{ name = "black" },
|
||||||
|
{ name = "anyio" },
|
||||||
|
{ name = "iniconfig" },
|
||||||
|
]
|
||||||
|
entrypoints = [
|
||||||
|
{ name = "black", install-path = "[TEMP_DIR]/bin/black", from = "black" },
|
||||||
|
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd", from = "black" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.options]
|
||||||
|
exclude-newer = "2024-03-25T00:00:00Z"
|
||||||
|
"#);
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Test installing a tool with additional requirements from a `requirements.txt` file.
|
/// Test installing a tool with additional requirements from a `requirements.txt` file.
|
||||||
#[test]
|
#[test]
|
||||||
fn tool_install_requirements_txt() {
|
fn tool_install_requirements_txt() {
|
||||||
|
|
|
||||||
|
|
@ -2658,6 +2658,80 @@ fn tool_run_with_incompatible_build_constraints() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tool_run_with_dependencies_from_script() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12").with_filtered_counts();
|
||||||
|
|
||||||
|
let script = context.temp_dir.child("script.py");
|
||||||
|
script.write_str(indoc! {r#"
|
||||||
|
# /// script
|
||||||
|
# requires-python = ">=3.11"
|
||||||
|
# dependencies = [
|
||||||
|
# "anyio",
|
||||||
|
# ]
|
||||||
|
# ///
|
||||||
|
|
||||||
|
import anyio
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
// script dependencies (anyio) are now installed.
|
||||||
|
uv_snapshot!(context.filters(), context.tool_run()
|
||||||
|
.arg("--with-requirements")
|
||||||
|
.arg("script.py")
|
||||||
|
.arg("black")
|
||||||
|
.arg("script.py")
|
||||||
|
.arg("-q"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved [N] packages in [TIME]
|
||||||
|
Prepared [N] packages in [TIME]
|
||||||
|
Installed [N] packages in [TIME]
|
||||||
|
+ anyio==4.3.0
|
||||||
|
+ black==24.3.0
|
||||||
|
+ click==8.1.7
|
||||||
|
+ idna==3.6
|
||||||
|
+ mypy-extensions==1.0.0
|
||||||
|
+ packaging==24.0
|
||||||
|
+ pathspec==0.12.1
|
||||||
|
+ platformdirs==4.2.0
|
||||||
|
+ sniffio==1.3.1
|
||||||
|
");
|
||||||
|
|
||||||
|
// Error when the script is not a valid PEP723 script.
|
||||||
|
let script = context.temp_dir.child("not_pep723_script.py");
|
||||||
|
script.write_str("import anyio")?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.tool_run()
|
||||||
|
.arg("--with-requirements")
|
||||||
|
.arg("not_pep723_script.py")
|
||||||
|
.arg("black"), @r"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
error: `not_pep723_script.py` does not contain inline script metadata
|
||||||
|
");
|
||||||
|
|
||||||
|
// Error when the script doesn't exist.
|
||||||
|
uv_snapshot!(context.filters(), context.tool_run()
|
||||||
|
.arg("--with-requirements")
|
||||||
|
.arg("missing_file.py")
|
||||||
|
.arg("black"), @r"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
error: Failed to read `missing_file.py` (not found)
|
||||||
|
");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Test windows runnable types, namely console scripts and legacy setuptools scripts.
|
/// Test windows runnable types, namely console scripts and legacy setuptools scripts.
|
||||||
/// Console Scripts <https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#console-scripts>
|
/// Console Scripts <https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#console-scripts>
|
||||||
/// Legacy Scripts <https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools/#scripts>.
|
/// Legacy Scripts <https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools/#scripts>.
|
||||||
|
|
|
||||||
|
|
@ -611,7 +611,7 @@ used.</p>
|
||||||
<p>When used in a project, these dependencies will be layered on top of the project environment in a separate, ephemeral environment. These dependencies are allowed to conflict with those specified by the project.</p>
|
<p>When used in a project, these dependencies will be layered on top of the project environment in a separate, ephemeral environment. These dependencies are allowed to conflict with those specified by the project.</p>
|
||||||
</dd><dt id="uv-run--with-editable"><a href="#uv-run--with-editable"><code>--with-editable</code></a> <i>with-editable</i></dt><dd><p>Run with the given packages installed in editable mode.</p>
|
</dd><dt id="uv-run--with-editable"><a href="#uv-run--with-editable"><code>--with-editable</code></a> <i>with-editable</i></dt><dd><p>Run with the given packages installed in editable mode.</p>
|
||||||
<p>When used in a project, these dependencies will be layered on top of the project environment in a separate, ephemeral environment. These dependencies are allowed to conflict with those specified by the project.</p>
|
<p>When used in a project, these dependencies will be layered on top of the project environment in a separate, ephemeral environment. These dependencies are allowed to conflict with those specified by the project.</p>
|
||||||
</dd><dt id="uv-run--with-requirements"><a href="#uv-run--with-requirements"><code>--with-requirements</code></a> <i>with-requirements</i></dt><dd><p>Run with all packages listed in the given <code>requirements.txt</code> files.</p>
|
</dd><dt id="uv-run--with-requirements"><a href="#uv-run--with-requirements"><code>--with-requirements</code></a> <i>with-requirements</i></dt><dd><p>Run with all packages listed in the given <code>requirements.txt</code> files or PEP 723 Python scripts.</p>
|
||||||
<p>The same environment semantics as <code>--with</code> apply.</p>
|
<p>The same environment semantics as <code>--with</code> apply.</p>
|
||||||
<p>Using <code>pyproject.toml</code>, <code>setup.py</code>, or <code>setup.cfg</code> files is not allowed.</p>
|
<p>Using <code>pyproject.toml</code>, <code>setup.py</code>, or <code>setup.cfg</code> files is not allowed.</p>
|
||||||
</dd></dl>
|
</dd></dl>
|
||||||
|
|
@ -2546,7 +2546,7 @@ uv tool run [OPTIONS] [COMMAND]
|
||||||
</dd><dt id="uv-tool-run--with"><a href="#uv-tool-run--with"><code>--with</code></a>, <code>-w</code> <i>with</i></dt><dd><p>Run with the given packages installed</p>
|
</dd><dt id="uv-tool-run--with"><a href="#uv-tool-run--with"><code>--with</code></a>, <code>-w</code> <i>with</i></dt><dd><p>Run with the given packages installed</p>
|
||||||
</dd><dt id="uv-tool-run--with-editable"><a href="#uv-tool-run--with-editable"><code>--with-editable</code></a> <i>with-editable</i></dt><dd><p>Run with the given packages installed in editable mode</p>
|
</dd><dt id="uv-tool-run--with-editable"><a href="#uv-tool-run--with-editable"><code>--with-editable</code></a> <i>with-editable</i></dt><dd><p>Run with the given packages installed in editable mode</p>
|
||||||
<p>When used in a project, these dependencies will be layered on top of the uv tool's environment in a separate, ephemeral environment. These dependencies are allowed to conflict with those specified.</p>
|
<p>When used in a project, these dependencies will be layered on top of the uv tool's environment in a separate, ephemeral environment. These dependencies are allowed to conflict with those specified.</p>
|
||||||
</dd><dt id="uv-tool-run--with-requirements"><a href="#uv-tool-run--with-requirements"><code>--with-requirements</code></a> <i>with-requirements</i></dt><dd><p>Run with all packages listed in the given <code>requirements.txt</code> files</p>
|
</dd><dt id="uv-tool-run--with-requirements"><a href="#uv-tool-run--with-requirements"><code>--with-requirements</code></a> <i>with-requirements</i></dt><dd><p>Run with all packages listed in the given <code>requirements.txt</code> files or PEP 723 Python scripts</p>
|
||||||
</dd></dl>
|
</dd></dl>
|
||||||
|
|
||||||
### uv tool install
|
### uv tool install
|
||||||
|
|
@ -2773,7 +2773,7 @@ uv tool install [OPTIONS] <PACKAGE>
|
||||||
</dd><dt id="uv-tool-install--with"><a href="#uv-tool-install--with"><code>--with</code></a>, <code>-w</code> <i>with</i></dt><dd><p>Include the following additional requirements</p>
|
</dd><dt id="uv-tool-install--with"><a href="#uv-tool-install--with"><code>--with</code></a>, <code>-w</code> <i>with</i></dt><dd><p>Include the following additional requirements</p>
|
||||||
</dd><dt id="uv-tool-install--with-editable"><a href="#uv-tool-install--with-editable"><code>--with-editable</code></a> <i>with-editable</i></dt><dd><p>Include the given packages in editable mode</p>
|
</dd><dt id="uv-tool-install--with-editable"><a href="#uv-tool-install--with-editable"><code>--with-editable</code></a> <i>with-editable</i></dt><dd><p>Include the given packages in editable mode</p>
|
||||||
</dd><dt id="uv-tool-install--with-executables-from"><a href="#uv-tool-install--with-executables-from"><code>--with-executables-from</code></a> <i>with-executables-from</i></dt><dd><p>Install executables from the following packages</p>
|
</dd><dt id="uv-tool-install--with-executables-from"><a href="#uv-tool-install--with-executables-from"><code>--with-executables-from</code></a> <i>with-executables-from</i></dt><dd><p>Install executables from the following packages</p>
|
||||||
</dd><dt id="uv-tool-install--with-requirements"><a href="#uv-tool-install--with-requirements"><code>--with-requirements</code></a> <i>with-requirements</i></dt><dd><p>Include all requirements listed in the given <code>requirements.txt</code> files</p>
|
</dd><dt id="uv-tool-install--with-requirements"><a href="#uv-tool-install--with-requirements"><code>--with-requirements</code></a> <i>with-requirements</i></dt><dd><p>Run with all packages listed in the given <code>requirements.txt</code> files or PEP 723 Python scripts</p>
|
||||||
</dd></dl>
|
</dd></dl>
|
||||||
|
|
||||||
### uv tool upgrade
|
### uv tool upgrade
|
||||||
|
|
@ -4808,7 +4808,7 @@ should be used with caution, as it can modify the system Python installation.</p
|
||||||
<ul>
|
<ul>
|
||||||
<li>Git dependencies are not supported. - Editable installations are not supported. - Local dependencies are not supported, unless they point to a specific wheel (<code>.whl</code>) or source archive (<code>.zip</code>, <code>.tar.gz</code>), as opposed to a directory.</li>
|
<li>Git dependencies are not supported. - Editable installations are not supported. - Local dependencies are not supported, unless they point to a specific wheel (<code>.whl</code>) or source archive (<code>.zip</code>, <code>.tar.gz</code>), as opposed to a directory.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>May also be set with the <code>UV_REQUIRE_HASHES</code> environment variable.</p></dd><dt id="uv-pip-install--requirements"><a href="#uv-pip-install--requirements"><code>--requirements</code></a>, <code>--requirement</code>, <code>-r</code> <i>requirements</i></dt><dd><p>Install all packages listed in the given <code>requirements.txt</code> or <code>pylock.toml</code> files.</p>
|
<p>May also be set with the <code>UV_REQUIRE_HASHES</code> environment variable.</p></dd><dt id="uv-pip-install--requirements"><a href="#uv-pip-install--requirements"><code>--requirements</code></a>, <code>--requirement</code>, <code>-r</code> <i>requirements</i></dt><dd><p>Install all packages listed in the given <code>requirements.txt</code>, PEP 723 scripts, or <code>pylock.toml</code> files.</p>
|
||||||
<p>If a <code>pyproject.toml</code>, <code>setup.py</code>, or <code>setup.cfg</code> file is provided, uv will extract the requirements for the relevant project.</p>
|
<p>If a <code>pyproject.toml</code>, <code>setup.py</code>, or <code>setup.cfg</code> file is provided, uv will extract the requirements for the relevant project.</p>
|
||||||
<p>If <code>-</code> is provided, then requirements will be read from stdin.</p>
|
<p>If <code>-</code> is provided, then requirements will be read from stdin.</p>
|
||||||
</dd><dt id="uv-pip-install--resolution"><a href="#uv-pip-install--resolution"><code>--resolution</code></a> <i>resolution</i></dt><dd><p>The strategy to use when selecting between the different compatible versions for a given package requirement.</p>
|
</dd><dt id="uv-pip-install--resolution"><a href="#uv-pip-install--resolution"><code>--resolution</code></a> <i>resolution</i></dt><dd><p>The strategy to use when selecting between the different compatible versions for a given package requirement.</p>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue