diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 75797cf52b76..1d5e4739a9ca 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -3135,8 +3135,7 @@ pub struct SyncArgs { #[arg(long, conflicts_with = "all_packages")] pub package: Option, - /// Sync the virtual environment for the specified PEP 723 Python script, rather than the current - /// project. + /// Sync the environment for a Python script, rather than the current project. /// /// If provided, uv will sync the dependencies based on the script's inline metadata table, in /// adherence with PEP 723. @@ -3146,7 +3145,19 @@ pub struct SyncArgs { conflicts_with = "all_packages", conflicts_with = "package", conflicts_with = "no_install_project", - conflicts_with = "no_install_workspace" + conflicts_with = "no_install_workspace", + conflicts_with = "extra", + conflicts_with = "all_extras", + conflicts_with = "no_extra", + conflicts_with = "no_all_extras", + conflicts_with = "dev", + conflicts_with = "no_dev", + conflicts_with = "only_dev", + conflicts_with = "group", + conflicts_with = "no_group", + conflicts_with = "no_default_groups", + conflicts_with = "only_group", + conflicts_with = "all_groups" )] pub script: Option, diff --git a/crates/uv-distribution/src/lib.rs b/crates/uv-distribution/src/lib.rs index 9cc5f0762cd2..a91ed6ccfb5e 100644 --- a/crates/uv-distribution/src/lib.rs +++ b/crates/uv-distribution/src/lib.rs @@ -3,8 +3,8 @@ pub use download::LocalWheel; pub use error::Error; pub use index::{BuiltWheelIndex, RegistryWheelIndex}; pub use metadata::{ - ArchiveMetadata, BuildRequires, FlatRequiresDist, LoweredRequirement, Metadata, MetadataError, - RequiresDist, + ArchiveMetadata, BuildRequires, FlatRequiresDist, LoweredRequirement, LoweringError, Metadata, + MetadataError, RequiresDist, }; pub use reporter::Reporter; pub use source::prune; diff --git a/crates/uv-distribution/src/metadata/mod.rs b/crates/uv-distribution/src/metadata/mod.rs index 1887c1e40ac9..8585eff4f90d 100644 --- a/crates/uv-distribution/src/metadata/mod.rs +++ b/crates/uv-distribution/src/metadata/mod.rs @@ -13,7 +13,7 @@ use uv_workspace::WorkspaceError; pub use crate::metadata::build_requires::BuildRequires; pub use crate::metadata::lowering::LoweredRequirement; -use crate::metadata::lowering::LoweringError; +pub use crate::metadata::lowering::LoweringError; pub use crate::metadata::requires_dist::{FlatRequiresDist, RequiresDist}; mod build_requires; diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index e035b707fd77..1de0d82c8ee3 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -1,7 +1,6 @@ use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet}; use std::fmt::Write; -use std::ops::Deref; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -14,10 +13,10 @@ use uv_cache_key::cache_digest; use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ Concurrency, Constraints, DevGroupsManifest, DevGroupsSpecification, DryRun, - ExtrasSpecification, PreviewMode, Reinstall, TrustedHost, Upgrade, + ExtrasSpecification, PreviewMode, Reinstall, SourceStrategy, TrustedHost, Upgrade, }; use uv_dispatch::{BuildDispatch, SharedState}; -use uv_distribution::DistributionDatabase; +use uv_distribution::{DistributionDatabase, LoweredRequirement}; use uv_distribution_types::{ Index, Resolution, UnresolvedRequirement, UnresolvedRequirementSpecification, }; @@ -227,6 +226,9 @@ pub(crate) enum ProjectError { #[error(transparent)] Metadata(#[from] uv_distribution::MetadataError), + #[error(transparent)] + Lowering(#[from] uv_distribution::LoweringError), + #[error(transparent)] PyprojectMut(#[from] uv_workspace::pyproject_mut::Error), @@ -1224,7 +1226,7 @@ impl ProjectEnvironment { } } -impl Deref for ProjectEnvironment { +impl std::ops::Deref for ProjectEnvironment { type Target = PythonEnvironment; fn deref(&self) -> &Self::Target { @@ -1389,7 +1391,7 @@ impl ScriptEnvironment { } } -impl Deref for ScriptEnvironment { +impl std::ops::Deref for ScriptEnvironment { type Target = PythonEnvironment; fn deref(&self) -> &Self::Target { @@ -1911,6 +1913,7 @@ pub(crate) async fn update_environment( native_tls: bool, allow_insecure_host: &[TrustedHost], cache: &Cache, + dry_run: DryRun, printer: Printer, preview: PreviewMode, ) -> Result { @@ -2026,7 +2029,6 @@ pub(crate) async fn update_environment( // optional on the downstream APIs. let build_constraints = Constraints::default(); let build_hasher = HashStrategy::default(); - let dry_run = DryRun::default(); let extras = ExtrasSpecification::default(); let groups = DevGroupsSpecification::default(); let hasher = HashStrategy::default(); @@ -2297,6 +2299,113 @@ pub(crate) fn detect_conflicts( Ok(()) } +/// Determine the [`RequirementsSpecification`] for a script. +#[allow(clippy::result_large_err)] +pub(crate) fn script_specification( + script: Pep723ItemRef<'_>, + settings: ResolverSettingsRef, +) -> Result, ProjectError> { + let Some(dependencies) = script.metadata().dependencies.as_ref() else { + return Ok(None); + }; + + // Determine the working directory for the script. + let script_dir = match &script { + Pep723ItemRef::Script(script) => std::path::absolute(&script.path)? + .parent() + .expect("script path has no parent") + .to_owned(), + Pep723ItemRef::Stdin(..) | Pep723ItemRef::Remote(..) => std::env::current_dir()?, + }; + + // Collect any `tool.uv.index` from the script. + let empty = Vec::default(); + let script_indexes = match settings.sources { + SourceStrategy::Enabled => script + .metadata() + .tool + .as_ref() + .and_then(|tool| tool.uv.as_ref()) + .and_then(|uv| uv.top_level.index.as_deref()) + .unwrap_or(&empty), + SourceStrategy::Disabled => &empty, + }; + + // Collect any `tool.uv.sources` from the script. + let empty = BTreeMap::default(); + let script_sources = match settings.sources { + SourceStrategy::Enabled => script + .metadata() + .tool + .as_ref() + .and_then(|tool| tool.uv.as_ref()) + .and_then(|uv| uv.sources.as_ref()) + .unwrap_or(&empty), + SourceStrategy::Disabled => &empty, + }; + + let requirements = dependencies + .iter() + .cloned() + .flat_map(|requirement| { + LoweredRequirement::from_non_workspace_requirement( + requirement, + script_dir.as_ref(), + script_sources, + script_indexes, + settings.index_locations, + ) + .map_ok(LoweredRequirement::into_inner) + }) + .collect::>()?; + let constraints = script + .metadata() + .tool + .as_ref() + .and_then(|tool| tool.uv.as_ref()) + .and_then(|uv| uv.constraint_dependencies.as_ref()) + .into_iter() + .flatten() + .cloned() + .flat_map(|requirement| { + LoweredRequirement::from_non_workspace_requirement( + requirement, + script_dir.as_ref(), + script_sources, + script_indexes, + settings.index_locations, + ) + .map_ok(LoweredRequirement::into_inner) + }) + .collect::, _>>()?; + let overrides = script + .metadata() + .tool + .as_ref() + .and_then(|tool| tool.uv.as_ref()) + .and_then(|uv| uv.override_dependencies.as_ref()) + .into_iter() + .flatten() + .cloned() + .flat_map(|requirement| { + LoweredRequirement::from_non_workspace_requirement( + requirement, + script_dir.as_ref(), + script_sources, + script_indexes, + settings.index_locations, + ) + .map_ok(LoweredRequirement::into_inner) + }) + .collect::, _>>()?; + + Ok(Some(RequirementsSpecification::from_overrides( + requirements, + constraints, + overrides, + ))) +} + /// Warn if the user provides (e.g.) an `--index-url` in a requirements file. fn warn_on_requirements_txt_setting( spec: &RequirementsSpecification, diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index eb21ecd26112..46d3b6e3df3d 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -1,5 +1,4 @@ use std::borrow::Cow; -use std::collections::BTreeMap; use std::ffi::OsString; use std::fmt::Write; use std::io::Read; @@ -18,9 +17,8 @@ use uv_cli::ExternalCommand; use uv_client::{BaseClientBuilder, Connectivity}; use uv_configuration::{ Concurrency, DevGroupsSpecification, DryRun, EditableMode, ExtrasSpecification, InstallOptions, - PreviewMode, SourceStrategy, TrustedHost, + PreviewMode, TrustedHost, }; -use uv_distribution::LoweredRequirement; use uv_fs::which::is_executable; use uv_fs::{PythonExt, Simplified}; use uv_installer::{SatisfiesResult, SitePackages}; @@ -46,9 +44,10 @@ use crate::commands::project::install_target::InstallTarget; use crate::commands::project::lock::LockMode; use crate::commands::project::lock_target::LockTarget; use crate::commands::project::{ - default_dependency_groups, update_environment, validate_project_requires_python, - DependencyGroupsTarget, EnvironmentSpecification, ProjectEnvironment, ProjectError, - ScriptEnvironment, ScriptInterpreter, UniversalState, WorkspacePython, + default_dependency_groups, script_specification, update_environment, + validate_project_requires_python, DependencyGroupsTarget, EnvironmentSpecification, + ProjectEnvironment, ProjectError, ScriptEnvironment, ScriptInterpreter, UniversalState, + WorkspacePython, }; use crate::commands::reporters::PythonDownloadReporter; use crate::commands::run::run_to_completion; @@ -318,98 +317,8 @@ pub(crate) async fn run( ); } - // Determine the working directory for the script. - let script_dir = match &script { - Pep723Item::Script(script) => std::path::absolute(&script.path)? - .parent() - .expect("script path has no parent") - .to_owned(), - Pep723Item::Stdin(..) | Pep723Item::Remote(..) => std::env::current_dir()?, - }; - let metadata = script.metadata(); - // Install the script requirements, if necessary. Otherwise, use an isolated environment. - if let Some(dependencies) = metadata.dependencies.as_ref() { - // Collect any `tool.uv.index` from the script. - let empty = Vec::default(); - let script_indexes = match settings.sources { - SourceStrategy::Enabled => metadata - .tool - .as_ref() - .and_then(|tool| tool.uv.as_ref()) - .and_then(|uv| uv.top_level.index.as_deref()) - .unwrap_or(&empty), - SourceStrategy::Disabled => &empty, - }; - - // Collect any `tool.uv.sources` from the script. - let empty = BTreeMap::default(); - let script_sources = match settings.sources { - SourceStrategy::Enabled => metadata - .tool - .as_ref() - .and_then(|tool| tool.uv.as_ref()) - .and_then(|uv| uv.sources.as_ref()) - .unwrap_or(&empty), - SourceStrategy::Disabled => &empty, - }; - - let requirements = dependencies - .iter() - .cloned() - .flat_map(|requirement| { - LoweredRequirement::from_non_workspace_requirement( - requirement, - script_dir.as_ref(), - script_sources, - script_indexes, - &settings.index_locations, - ) - .map_ok(LoweredRequirement::into_inner) - }) - .collect::>()?; - let constraints = metadata - .tool - .as_ref() - .and_then(|tool| tool.uv.as_ref()) - .and_then(|uv| uv.constraint_dependencies.as_ref()) - .into_iter() - .flatten() - .cloned() - .flat_map(|requirement| { - LoweredRequirement::from_non_workspace_requirement( - requirement, - script_dir.as_ref(), - script_sources, - script_indexes, - &settings.index_locations, - ) - .map_ok(LoweredRequirement::into_inner) - }) - .collect::, _>>()?; - let overrides = metadata - .tool - .as_ref() - .and_then(|tool| tool.uv.as_ref()) - .and_then(|uv| uv.override_dependencies.as_ref()) - .into_iter() - .flatten() - .cloned() - .flat_map(|requirement| { - LoweredRequirement::from_non_workspace_requirement( - requirement, - script_dir.as_ref(), - script_sources, - script_indexes, - &settings.index_locations, - ) - .map_ok(LoweredRequirement::into_inner) - }) - .collect::, _>>()?; - - let spec = - RequirementsSpecification::from_overrides(requirements, constraints, overrides); - + if let Some(spec) = script_specification((&script).into(), settings.as_ref().into())? { let environment = ScriptEnvironment::get_or_init( (&script).into(), python.as_deref().map(PythonRequest::parse), @@ -449,6 +358,7 @@ pub(crate) async fn run( native_tls, allow_insecure_host, cache, + DryRun::Disabled, printer, preview, ) diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index f2b92b766266..4b1ec5043e1c 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -38,8 +38,9 @@ use crate::commands::project::install_target::InstallTarget; use crate::commands::project::lock::{do_safe_lock, LockMode, LockResult}; use crate::commands::project::lock_target::LockTarget; use crate::commands::project::{ - default_dependency_groups, detect_conflicts, DependencyGroupsTarget, PlatformState, - ProjectEnvironment, ProjectError, ScriptEnvironment, UniversalState, + default_dependency_groups, detect_conflicts, script_specification, update_environment, + DependencyGroupsTarget, PlatformState, ProjectEnvironment, ProjectError, ScriptEnvironment, + UniversalState, }; use crate::commands::{diagnostics, ExitStatus}; use crate::printer::Printer; @@ -278,6 +279,59 @@ pub(crate) async fn sync( _ => {} } + // Special-case: we're syncing a script that doesn't have an associated lockfile. In that case, + // we don't create a lockfile, so the resolve-and-install semantics are different. + if let SyncTarget::Script(script) = &target { + let lockfile = LockTarget::from(script).lock_path(); + if !lockfile.is_file() { + if frozen { + return Err(anyhow::anyhow!( + "`uv sync --frozen` requires a script lockfile; run `{}` to lock the script", + format!("uv lock --script {}", script.path.user_display()).green(), + )); + } + + if locked { + return Err(anyhow::anyhow!( + "`uv sync --locked` requires a script lockfile; run `{}` to lock the script", + format!("uv lock --script {}", script.path.user_display()).green(), + )); + } + + let spec = + script_specification(Pep723ItemRef::Script(script), settings.as_ref().into())? + .unwrap_or_default(); + match update_environment( + Deref::deref(&environment).clone(), + spec, + modifications, + &settings, + &PlatformState::default(), + Box::new(DefaultResolveLogger), + Box::new(DefaultInstallLogger), + installer_metadata, + connectivity, + concurrency, + native_tls, + allow_insecure_host, + cache, + dry_run, + printer, + preview, + ) + .await + { + Ok(..) => return Ok(ExitStatus::Success), + Err(ProjectError::Operation(err)) => { + return diagnostics::OperationDiagnostic::native_tls(native_tls) + .report(err) + .map_or(Ok(ExitStatus::Failure), |err| Err(err.into())) + } + Err(err) => return Err(err.into()), + } + } + } + // Initialize any shared state. let state = UniversalState::default(); diff --git a/crates/uv/src/commands/tool/install.rs b/crates/uv/src/commands/tool/install.rs index 06e07bed95dc..672d18ee29da 100644 --- a/crates/uv/src/commands/tool/install.rs +++ b/crates/uv/src/commands/tool/install.rs @@ -8,7 +8,7 @@ use tracing::{debug, trace}; use uv_cache::{Cache, Refresh}; use uv_cache_info::Timestamp; use uv_client::{BaseClientBuilder, Connectivity}; -use uv_configuration::{Concurrency, PreviewMode, Reinstall, TrustedHost, Upgrade}; +use uv_configuration::{Concurrency, DryRun, PreviewMode, Reinstall, TrustedHost, Upgrade}; use uv_distribution_types::{NameRequirementSpecification, UnresolvedRequirementSpecification}; use uv_normalize::PackageName; use uv_pep440::{VersionSpecifier, VersionSpecifiers}; @@ -410,6 +410,7 @@ pub(crate) async fn install( native_tls, allow_insecure_host, &cache, + DryRun::Disabled, printer, preview, ) diff --git a/crates/uv/src/commands/tool/upgrade.rs b/crates/uv/src/commands/tool/upgrade.rs index b3a6ba66858a..bfd7ee194d1c 100644 --- a/crates/uv/src/commands/tool/upgrade.rs +++ b/crates/uv/src/commands/tool/upgrade.rs @@ -7,7 +7,7 @@ use tracing::debug; use uv_cache::Cache; use uv_client::{BaseClientBuilder, Connectivity}; -use uv_configuration::{Concurrency, PreviewMode, TrustedHost}; +use uv_configuration::{Concurrency, DryRun, PreviewMode, TrustedHost}; use uv_fs::CWD; use uv_normalize::PackageName; use uv_pypi_types::Requirement; @@ -352,6 +352,7 @@ async fn upgrade_tool( native_tls, allow_insecure_host, cache, + DryRun::Disabled, printer, preview, ) diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 8362d8eeee61..755ed3eeba8d 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -6726,6 +6726,128 @@ fn sync_script() -> Result<()> { + sniffio==1.3.1 "###); + // If a lockfile didn't exist already, `uv sync --script` shouldn't create one. + assert!(!context.temp_dir.child("uv.lock").exists()); + + // Modify the script's dependencies. + script.write_str(indoc! { r#" + # /// script + # requires-python = ">=3.11" + # dependencies = [ + # "anyio", + # "iniconfig", + # ] + # /// + + import anyio + "# + })?; + + uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using script environment at: [CACHE_DIR]/environments-v1/script-[HASH] + Resolved 4 packages in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + iniconfig==2.0.0 + "###); + + // Modify the `requires-python`. + script.write_str(indoc! { r#" + # /// script + # requires-python = ">=3.8, <3.11" + # dependencies = [ + # "anyio", + # "iniconfig", + # ] + # /// + + import anyio + "# + })?; + + uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Recreating script environment at: [CACHE_DIR]/environments-v1/script-[HASH] + Resolved 6 packages in [TIME] + Prepared 2 packages in [TIME] + Installed 6 packages in [TIME] + + anyio==4.3.0 + + exceptiongroup==1.2.0 + + idna==3.6 + + iniconfig==2.0.0 + + sniffio==1.3.1 + + typing-extensions==4.10.0 + "###); + + // `--locked` and `--frozen` should fail with helpful error messages. + uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").arg("--locked"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Using script environment at: [CACHE_DIR]/environments-v1/script-[HASH] + error: `uv sync --locked` requires a script lockfile; run `uv lock --script script.py` to lock the script + "###); + + uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").arg("--frozen"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Using script environment at: [CACHE_DIR]/environments-v1/script-[HASH] + error: `uv sync --frozen` requires a script lockfile; run `uv lock --script script.py` to lock the script + "###); + + Ok(()) +} + +#[test] +fn sync_locked_script() -> Result<()> { + let context = TestContext::new_with_versions(&["3.8", "3.12"]); + + let script = context.temp_dir.child("script.py"); + script.write_str(indoc! { r#" + # /// script + # requires-python = ">=3.11" + # dependencies = [ + # "anyio", + # ] + # /// + + import anyio + "# + })?; + + let filters = context + .filters() + .into_iter() + .chain(vec![( + r"environments-v1/script-\w+", + "environments-v1/script-[HASH]", + )]) + .collect::>(); + + // Lock the script. + uv_snapshot!(&filters, context.lock().arg("--script").arg("script.py"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + "###); + let lock = context.read("script.py.lock"); insta::with_settings!({ @@ -6776,6 +6898,21 @@ fn sync_script() -> Result<()> { ); }); + uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Creating script environment at: [CACHE_DIR]/environments-v1/script-[HASH] + 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 + "###); + // Modify the script's dependencies. script.write_str(indoc! { r#" # /// script @@ -6815,6 +6952,68 @@ fn sync_script() -> Result<()> { + iniconfig==2.0.0 "###); + let lock = context.read("script.py.lock"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r###" + version = 1 + requires-python = ">=3.11" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [manifest] + requirements = [ + { name = "anyio" }, + { name = "iniconfig" }, + ] + + [[package]] + name = "anyio" + version = "4.3.0" + source = { registry = "https://pypi.org/simple" } + dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6", size = 159642 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8", size = 85584 }, + ] + + [[package]] + name = "idna" + version = "3.6" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567 }, + ] + + [[package]] + name = "iniconfig" + version = "2.0.0" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, + ] + + [[package]] + name = "sniffio" + version = "1.3.1" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, + ] + "### + ); + }); + // Modify the `requires-python`. script.write_str(indoc! { r#" # /// script diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 7a4e97e7daa5..8d6be84c413b 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -1868,7 +1868,7 @@ uv sync [OPTIONS]
  • lowest-direct: Resolve the lowest compatible version of any direct dependencies, and the highest compatible version of any transitive dependencies
  • -
    --script script

    Sync the virtual environment for the specified PEP 723 Python script, rather than the current project.

    +
    --script script

    Sync the environment for a Python script, rather than the current project.

    If provided, uv will sync the dependencies based on the script’s inline metadata table, in adherence with PEP 723.