mirror of https://github.com/astral-sh/uv
Add an experimental `uv format` command (#15017)
As a frontend to Ruff's formatter. There are some interesting choices here, some of which may just be temporary: 1. We pin a default version of Ruff, so `uv format` is stable for a given uv version 2. We install Ruff from GitHub instead of PyPI, which means we don't need a Python interpreter or environment 3. We do not read the Ruff version from the dependency tree See https://github.com/astral-sh/ruff/pull/19665 for a prototype of the LSP integration.
This commit is contained in:
parent
8d6ea3f2ea
commit
e31f000da7
|
|
@ -5043,6 +5043,7 @@ dependencies = [
|
||||||
"unicode-width 0.2.1",
|
"unicode-width 0.2.1",
|
||||||
"url",
|
"url",
|
||||||
"uv-auth",
|
"uv-auth",
|
||||||
|
"uv-bin-install",
|
||||||
"uv-build-backend",
|
"uv-build-backend",
|
||||||
"uv-build-frontend",
|
"uv-build-frontend",
|
||||||
"uv-cache",
|
"uv-cache",
|
||||||
|
|
@ -5154,6 +5155,29 @@ dependencies = [
|
||||||
"uv-workspace",
|
"uv-workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uv-bin-install"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"fs-err",
|
||||||
|
"futures",
|
||||||
|
"reqwest",
|
||||||
|
"reqwest-middleware",
|
||||||
|
"reqwest-retry",
|
||||||
|
"tempfile",
|
||||||
|
"thiserror 2.0.12",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"tracing",
|
||||||
|
"url",
|
||||||
|
"uv-cache",
|
||||||
|
"uv-client",
|
||||||
|
"uv-distribution-filename",
|
||||||
|
"uv-extract",
|
||||||
|
"uv-pep440",
|
||||||
|
"uv-platform",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uv-build"
|
name = "uv-build"
|
||||||
version = "0.8.12"
|
version = "0.8.12"
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
uv-auth = { path = "crates/uv-auth" }
|
uv-auth = { path = "crates/uv-auth" }
|
||||||
|
uv-bin-install = { path = "crates/uv-bin-install" }
|
||||||
uv-build-backend = { path = "crates/uv-build-backend" }
|
uv-build-backend = { path = "crates/uv-build-backend" }
|
||||||
uv-build-frontend = { path = "crates/uv-build-frontend" }
|
uv-build-frontend = { path = "crates/uv-build-frontend" }
|
||||||
uv-cache = { path = "crates/uv-cache" }
|
uv-cache = { path = "crates/uv-cache" }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
[package]
|
||||||
|
name = "uv-bin-install"
|
||||||
|
version = "0.0.1"
|
||||||
|
edition = { workspace = true }
|
||||||
|
rust-version = { workspace = true }
|
||||||
|
homepage = { workspace = true }
|
||||||
|
documentation = { workspace = true }
|
||||||
|
repository = { workspace = true }
|
||||||
|
authors = { workspace = true }
|
||||||
|
license = { workspace = true }
|
||||||
|
description = "Binary download and installation utilities for uv"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
doctest = false
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
uv-cache = { workspace = true }
|
||||||
|
uv-client = { workspace = true }
|
||||||
|
uv-distribution-filename = { workspace = true }
|
||||||
|
uv-extract = { workspace = true }
|
||||||
|
uv-pep440 = { workspace = true }
|
||||||
|
uv-platform = { workspace = true }
|
||||||
|
fs-err = { workspace = true, features = ["tokio"] }
|
||||||
|
futures = { workspace = true }
|
||||||
|
reqwest = { workspace = true }
|
||||||
|
reqwest-middleware = { workspace = true }
|
||||||
|
reqwest-retry = { workspace = true }
|
||||||
|
tempfile = { workspace = true }
|
||||||
|
thiserror = { workspace = true }
|
||||||
|
tokio = { workspace = true }
|
||||||
|
tokio-util = { workspace = true }
|
||||||
|
tracing = { workspace = true }
|
||||||
|
url = { workspace = true }
|
||||||
|
|
||||||
|
|
@ -0,0 +1,431 @@
|
||||||
|
//! Binary download and installation utilities for uv.
|
||||||
|
//!
|
||||||
|
//! These utilities are specifically for consuming distributions that are _not_ Python packages,
|
||||||
|
//! e.g., `ruff` (which does have a Python package, but also has standalone binaries on GitHub).
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
|
use futures::TryStreamExt;
|
||||||
|
use reqwest_retry::RetryPolicy;
|
||||||
|
use std::fmt;
|
||||||
|
use thiserror::Error;
|
||||||
|
use tokio::io::{AsyncRead, ReadBuf};
|
||||||
|
use tokio_util::compat::FuturesAsyncReadCompatExt;
|
||||||
|
use tracing::debug;
|
||||||
|
use url::Url;
|
||||||
|
use uv_distribution_filename::SourceDistExtension;
|
||||||
|
|
||||||
|
use uv_cache::{Cache, CacheBucket, CacheEntry};
|
||||||
|
use uv_client::{BaseClient, is_extended_transient_error};
|
||||||
|
use uv_extract::{Error as ExtractError, stream};
|
||||||
|
use uv_pep440::Version;
|
||||||
|
use uv_platform::Platform;
|
||||||
|
|
||||||
|
/// Binary tools that can be installed.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub enum Binary {
|
||||||
|
Ruff,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Binary {
|
||||||
|
/// Get the default version for this binary.
|
||||||
|
pub fn default_version(&self) -> Version {
|
||||||
|
match self {
|
||||||
|
// TODO(zanieb): Figure out a nice way to automate updating this
|
||||||
|
Self::Ruff => Version::new([0, 12, 5]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The name of the binary.
|
||||||
|
///
|
||||||
|
/// See [`Binary::executable`] for the platform-specific executable name.
|
||||||
|
pub fn name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Ruff => "ruff",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the download URL for a specific version and platform.
|
||||||
|
pub fn download_url(
|
||||||
|
&self,
|
||||||
|
version: &Version,
|
||||||
|
platform: &str,
|
||||||
|
format: ArchiveFormat,
|
||||||
|
) -> Result<Url, Error> {
|
||||||
|
match self {
|
||||||
|
Self::Ruff => {
|
||||||
|
let url = format!(
|
||||||
|
"https://github.com/astral-sh/ruff/releases/download/{version}/ruff-{platform}.{}",
|
||||||
|
format.extension()
|
||||||
|
);
|
||||||
|
Url::parse(&url).map_err(|err| Error::UrlParse { url, source: err })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the executable name
|
||||||
|
pub fn executable(&self) -> String {
|
||||||
|
format!("{}{}", self.name(), std::env::consts::EXE_SUFFIX)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Binary {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(self.name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Archive formats for binary downloads.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum ArchiveFormat {
|
||||||
|
Zip,
|
||||||
|
TarGz,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArchiveFormat {
|
||||||
|
/// Get the file extension for this archive format.
|
||||||
|
pub fn extension(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Zip => "zip",
|
||||||
|
Self::TarGz => "tar.gz",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ArchiveFormat> for SourceDistExtension {
|
||||||
|
fn from(val: ArchiveFormat) -> Self {
|
||||||
|
match val {
|
||||||
|
ArchiveFormat::Zip => Self::Zip,
|
||||||
|
ArchiveFormat::TarGz => Self::TarGz,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Errors that can occur during binary download and installation.
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("Failed to download from: {url}")]
|
||||||
|
Download {
|
||||||
|
url: Url,
|
||||||
|
#[source]
|
||||||
|
source: reqwest_middleware::Error,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("Failed to parse URL: {url}")]
|
||||||
|
UrlParse {
|
||||||
|
url: String,
|
||||||
|
#[source]
|
||||||
|
source: url::ParseError,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("Failed to extract archive")]
|
||||||
|
Extract {
|
||||||
|
#[source]
|
||||||
|
source: ExtractError,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("Binary not found in archive at expected location: {expected}")]
|
||||||
|
BinaryNotFound { expected: PathBuf },
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
|
||||||
|
#[error("Failed to detect platform")]
|
||||||
|
Platform(#[from] uv_platform::Error),
|
||||||
|
|
||||||
|
#[error("Attempt failed after {retries} retries")]
|
||||||
|
RetriedError {
|
||||||
|
#[source]
|
||||||
|
err: Box<Error>,
|
||||||
|
retries: u32,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
/// Return the number of attempts that were made to complete this request before this error was
|
||||||
|
/// returned. Note that e.g. 3 retries equates to 4 attempts.
|
||||||
|
fn attempts(&self) -> u32 {
|
||||||
|
if let Self::RetriedError { retries, .. } = self {
|
||||||
|
return retries + 1;
|
||||||
|
}
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Install the given binary.
|
||||||
|
pub async fn bin_install(
|
||||||
|
binary: Binary,
|
||||||
|
version: &Version,
|
||||||
|
client: &BaseClient,
|
||||||
|
cache: &Cache,
|
||||||
|
reporter: &dyn Reporter,
|
||||||
|
) -> Result<PathBuf, Error> {
|
||||||
|
let platform = Platform::from_env()?;
|
||||||
|
let platform_name = platform.as_cargo_dist_triple();
|
||||||
|
|
||||||
|
// Check the cache first
|
||||||
|
let cache_entry = CacheEntry::new(
|
||||||
|
cache
|
||||||
|
.bucket(CacheBucket::Binaries)
|
||||||
|
.join(binary.name())
|
||||||
|
.join(version.to_string())
|
||||||
|
.join(&platform_name),
|
||||||
|
binary.executable(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if cache_entry.path().exists() {
|
||||||
|
return Ok(cache_entry.into_path_buf());
|
||||||
|
}
|
||||||
|
|
||||||
|
let format = if platform.os.is_windows() {
|
||||||
|
ArchiveFormat::Zip
|
||||||
|
} else {
|
||||||
|
ArchiveFormat::TarGz
|
||||||
|
};
|
||||||
|
|
||||||
|
let download_url = binary.download_url(version, &platform_name, format)?;
|
||||||
|
|
||||||
|
let cache_dir = cache_entry.dir();
|
||||||
|
fs_err::tokio::create_dir_all(&cache_dir).await?;
|
||||||
|
|
||||||
|
let path = download_and_unpack_with_retry(
|
||||||
|
binary,
|
||||||
|
version,
|
||||||
|
client,
|
||||||
|
cache,
|
||||||
|
reporter,
|
||||||
|
&platform_name,
|
||||||
|
format,
|
||||||
|
&download_url,
|
||||||
|
&cache_entry,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Add executable bit
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::fs::Permissions;
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
let permissions = fs_err::tokio::metadata(&path).await?.permissions();
|
||||||
|
if permissions.mode() & 0o111 != 0o111 {
|
||||||
|
fs_err::tokio::set_permissions(
|
||||||
|
&path,
|
||||||
|
Permissions::from_mode(permissions.mode() | 0o111),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Download and unpack a binary with retry on stream failures.
|
||||||
|
async fn download_and_unpack_with_retry(
|
||||||
|
binary: Binary,
|
||||||
|
version: &Version,
|
||||||
|
client: &BaseClient,
|
||||||
|
cache: &Cache,
|
||||||
|
reporter: &dyn Reporter,
|
||||||
|
platform_name: &str,
|
||||||
|
format: ArchiveFormat,
|
||||||
|
download_url: &Url,
|
||||||
|
cache_entry: &CacheEntry,
|
||||||
|
) -> Result<PathBuf, Error> {
|
||||||
|
let mut total_attempts = 0;
|
||||||
|
let mut retried_here = false;
|
||||||
|
let start_time = SystemTime::now();
|
||||||
|
let retry_policy = client.retry_policy();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let result = download_and_unpack(
|
||||||
|
binary,
|
||||||
|
version,
|
||||||
|
client,
|
||||||
|
cache,
|
||||||
|
reporter,
|
||||||
|
platform_name,
|
||||||
|
format,
|
||||||
|
download_url,
|
||||||
|
cache_entry,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let result = match result {
|
||||||
|
Ok(path) => Ok(path),
|
||||||
|
Err(err) => {
|
||||||
|
total_attempts += err.attempts();
|
||||||
|
let past_retries = total_attempts - 1;
|
||||||
|
|
||||||
|
if is_extended_transient_error(&err) {
|
||||||
|
let retry_decision = retry_policy.should_retry(start_time, past_retries);
|
||||||
|
if let reqwest_retry::RetryDecision::Retry { execute_after } = retry_decision {
|
||||||
|
debug!(
|
||||||
|
"Transient failure while installing {} {}; retrying...",
|
||||||
|
binary.name(),
|
||||||
|
version
|
||||||
|
);
|
||||||
|
let duration = execute_after
|
||||||
|
.duration_since(SystemTime::now())
|
||||||
|
.unwrap_or_else(|_| Duration::default());
|
||||||
|
tokio::time::sleep(duration).await;
|
||||||
|
retried_here = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if retried_here {
|
||||||
|
Err(Error::RetriedError {
|
||||||
|
err: Box::new(err),
|
||||||
|
retries: past_retries,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Download and unpackage a binary,
|
||||||
|
///
|
||||||
|
/// NOTE [`download_and_unpack_with_retry`] should be used instead.
|
||||||
|
async fn download_and_unpack(
|
||||||
|
binary: Binary,
|
||||||
|
version: &Version,
|
||||||
|
client: &BaseClient,
|
||||||
|
cache: &Cache,
|
||||||
|
reporter: &dyn Reporter,
|
||||||
|
platform_name: &str,
|
||||||
|
format: ArchiveFormat,
|
||||||
|
download_url: &Url,
|
||||||
|
cache_entry: &CacheEntry,
|
||||||
|
) -> Result<PathBuf, Error> {
|
||||||
|
// Create a temporary directory for extraction
|
||||||
|
let temp_dir = tempfile::tempdir_in(cache.bucket(CacheBucket::Binaries))?;
|
||||||
|
|
||||||
|
let response = client
|
||||||
|
.for_host(&download_url.clone().into())
|
||||||
|
.get(download_url.clone())
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|err| Error::Download {
|
||||||
|
url: download_url.clone(),
|
||||||
|
source: err,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let inner_retries = response
|
||||||
|
.extensions()
|
||||||
|
.get::<reqwest_retry::RetryCount>()
|
||||||
|
.map(|retries| retries.value());
|
||||||
|
|
||||||
|
if let Err(status_error) = response.error_for_status_ref() {
|
||||||
|
let err = Error::Download {
|
||||||
|
url: download_url.clone(),
|
||||||
|
source: reqwest_middleware::Error::from(status_error),
|
||||||
|
};
|
||||||
|
if let Some(retries) = inner_retries {
|
||||||
|
return Err(Error::RetriedError {
|
||||||
|
err: Box::new(err),
|
||||||
|
retries,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the download size from headers if available
|
||||||
|
let size = response
|
||||||
|
.headers()
|
||||||
|
.get(reqwest::header::CONTENT_LENGTH)
|
||||||
|
.and_then(|val| val.to_str().ok())
|
||||||
|
.and_then(|val| val.parse::<u64>().ok());
|
||||||
|
|
||||||
|
// Stream download directly to extraction
|
||||||
|
let reader = response
|
||||||
|
.bytes_stream()
|
||||||
|
.map_err(std::io::Error::other)
|
||||||
|
.into_async_read()
|
||||||
|
.compat();
|
||||||
|
|
||||||
|
let id = reporter.on_download_start(binary.name(), version, size);
|
||||||
|
let mut progress_reader = ProgressReader::new(reader, id, reporter);
|
||||||
|
stream::archive(&mut progress_reader, format.into(), temp_dir.path())
|
||||||
|
.await
|
||||||
|
.map_err(|e| Error::Extract { source: e })?;
|
||||||
|
reporter.on_download_complete(id);
|
||||||
|
|
||||||
|
// Find the binary in the extracted files
|
||||||
|
let extracted_binary = match format {
|
||||||
|
ArchiveFormat::Zip => {
|
||||||
|
// Windows ZIP archives contain the binary directly in the root
|
||||||
|
temp_dir.path().join(binary.executable())
|
||||||
|
}
|
||||||
|
ArchiveFormat::TarGz => {
|
||||||
|
// tar.gz archives contain the binary in a subdirectory
|
||||||
|
temp_dir
|
||||||
|
.path()
|
||||||
|
.join(format!("{}-{platform_name}", binary.name()))
|
||||||
|
.join(binary.executable())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !extracted_binary.exists() {
|
||||||
|
return Err(Error::BinaryNotFound {
|
||||||
|
expected: extracted_binary,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the binary to its final location before the temp directory is dropped
|
||||||
|
fs_err::tokio::rename(&extracted_binary, cache_entry.path()).await?;
|
||||||
|
|
||||||
|
Ok(cache_entry.path().to_path_buf())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Progress reporter for binary downloads.
|
||||||
|
pub trait Reporter: Send + Sync {
|
||||||
|
/// Called when a download starts.
|
||||||
|
fn on_download_start(&self, name: &str, version: &Version, size: Option<u64>) -> usize;
|
||||||
|
/// Called when download progress is made.
|
||||||
|
fn on_download_progress(&self, id: usize, inc: u64);
|
||||||
|
/// Called when a download completes.
|
||||||
|
fn on_download_complete(&self, id: usize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An asynchronous reader that reports progress as bytes are read.
|
||||||
|
struct ProgressReader<'a, R> {
|
||||||
|
reader: R,
|
||||||
|
index: usize,
|
||||||
|
reporter: &'a dyn Reporter,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, R> ProgressReader<'a, R> {
|
||||||
|
/// Create a new [`ProgressReader`] that wraps another reader.
|
||||||
|
fn new(reader: R, index: usize, reporter: &'a dyn Reporter) -> Self {
|
||||||
|
Self {
|
||||||
|
reader,
|
||||||
|
index,
|
||||||
|
reporter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R> AsyncRead for ProgressReader<'_, R>
|
||||||
|
where
|
||||||
|
R: AsyncRead + Unpin,
|
||||||
|
{
|
||||||
|
fn poll_read(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
buf: &mut ReadBuf<'_>,
|
||||||
|
) -> Poll<std::io::Result<()>> {
|
||||||
|
Pin::new(&mut self.as_mut().reader)
|
||||||
|
.poll_read(cx, buf)
|
||||||
|
.map_ok(|()| {
|
||||||
|
self.reporter
|
||||||
|
.on_download_progress(self.index, buf.filled().len() as u64);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -987,6 +987,8 @@ pub enum CacheBucket {
|
||||||
Environments,
|
Environments,
|
||||||
/// Cached Python downloads
|
/// Cached Python downloads
|
||||||
Python,
|
Python,
|
||||||
|
/// Downloaded tool binaries (e.g., Ruff).
|
||||||
|
Binaries,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CacheBucket {
|
impl CacheBucket {
|
||||||
|
|
@ -1010,6 +1012,7 @@ impl CacheBucket {
|
||||||
Self::Builds => "builds-v0",
|
Self::Builds => "builds-v0",
|
||||||
Self::Environments => "environments-v2",
|
Self::Environments => "environments-v2",
|
||||||
Self::Python => "python-v0",
|
Self::Python => "python-v0",
|
||||||
|
Self::Binaries => "binaries-v0",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1116,7 +1119,8 @@ impl CacheBucket {
|
||||||
| Self::Archive
|
| Self::Archive
|
||||||
| Self::Builds
|
| Self::Builds
|
||||||
| Self::Environments
|
| Self::Environments
|
||||||
| Self::Python => {
|
| Self::Python
|
||||||
|
| Self::Binaries => {
|
||||||
// Nothing to do.
|
// Nothing to do.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1135,6 +1139,7 @@ impl CacheBucket {
|
||||||
Self::Archive,
|
Self::Archive,
|
||||||
Self::Builds,
|
Self::Builds,
|
||||||
Self::Environments,
|
Self::Environments,
|
||||||
|
Self::Binaries,
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
|
|
|
||||||
|
|
@ -1006,6 +1006,21 @@ pub enum ProjectCommand {
|
||||||
Export(ExportArgs),
|
Export(ExportArgs),
|
||||||
/// Display the project's dependency tree.
|
/// Display the project's dependency tree.
|
||||||
Tree(TreeArgs),
|
Tree(TreeArgs),
|
||||||
|
/// Format Python code in the project.
|
||||||
|
///
|
||||||
|
/// Formats Python code using the Ruff formatter. By default, all Python files in the project
|
||||||
|
/// are formatted. This command has the same behavior as running `ruff format` in the project
|
||||||
|
/// root.
|
||||||
|
///
|
||||||
|
/// To check if files are formatted without modifying them, use `--check`. To see a diff of
|
||||||
|
/// formatting changes, use `--diff`.
|
||||||
|
///
|
||||||
|
/// By default, Additional arguments can be passed to Ruff after `--`.
|
||||||
|
#[command(
|
||||||
|
after_help = "Use `uv help format` for more details.",
|
||||||
|
after_long_help = ""
|
||||||
|
)]
|
||||||
|
Format(FormatArgs),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A re-implementation of `Option`, used to avoid Clap's automatic `Option` flattening in
|
/// A re-implementation of `Option`, used to avoid Clap's automatic `Option` flattening in
|
||||||
|
|
@ -4281,6 +4296,32 @@ pub struct ExportArgs {
|
||||||
pub python: Option<Maybe<String>>,
|
pub python: Option<Maybe<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
pub struct FormatArgs {
|
||||||
|
/// Check if files are formatted without applying changes.
|
||||||
|
#[arg(long)]
|
||||||
|
pub check: bool,
|
||||||
|
|
||||||
|
/// Show a diff of formatting changes without applying them.
|
||||||
|
///
|
||||||
|
/// Implies `--check`.
|
||||||
|
#[arg(long)]
|
||||||
|
pub diff: bool,
|
||||||
|
|
||||||
|
/// The version of Ruff to use for formatting.
|
||||||
|
///
|
||||||
|
/// By default, a version of Ruff pinned by uv will be used.
|
||||||
|
#[arg(long)]
|
||||||
|
pub version: Option<String>,
|
||||||
|
|
||||||
|
/// Additional arguments to pass to Ruff.
|
||||||
|
///
|
||||||
|
/// For example, use `uv format -- --line-length 100` to set the line length or
|
||||||
|
/// `uv format -- src/module/foo.py` to format a specific file.
|
||||||
|
#[arg(last = true)]
|
||||||
|
pub extra_args: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
pub struct ToolNamespace {
|
pub struct ToolNamespace {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ bitflags::bitflags! {
|
||||||
const PACKAGE_CONFLICTS = 1 << 5;
|
const PACKAGE_CONFLICTS = 1 << 5;
|
||||||
const EXTRA_BUILD_DEPENDENCIES = 1 << 6;
|
const EXTRA_BUILD_DEPENDENCIES = 1 << 6;
|
||||||
const DETECT_MODULE_CONFLICTS = 1 << 7;
|
const DETECT_MODULE_CONFLICTS = 1 << 7;
|
||||||
|
const FORMAT = 1 << 8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,6 +35,7 @@ impl PreviewFeatures {
|
||||||
Self::PACKAGE_CONFLICTS => "package-conflicts",
|
Self::PACKAGE_CONFLICTS => "package-conflicts",
|
||||||
Self::EXTRA_BUILD_DEPENDENCIES => "extra-build-dependencies",
|
Self::EXTRA_BUILD_DEPENDENCIES => "extra-build-dependencies",
|
||||||
Self::DETECT_MODULE_CONFLICTS => "detect-module-conflicts",
|
Self::DETECT_MODULE_CONFLICTS => "detect-module-conflicts",
|
||||||
|
Self::FORMAT => "format",
|
||||||
_ => panic!("`flag_as_str` can only be used for exactly one feature flag"),
|
_ => panic!("`flag_as_str` can only be used for exactly one feature flag"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -79,6 +81,7 @@ impl FromStr for PreviewFeatures {
|
||||||
"package-conflicts" => Self::PACKAGE_CONFLICTS,
|
"package-conflicts" => Self::PACKAGE_CONFLICTS,
|
||||||
"extra-build-dependencies" => Self::EXTRA_BUILD_DEPENDENCIES,
|
"extra-build-dependencies" => Self::EXTRA_BUILD_DEPENDENCIES,
|
||||||
"detect-module-conflicts" => Self::DETECT_MODULE_CONFLICTS,
|
"detect-module-conflicts" => Self::DETECT_MODULE_CONFLICTS,
|
||||||
|
"format" => Self::FORMAT,
|
||||||
_ => {
|
_ => {
|
||||||
warn_user_once!("Unknown preview feature: `{part}`");
|
warn_user_once!("Unknown preview feature: `{part}`");
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -253,6 +256,7 @@ mod tests {
|
||||||
PreviewFeatures::DETECT_MODULE_CONFLICTS.flag_as_str(),
|
PreviewFeatures::DETECT_MODULE_CONFLICTS.flag_as_str(),
|
||||||
"detect-module-conflicts"
|
"detect-module-conflicts"
|
||||||
);
|
);
|
||||||
|
assert_eq!(PreviewFeatures::FORMAT.flag_as_str(), "format");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,49 @@ impl Platform {
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert this platform to a `cargo-dist` style triple string.
|
||||||
|
pub fn as_cargo_dist_triple(&self) -> String {
|
||||||
|
use target_lexicon::{
|
||||||
|
Architecture, ArmArchitecture, OperatingSystem, Riscv64Architecture, X86_32Architecture,
|
||||||
|
};
|
||||||
|
|
||||||
|
let Self { os, arch, libc } = &self;
|
||||||
|
|
||||||
|
let arch_name = match arch.family() {
|
||||||
|
// Special cases where Display doesn't match target triple
|
||||||
|
Architecture::X86_32(X86_32Architecture::I686) => "i686".to_string(),
|
||||||
|
Architecture::Riscv64(Riscv64Architecture::Riscv64) => "riscv64gc".to_string(),
|
||||||
|
_ => arch.to_string(),
|
||||||
|
};
|
||||||
|
let vendor = match &**os {
|
||||||
|
OperatingSystem::Darwin(_) => "apple",
|
||||||
|
OperatingSystem::Windows => "pc",
|
||||||
|
_ => "unknown",
|
||||||
|
};
|
||||||
|
let os_name = match &**os {
|
||||||
|
OperatingSystem::Darwin(_) => "darwin",
|
||||||
|
_ => &os.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let abi = match (&**os, libc) {
|
||||||
|
(OperatingSystem::Windows, _) => Some("msvc".to_string()),
|
||||||
|
(OperatingSystem::Linux, Libc::Some(env)) => Some({
|
||||||
|
// Special suffix for ARM with hardware float
|
||||||
|
if matches!(arch.family(), Architecture::Arm(ArmArchitecture::Armv7)) {
|
||||||
|
format!("{env}eabihf")
|
||||||
|
} else {
|
||||||
|
env.to_string()
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"{arch_name}-{vendor}-{os_name}{abi}",
|
||||||
|
abi = abi.map(|abi| format!("-{abi}")).unwrap_or_default()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Platform {
|
impl fmt::Display for Platform {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
uv-auth = { workspace = true }
|
uv-auth = { workspace = true }
|
||||||
|
uv-bin-install = { workspace = true }
|
||||||
uv-build-backend = { workspace = true }
|
uv-build-backend = { workspace = true }
|
||||||
uv-build-frontend = { workspace = true }
|
uv-build-frontend = { workspace = true }
|
||||||
uv-cache = { workspace = true }
|
uv-cache = { workspace = true }
|
||||||
|
|
@ -90,6 +91,7 @@ rkyv = { workspace = true }
|
||||||
rustc-hash = { workspace = true }
|
rustc-hash = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
|
tar = { workspace = true }
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
textwrap = { workspace = true }
|
textwrap = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ pub(crate) use pip::tree::pip_tree;
|
||||||
pub(crate) use pip::uninstall::pip_uninstall;
|
pub(crate) use pip::uninstall::pip_uninstall;
|
||||||
pub(crate) use project::add::add;
|
pub(crate) use project::add::add;
|
||||||
pub(crate) use project::export::export;
|
pub(crate) use project::export::export;
|
||||||
|
pub(crate) use project::format::format;
|
||||||
pub(crate) use project::init::{InitKind, InitProjectKind, init};
|
pub(crate) use project::init::{InitKind, InitProjectKind, init};
|
||||||
pub(crate) use project::lock::lock;
|
pub(crate) use project::lock::lock;
|
||||||
pub(crate) use project::remove::remove;
|
pub(crate) use project::remove::remove;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
use uv_bin_install::{Binary, bin_install};
|
||||||
|
use uv_cache::Cache;
|
||||||
|
use uv_client::BaseClientBuilder;
|
||||||
|
use uv_configuration::{Preview, PreviewFeatures};
|
||||||
|
use uv_pep440::Version;
|
||||||
|
use uv_warnings::warn_user;
|
||||||
|
|
||||||
|
use crate::child::run_to_completion;
|
||||||
|
use crate::commands::ExitStatus;
|
||||||
|
use crate::commands::reporters::BinaryDownloadReporter;
|
||||||
|
use crate::printer::Printer;
|
||||||
|
use crate::settings::NetworkSettings;
|
||||||
|
|
||||||
|
/// Run the formatter.
|
||||||
|
pub(crate) async fn format(
|
||||||
|
check: bool,
|
||||||
|
diff: bool,
|
||||||
|
extra_args: Vec<String>,
|
||||||
|
version: Option<String>,
|
||||||
|
network_settings: NetworkSettings,
|
||||||
|
cache: Cache,
|
||||||
|
printer: Printer,
|
||||||
|
preview: Preview,
|
||||||
|
) -> Result<ExitStatus> {
|
||||||
|
// Check if the format feature is in preview
|
||||||
|
if !preview.is_enabled(PreviewFeatures::FORMAT) {
|
||||||
|
warn_user!(
|
||||||
|
"`uv format` is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.",
|
||||||
|
PreviewFeatures::FORMAT
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Parse version if provided
|
||||||
|
let version = version.as_deref().map(Version::from_str).transpose()?;
|
||||||
|
|
||||||
|
let client = BaseClientBuilder::new()
|
||||||
|
.retries_from_env()?
|
||||||
|
.connectivity(network_settings.connectivity)
|
||||||
|
.native_tls(network_settings.native_tls)
|
||||||
|
.allow_insecure_host(network_settings.allow_insecure_host.clone())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Get the path to Ruff, downloading it if necessary
|
||||||
|
let reporter = BinaryDownloadReporter::single(printer);
|
||||||
|
let default_version = Binary::Ruff.default_version();
|
||||||
|
let version = version.as_ref().unwrap_or(&default_version);
|
||||||
|
let ruff_path = bin_install(Binary::Ruff, version, &client, &cache, &reporter)
|
||||||
|
.await
|
||||||
|
.context("Failed to install ruff {version}")?;
|
||||||
|
|
||||||
|
let mut command = Command::new(&ruff_path);
|
||||||
|
command.arg("format");
|
||||||
|
|
||||||
|
if check {
|
||||||
|
command.arg("--check");
|
||||||
|
}
|
||||||
|
if diff {
|
||||||
|
command.arg("--diff");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any additional arguments passed after `--`
|
||||||
|
command.args(extra_args.iter());
|
||||||
|
|
||||||
|
let handle = command.spawn().context("Failed to spawn `ruff format`")?;
|
||||||
|
run_to_completion(handle).await
|
||||||
|
}
|
||||||
|
|
@ -63,6 +63,7 @@ use crate::settings::{
|
||||||
pub(crate) mod add;
|
pub(crate) mod add;
|
||||||
pub(crate) mod environment;
|
pub(crate) mod environment;
|
||||||
pub(crate) mod export;
|
pub(crate) mod export;
|
||||||
|
pub(crate) mod format;
|
||||||
pub(crate) mod init;
|
pub(crate) mod init;
|
||||||
mod install_target;
|
mod install_target;
|
||||||
pub(crate) mod lock;
|
pub(crate) mod lock;
|
||||||
|
|
|
||||||
|
|
@ -832,3 +832,32 @@ impl ColorDisplay for BuildableSource<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) struct BinaryDownloadReporter {
|
||||||
|
reporter: ProgressReporter,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BinaryDownloadReporter {
|
||||||
|
/// Initialize a [`BinaryDownloadReporter`] for a single binary download.
|
||||||
|
pub(crate) fn single(printer: Printer) -> Self {
|
||||||
|
let multi_progress = MultiProgress::with_draw_target(printer.target());
|
||||||
|
let root = multi_progress.add(ProgressBar::with_draw_target(Some(1), printer.target()));
|
||||||
|
let reporter = ProgressReporter::new(root, multi_progress, printer);
|
||||||
|
Self { reporter }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl uv_bin_install::Reporter for BinaryDownloadReporter {
|
||||||
|
fn on_download_start(&self, name: &str, version: &Version, size: Option<u64>) -> usize {
|
||||||
|
self.reporter
|
||||||
|
.on_request_start(Direction::Download, format!("{name} v{version}"), size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_download_progress(&self, id: usize, inc: u64) {
|
||||||
|
self.reporter.on_request_progress(id, inc);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_download_complete(&self, id: usize) {
|
||||||
|
self.reporter.on_request_complete(Direction::Download, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2187,6 +2187,26 @@ async fn run_project(
|
||||||
.boxed_local()
|
.boxed_local()
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
ProjectCommand::Format(args) => {
|
||||||
|
// Resolve the settings from the command-line arguments and workspace configuration.
|
||||||
|
let args = settings::FormatSettings::resolve(args, filesystem);
|
||||||
|
show_settings!(args);
|
||||||
|
|
||||||
|
// Initialize the cache.
|
||||||
|
let cache = cache.init()?;
|
||||||
|
|
||||||
|
Box::pin(commands::format(
|
||||||
|
args.check,
|
||||||
|
args.diff,
|
||||||
|
args.extra_args,
|
||||||
|
args.version,
|
||||||
|
globals.network_settings,
|
||||||
|
cache,
|
||||||
|
printer,
|
||||||
|
globals.preview,
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ use uv_cli::{
|
||||||
ToolUninstallArgs, TreeArgs, VenvArgs, VersionArgs, VersionBump, VersionFormat,
|
ToolUninstallArgs, TreeArgs, VenvArgs, VersionArgs, VersionBump, VersionFormat,
|
||||||
};
|
};
|
||||||
use uv_cli::{
|
use uv_cli::{
|
||||||
AuthorFrom, BuildArgs, ExportArgs, PublishArgs, PythonDirArgs, ResolverInstallerArgs,
|
AuthorFrom, BuildArgs, ExportArgs, FormatArgs, PublishArgs, PythonDirArgs,
|
||||||
ToolUpgradeArgs,
|
ResolverInstallerArgs, ToolUpgradeArgs,
|
||||||
options::{flag, resolver_installer_options, resolver_options},
|
options::{flag, resolver_installer_options, resolver_options},
|
||||||
};
|
};
|
||||||
use uv_client::Connectivity;
|
use uv_client::Connectivity;
|
||||||
|
|
@ -1873,6 +1873,34 @@ impl ExportSettings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The resolved settings to use for a `format` invocation.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct FormatSettings {
|
||||||
|
pub(crate) check: bool,
|
||||||
|
pub(crate) diff: bool,
|
||||||
|
pub(crate) extra_args: Vec<String>,
|
||||||
|
pub(crate) version: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FormatSettings {
|
||||||
|
/// Resolve the [`FormatSettings`] from the CLI and filesystem configuration.
|
||||||
|
pub(crate) fn resolve(args: FormatArgs, _filesystem: Option<FilesystemOptions>) -> Self {
|
||||||
|
let FormatArgs {
|
||||||
|
check,
|
||||||
|
diff,
|
||||||
|
extra_args,
|
||||||
|
version,
|
||||||
|
} = args;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
check,
|
||||||
|
diff,
|
||||||
|
extra_args,
|
||||||
|
version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The resolved settings to use for a `pip compile` invocation.
|
/// The resolved settings to use for a `pip compile` invocation.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) struct PipCompileSettings {
|
pub(crate) struct PipCompileSettings {
|
||||||
|
|
|
||||||
|
|
@ -996,6 +996,14 @@ impl TestContext {
|
||||||
command
|
command
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a `uv format` command with options shared across scenarios.
|
||||||
|
pub fn format(&self) -> Command {
|
||||||
|
let mut command = Self::new_command();
|
||||||
|
command.arg("format");
|
||||||
|
self.add_shared_options(&mut command, false);
|
||||||
|
command
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a `uv build` command with options shared across scenarios.
|
/// Create a `uv build` command with options shared across scenarios.
|
||||||
pub fn build(&self) -> Command {
|
pub fn build(&self) -> Command {
|
||||||
let mut command = Self::new_command();
|
let mut command = Self::new_command();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,273 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use assert_fs::prelude::*;
|
||||||
|
use indoc::indoc;
|
||||||
|
use insta::assert_snapshot;
|
||||||
|
|
||||||
|
use crate::common::{TestContext, uv_snapshot};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_project() -> Result<()> {
|
||||||
|
let context = TestContext::new_with_versions(&[]);
|
||||||
|
|
||||||
|
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||||
|
pyproject_toml.write_str(indoc! {r#"
|
||||||
|
[project]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = []
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
// Create an unformatted Python file
|
||||||
|
let main_py = context.temp_dir.child("main.py");
|
||||||
|
main_py.write_str(indoc! {r#"
|
||||||
|
import sys
|
||||||
|
def hello():
|
||||||
|
print( "Hello, World!" )
|
||||||
|
if __name__=="__main__":
|
||||||
|
hello( )
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.format(), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
1 file reformatted
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `uv format` is experimental and may change without warning. Pass `--preview-features format` to disable this warning.
|
||||||
|
");
|
||||||
|
|
||||||
|
// Check that the file was formatted
|
||||||
|
let formatted_content = fs_err::read_to_string(&main_py)?;
|
||||||
|
assert_snapshot!(formatted_content, @r#"
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def hello():
|
||||||
|
print("Hello, World!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
hello()
|
||||||
|
"#);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_check() -> Result<()> {
|
||||||
|
let context = TestContext::new_with_versions(&[]);
|
||||||
|
|
||||||
|
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||||
|
pyproject_toml.write_str(indoc! {r#"
|
||||||
|
[project]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = []
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
// Create an unformatted Python file
|
||||||
|
let main_py = context.temp_dir.child("main.py");
|
||||||
|
main_py.write_str(indoc! {r#"
|
||||||
|
def hello():
|
||||||
|
print( "Hello, World!" )
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.format().arg("--check"), @r"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
Would reformat: main.py
|
||||||
|
1 file would be reformatted
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `uv format` is experimental and may change without warning. Pass `--preview-features format` to disable this warning.
|
||||||
|
");
|
||||||
|
|
||||||
|
// Verify the file wasn't modified
|
||||||
|
let content = fs_err::read_to_string(&main_py)?;
|
||||||
|
assert_snapshot!(content, @r#"
|
||||||
|
def hello():
|
||||||
|
print( "Hello, World!" )
|
||||||
|
"#);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_diff() -> Result<()> {
|
||||||
|
let context = TestContext::new_with_versions(&[]);
|
||||||
|
|
||||||
|
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||||
|
pyproject_toml.write_str(indoc! {r#"
|
||||||
|
[project]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = []
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
// Create an unformatted Python file
|
||||||
|
let main_py = context.temp_dir.child("main.py");
|
||||||
|
main_py.write_str(indoc! {r#"
|
||||||
|
def hello():
|
||||||
|
print( "Hello, World!" )
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.format().arg("--diff"), @r#"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
--- main.py
|
||||||
|
+++ main.py
|
||||||
|
@@ -1,2 +1,2 @@
|
||||||
|
-def hello():
|
||||||
|
- print( "Hello, World!" )
|
||||||
|
+def hello():
|
||||||
|
+ print("Hello, World!")
|
||||||
|
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `uv format` is experimental and may change without warning. Pass `--preview-features format` to disable this warning.
|
||||||
|
1 file would be reformatted
|
||||||
|
"#);
|
||||||
|
|
||||||
|
// Verify the file wasn't modified
|
||||||
|
let content = fs_err::read_to_string(&main_py)?;
|
||||||
|
assert_snapshot!(content, @r#"
|
||||||
|
def hello():
|
||||||
|
print( "Hello, World!" )
|
||||||
|
"#);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_with_ruff_args() -> Result<()> {
|
||||||
|
let context = TestContext::new_with_versions(&[]);
|
||||||
|
|
||||||
|
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||||
|
pyproject_toml.write_str(indoc! {r#"
|
||||||
|
[project]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = []
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
// Create a Python file with a long line
|
||||||
|
let main_py = context.temp_dir.child("main.py");
|
||||||
|
main_py.write_str(indoc! {r#"
|
||||||
|
def hello():
|
||||||
|
print("This is a very long line that should normally be wrapped by the formatter but we will configure it to have a longer line length")
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
// Run format with custom line length
|
||||||
|
uv_snapshot!(context.filters(), context.format().arg("--").arg("main.py").arg("--line-length").arg("200"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
1 file left unchanged
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `uv format` is experimental and may change without warning. Pass `--preview-features format` to disable this warning.
|
||||||
|
");
|
||||||
|
|
||||||
|
// Check that the line wasn't wrapped (since we set a long line length)
|
||||||
|
let formatted_content = fs_err::read_to_string(&main_py)?;
|
||||||
|
assert_snapshot!(formatted_content, @r#"
|
||||||
|
def hello():
|
||||||
|
print("This is a very long line that should normally be wrapped by the formatter but we will configure it to have a longer line length")
|
||||||
|
"#);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_specific_files() -> Result<()> {
|
||||||
|
let context = TestContext::new_with_versions(&[]);
|
||||||
|
|
||||||
|
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||||
|
pyproject_toml.write_str(indoc! {r#"
|
||||||
|
[project]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = []
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
// Create multiple unformatted Python files
|
||||||
|
let main_py = context.temp_dir.child("main.py");
|
||||||
|
main_py.write_str(indoc! {r#"
|
||||||
|
def main():
|
||||||
|
print( "Main" )
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
let utils_py = context.temp_dir.child("utils.py");
|
||||||
|
utils_py.write_str(indoc! {r#"
|
||||||
|
def utils():
|
||||||
|
print( "utils" )
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.format().arg("--").arg("main.py"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
1 file reformatted
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `uv format` is experimental and may change without warning. Pass `--preview-features format` to disable this warning.
|
||||||
|
");
|
||||||
|
|
||||||
|
let main_content = fs_err::read_to_string(&main_py)?;
|
||||||
|
assert_snapshot!(main_content, @r#"
|
||||||
|
def main():
|
||||||
|
print("Main")
|
||||||
|
"#);
|
||||||
|
|
||||||
|
// Unchanged
|
||||||
|
let utils_content = fs_err::read_to_string(&utils_py)?;
|
||||||
|
assert_snapshot!(utils_content, @r#"
|
||||||
|
def utils():
|
||||||
|
print( "utils" )
|
||||||
|
"#);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_version_option() -> Result<()> {
|
||||||
|
let context = TestContext::new_with_versions(&[]);
|
||||||
|
|
||||||
|
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||||
|
pyproject_toml.write_str(indoc! {r#"
|
||||||
|
[project]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
dependencies = []
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
let main_py = context.temp_dir.child("main.py");
|
||||||
|
main_py.write_str(indoc! {r"
|
||||||
|
def hello(): pass
|
||||||
|
"})?;
|
||||||
|
|
||||||
|
// Run format with specific Ruff version
|
||||||
|
// TODO(zanieb): It'd be nice to assert on the version used here somehow? Maybe we should emit
|
||||||
|
// the version we're using to stderr? Alas there's not a way to get the Ruff version from the
|
||||||
|
// format command :)
|
||||||
|
uv_snapshot!(context.filters(), context.format().arg("--version").arg("0.8.2"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
1 file reformatted
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `uv format` is experimental and may change without warning. Pass `--preview-features format` to disable this warning.
|
||||||
|
");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -25,6 +25,7 @@ fn help() {
|
||||||
lock Update the project's lockfile
|
lock Update the project's lockfile
|
||||||
export Export the project's lockfile to an alternate format
|
export Export the project's lockfile to an alternate format
|
||||||
tree Display the project's dependency tree
|
tree Display the project's dependency tree
|
||||||
|
format Format Python code in the project
|
||||||
tool Run and install commands provided by Python packages
|
tool Run and install commands provided by Python packages
|
||||||
python Manage Python versions and installations
|
python Manage Python versions and installations
|
||||||
pip Manage Python packages with a pip-compatible interface
|
pip Manage Python packages with a pip-compatible interface
|
||||||
|
|
@ -105,6 +106,7 @@ fn help_flag() {
|
||||||
lock Update the project's lockfile
|
lock Update the project's lockfile
|
||||||
export Export the project's lockfile to an alternate format
|
export Export the project's lockfile to an alternate format
|
||||||
tree Display the project's dependency tree
|
tree Display the project's dependency tree
|
||||||
|
format Format Python code in the project
|
||||||
tool Run and install commands provided by Python packages
|
tool Run and install commands provided by Python packages
|
||||||
python Manage Python versions and installations
|
python Manage Python versions and installations
|
||||||
pip Manage Python packages with a pip-compatible interface
|
pip Manage Python packages with a pip-compatible interface
|
||||||
|
|
@ -183,6 +185,7 @@ fn help_short_flag() {
|
||||||
lock Update the project's lockfile
|
lock Update the project's lockfile
|
||||||
export Export the project's lockfile to an alternate format
|
export Export the project's lockfile to an alternate format
|
||||||
tree Display the project's dependency tree
|
tree Display the project's dependency tree
|
||||||
|
format Format Python code in the project
|
||||||
tool Run and install commands provided by Python packages
|
tool Run and install commands provided by Python packages
|
||||||
python Manage Python versions and installations
|
python Manage Python versions and installations
|
||||||
pip Manage Python packages with a pip-compatible interface
|
pip Manage Python packages with a pip-compatible interface
|
||||||
|
|
@ -880,6 +883,7 @@ fn help_unknown_subcommand() {
|
||||||
lock
|
lock
|
||||||
export
|
export
|
||||||
tree
|
tree
|
||||||
|
format
|
||||||
tool
|
tool
|
||||||
python
|
python
|
||||||
pip
|
pip
|
||||||
|
|
@ -907,6 +911,7 @@ fn help_unknown_subcommand() {
|
||||||
lock
|
lock
|
||||||
export
|
export
|
||||||
tree
|
tree
|
||||||
|
format
|
||||||
tool
|
tool
|
||||||
python
|
python
|
||||||
pip
|
pip
|
||||||
|
|
@ -963,6 +968,7 @@ fn help_with_global_option() {
|
||||||
lock Update the project's lockfile
|
lock Update the project's lockfile
|
||||||
export Export the project's lockfile to an alternate format
|
export Export the project's lockfile to an alternate format
|
||||||
tree Display the project's dependency tree
|
tree Display the project's dependency tree
|
||||||
|
format Format Python code in the project
|
||||||
tool Run and install commands provided by Python packages
|
tool Run and install commands provided by Python packages
|
||||||
python Manage Python versions and installations
|
python Manage Python versions and installations
|
||||||
pip Manage Python packages with a pip-compatible interface
|
pip Manage Python packages with a pip-compatible interface
|
||||||
|
|
@ -1084,6 +1090,7 @@ fn help_with_no_pager() {
|
||||||
lock Update the project's lockfile
|
lock Update the project's lockfile
|
||||||
export Export the project's lockfile to an alternate format
|
export Export the project's lockfile to an alternate format
|
||||||
tree Display the project's dependency tree
|
tree Display the project's dependency tree
|
||||||
|
format Format Python code in the project
|
||||||
tool Run and install commands provided by Python packages
|
tool Run and install commands provided by Python packages
|
||||||
python Manage Python versions and installations
|
python Manage Python versions and installations
|
||||||
pip Manage Python packages with a pip-compatible interface
|
pip Manage Python packages with a pip-compatible interface
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,9 @@ mod edit;
|
||||||
#[cfg(all(feature = "python", feature = "pypi"))]
|
#[cfg(all(feature = "python", feature = "pypi"))]
|
||||||
mod export;
|
mod export;
|
||||||
|
|
||||||
|
#[cfg(all(feature = "python", feature = "pypi"))]
|
||||||
|
mod format;
|
||||||
|
|
||||||
mod help;
|
mod help;
|
||||||
|
|
||||||
#[cfg(all(feature = "python", feature = "pypi", feature = "git"))]
|
#[cfg(all(feature = "python", feature = "pypi", feature = "git"))]
|
||||||
|
|
|
||||||
|
|
@ -7720,7 +7720,7 @@ fn preview_features() {
|
||||||
show_settings: true,
|
show_settings: true,
|
||||||
preview: Preview {
|
preview: Preview {
|
||||||
flags: PreviewFeatures(
|
flags: PreviewFeatures(
|
||||||
PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | PACKAGE_CONFLICTS | EXTRA_BUILD_DEPENDENCIES | DETECT_MODULE_CONFLICTS,
|
PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | PACKAGE_CONFLICTS | EXTRA_BUILD_DEPENDENCIES | DETECT_MODULE_CONFLICTS | FORMAT,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
python_preference: Managed,
|
python_preference: Managed,
|
||||||
|
|
@ -7946,7 +7946,7 @@ fn preview_features() {
|
||||||
show_settings: true,
|
show_settings: true,
|
||||||
preview: Preview {
|
preview: Preview {
|
||||||
flags: PreviewFeatures(
|
flags: PreviewFeatures(
|
||||||
PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | PACKAGE_CONFLICTS | EXTRA_BUILD_DEPENDENCIES | DETECT_MODULE_CONFLICTS,
|
PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | PACKAGE_CONFLICTS | EXTRA_BUILD_DEPENDENCIES | DETECT_MODULE_CONFLICTS | FORMAT,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
python_preference: Managed,
|
python_preference: Managed,
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ The following preview features are available:
|
||||||
[installing `python` and `python3` executables](./python-versions.md#installing-python-executables).
|
[installing `python` and `python3` executables](./python-versions.md#installing-python-executables).
|
||||||
- `python-upgrade`: Allows
|
- `python-upgrade`: Allows
|
||||||
[transparent Python version upgrades](./python-versions.md#upgrading-python-versions).
|
[transparent Python version upgrades](./python-versions.md#upgrading-python-versions).
|
||||||
|
- `format`: Allows using `uv format`.
|
||||||
|
|
||||||
## Disabling preview features
|
## Disabling preview features
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ uv [OPTIONS] <COMMAND>
|
||||||
<dt><a href="#uv-lock"><code>uv lock</code></a></dt><dd><p>Update the project's lockfile</p></dd>
|
<dt><a href="#uv-lock"><code>uv lock</code></a></dt><dd><p>Update the project's lockfile</p></dd>
|
||||||
<dt><a href="#uv-export"><code>uv export</code></a></dt><dd><p>Export the project's lockfile to an alternate format</p></dd>
|
<dt><a href="#uv-export"><code>uv export</code></a></dt><dd><p>Export the project's lockfile to an alternate format</p></dd>
|
||||||
<dt><a href="#uv-tree"><code>uv tree</code></a></dt><dd><p>Display the project's dependency tree</p></dd>
|
<dt><a href="#uv-tree"><code>uv tree</code></a></dt><dd><p>Display the project's dependency tree</p></dd>
|
||||||
|
<dt><a href="#uv-format"><code>uv format</code></a></dt><dd><p>Format Python code in the project</p></dd>
|
||||||
<dt><a href="#uv-tool"><code>uv tool</code></a></dt><dd><p>Run and install commands provided by Python packages</p></dd>
|
<dt><a href="#uv-tool"><code>uv tool</code></a></dt><dd><p>Run and install commands provided by Python packages</p></dd>
|
||||||
<dt><a href="#uv-python"><code>uv python</code></a></dt><dd><p>Manage Python versions and installations</p></dd>
|
<dt><a href="#uv-python"><code>uv python</code></a></dt><dd><p>Manage Python versions and installations</p></dd>
|
||||||
<dt><a href="#uv-pip"><code>uv pip</code></a></dt><dd><p>Manage Python packages with a pip-compatible interface</p></dd>
|
<dt><a href="#uv-pip"><code>uv pip</code></a></dt><dd><p>Manage Python packages with a pip-compatible interface</p></dd>
|
||||||
|
|
@ -1841,6 +1842,81 @@ interpreter. Use <code>--universal</code> to display the tree for all platforms,
|
||||||
<p>You can configure fine-grained logging using the <code>RUST_LOG</code> environment variable. (<a href="https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives">https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives</a>)</p>
|
<p>You can configure fine-grained logging using the <code>RUST_LOG</code> environment variable. (<a href="https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives">https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives</a>)</p>
|
||||||
</dd></dl>
|
</dd></dl>
|
||||||
|
|
||||||
|
## uv format
|
||||||
|
|
||||||
|
Format Python code in the project.
|
||||||
|
|
||||||
|
Formats Python code using the Ruff formatter. By default, all Python files in the project are formatted. This command has the same behavior as running `ruff format` in the project root.
|
||||||
|
|
||||||
|
To check if files are formatted without modifying them, use `--check`. To see a diff of formatting changes, use `--diff`.
|
||||||
|
|
||||||
|
By default, Additional arguments can be passed to Ruff after `--`.
|
||||||
|
|
||||||
|
<h3 class="cli-reference">Usage</h3>
|
||||||
|
|
||||||
|
```
|
||||||
|
uv format [OPTIONS] [-- <EXTRA_ARGS>...]
|
||||||
|
```
|
||||||
|
|
||||||
|
<h3 class="cli-reference">Arguments</h3>
|
||||||
|
|
||||||
|
<dl class="cli-reference"><dt id="uv-format--extra_args"><a href="#uv-format--extra_args"<code>EXTRA_ARGS</code></a></dt><dd><p>Additional arguments to pass to Ruff.</p>
|
||||||
|
<p>For example, use <code>uv format -- --line-length 100</code> to set the line length or <code>uv format -- src/module/foo.py</code> to format a specific file.</p>
|
||||||
|
</dd></dl>
|
||||||
|
|
||||||
|
<h3 class="cli-reference">Options</h3>
|
||||||
|
|
||||||
|
<dl class="cli-reference"><dt id="uv-format--allow-insecure-host"><a href="#uv-format--allow-insecure-host"><code>--allow-insecure-host</code></a>, <code>--trusted-host</code> <i>allow-insecure-host</i></dt><dd><p>Allow insecure connections to a host.</p>
|
||||||
|
<p>Can be provided multiple times.</p>
|
||||||
|
<p>Expects to receive either a hostname (e.g., <code>localhost</code>), a host-port pair (e.g., <code>localhost:8080</code>), or a URL (e.g., <code>https://localhost</code>).</p>
|
||||||
|
<p>WARNING: Hosts included in this list will not be verified against the system's certificate store. Only use <code>--allow-insecure-host</code> in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.</p>
|
||||||
|
<p>May also be set with the <code>UV_INSECURE_HOST</code> environment variable.</p></dd><dt id="uv-format--cache-dir"><a href="#uv-format--cache-dir"><code>--cache-dir</code></a> <i>cache-dir</i></dt><dd><p>Path to the cache directory.</p>
|
||||||
|
<p>Defaults to <code>$XDG_CACHE_HOME/uv</code> or <code>$HOME/.cache/uv</code> on macOS and Linux, and <code>%LOCALAPPDATA%\uv\cache</code> on Windows.</p>
|
||||||
|
<p>To view the location of the cache directory, run <code>uv cache dir</code>.</p>
|
||||||
|
<p>May also be set with the <code>UV_CACHE_DIR</code> environment variable.</p></dd><dt id="uv-format--check"><a href="#uv-format--check"><code>--check</code></a></dt><dd><p>Check if files are formatted without applying changes</p>
|
||||||
|
</dd><dt id="uv-format--color"><a href="#uv-format--color"><code>--color</code></a> <i>color-choice</i></dt><dd><p>Control the use of color in output.</p>
|
||||||
|
<p>By default, uv will automatically detect support for colors when writing to a terminal.</p>
|
||||||
|
<p>Possible values:</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>auto</code>: Enables colored output only when the output is going to a terminal or TTY with support</li>
|
||||||
|
<li><code>always</code>: Enables colored output regardless of the detected environment</li>
|
||||||
|
<li><code>never</code>: Disables colored output</li>
|
||||||
|
</ul></dd><dt id="uv-format--config-file"><a href="#uv-format--config-file"><code>--config-file</code></a> <i>config-file</i></dt><dd><p>The path to a <code>uv.toml</code> file to use for configuration.</p>
|
||||||
|
<p>While uv configuration can be included in a <code>pyproject.toml</code> file, it is not allowed in this context.</p>
|
||||||
|
<p>May also be set with the <code>UV_CONFIG_FILE</code> environment variable.</p></dd><dt id="uv-format--diff"><a href="#uv-format--diff"><code>--diff</code></a></dt><dd><p>Show a diff of formatting changes without applying them.</p>
|
||||||
|
<p>Implies <code>--check</code>.</p>
|
||||||
|
</dd><dt id="uv-format--directory"><a href="#uv-format--directory"><code>--directory</code></a> <i>directory</i></dt><dd><p>Change to the given directory prior to running the command.</p>
|
||||||
|
<p>Relative paths are resolved with the given directory as the base.</p>
|
||||||
|
<p>See <code>--project</code> to only change the project root directory.</p>
|
||||||
|
</dd><dt id="uv-format--help"><a href="#uv-format--help"><code>--help</code></a>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
|
||||||
|
</dd><dt id="uv-format--managed-python"><a href="#uv-format--managed-python"><code>--managed-python</code></a></dt><dd><p>Require use of uv-managed Python versions.</p>
|
||||||
|
<p>By default, uv prefers using Python versions it manages. However, it will use system Python versions if a uv-managed Python is not installed. This option disables use of system Python versions.</p>
|
||||||
|
<p>May also be set with the <code>UV_MANAGED_PYTHON</code> environment variable.</p></dd><dt id="uv-format--native-tls"><a href="#uv-format--native-tls"><code>--native-tls</code></a></dt><dd><p>Whether to load TLS certificates from the platform's native certificate store.</p>
|
||||||
|
<p>By default, uv loads certificates from the bundled <code>webpki-roots</code> crate. The <code>webpki-roots</code> are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).</p>
|
||||||
|
<p>However, in some cases, you may want to use the platform's native certificate store, especially if you're relying on a corporate trust root (e.g., for a mandatory proxy) that's included in your system's certificate store.</p>
|
||||||
|
<p>May also be set with the <code>UV_NATIVE_TLS</code> environment variable.</p></dd><dt id="uv-format--no-cache"><a href="#uv-format--no-cache"><code>--no-cache</code></a>, <code>--no-cache-dir</code>, <code>-n</code></dt><dd><p>Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation</p>
|
||||||
|
<p>May also be set with the <code>UV_NO_CACHE</code> environment variable.</p></dd><dt id="uv-format--no-config"><a href="#uv-format--no-config"><code>--no-config</code></a></dt><dd><p>Avoid discovering configuration files (<code>pyproject.toml</code>, <code>uv.toml</code>).</p>
|
||||||
|
<p>Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.</p>
|
||||||
|
<p>May also be set with the <code>UV_NO_CONFIG</code> environment variable.</p></dd><dt id="uv-format--no-managed-python"><a href="#uv-format--no-managed-python"><code>--no-managed-python</code></a></dt><dd><p>Disable use of uv-managed Python versions.</p>
|
||||||
|
<p>Instead, uv will search for a suitable Python version on the system.</p>
|
||||||
|
<p>May also be set with the <code>UV_NO_MANAGED_PYTHON</code> environment variable.</p></dd><dt id="uv-format--no-progress"><a href="#uv-format--no-progress"><code>--no-progress</code></a></dt><dd><p>Hide all progress outputs.</p>
|
||||||
|
<p>For example, spinners or progress bars.</p>
|
||||||
|
<p>May also be set with the <code>UV_NO_PROGRESS</code> environment variable.</p></dd><dt id="uv-format--no-python-downloads"><a href="#uv-format--no-python-downloads"><code>--no-python-downloads</code></a></dt><dd><p>Disable automatic downloads of Python.</p>
|
||||||
|
</dd><dt id="uv-format--offline"><a href="#uv-format--offline"><code>--offline</code></a></dt><dd><p>Disable network access.</p>
|
||||||
|
<p>When disabled, uv will only use locally cached data and locally available files.</p>
|
||||||
|
<p>May also be set with the <code>UV_OFFLINE</code> environment variable.</p></dd><dt id="uv-format--project"><a href="#uv-format--project"><code>--project</code></a> <i>project</i></dt><dd><p>Run the command within the given project directory.</p>
|
||||||
|
<p>All <code>pyproject.toml</code>, <code>uv.toml</code>, and <code>.python-version</code> files will be discovered by walking up the directory tree from the project root, as will the project's virtual environment (<code>.venv</code>).</p>
|
||||||
|
<p>Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.</p>
|
||||||
|
<p>See <code>--directory</code> to change the working directory entirely.</p>
|
||||||
|
<p>This setting has no effect when used in the <code>uv pip</code> interface.</p>
|
||||||
|
<p>May also be set with the <code>UV_PROJECT</code> environment variable.</p></dd><dt id="uv-format--quiet"><a href="#uv-format--quiet"><code>--quiet</code></a>, <code>-q</code></dt><dd><p>Use quiet output.</p>
|
||||||
|
<p>Repeating this option, e.g., <code>-qq</code>, will enable a silent mode in which uv will write no output to stdout.</p>
|
||||||
|
</dd><dt id="uv-format--verbose"><a href="#uv-format--verbose"><code>--verbose</code></a>, <code>-v</code></dt><dd><p>Use verbose output.</p>
|
||||||
|
<p>You can configure fine-grained logging using the <code>RUST_LOG</code> environment variable. (<a href="https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives">https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives</a>)</p>
|
||||||
|
</dd><dt id="uv-format--version"><a href="#uv-format--version"><code>--version</code></a> <i>version</i></dt><dd><p>The version of Ruff to use for formatting.</p>
|
||||||
|
<p>By default, a version of Ruff pinned by uv will be used.</p>
|
||||||
|
</dd></dl>
|
||||||
|
|
||||||
## uv tool
|
## uv tool
|
||||||
|
|
||||||
Run and install commands provided by Python packages
|
Run and install commands provided by Python packages
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue