Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable reading uv.toml from venv #9935

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion crates/uv-python/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ pub use crate::version_files::{
DiscoveryOptions as VersionFileDiscoveryOptions, FilePreference as VersionFilePreference,
PythonVersionFile, PYTHON_VERSIONS_FILENAME, PYTHON_VERSION_FILENAME,
};
pub use crate::virtualenv::{Error as VirtualEnvError, PyVenvConfiguration, VirtualEnvironment};
pub use crate::virtualenv::{
virtualenv_from_env, virtualenv_from_working_dir, Error as VirtualEnvError,
PyVenvConfiguration, VirtualEnvironment,
};

mod cpuinfo;
mod discovery;
Expand Down
4 changes: 2 additions & 2 deletions crates/uv-python/src/virtualenv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ pub enum Error {
/// Locate an active virtual environment by inspecting environment variables.
///
/// Supports `VIRTUAL_ENV`.
pub(crate) fn virtualenv_from_env() -> Option<PathBuf> {
pub fn virtualenv_from_env() -> Option<PathBuf> {
if let Some(dir) = env::var_os(EnvVars::VIRTUAL_ENV).filter(|value| !value.is_empty()) {
return Some(PathBuf::from(dir));
}
Expand Down Expand Up @@ -114,7 +114,7 @@ pub(crate) fn conda_environment_from_env(kind: CondaEnvironmentKind) -> Option<P
/// Searches for a `.venv` directory in the current or any parent directory. If the current
/// directory is itself a virtual environment (or a subdirectory of a virtual environment), the
/// containing virtual environment is returned.
pub(crate) fn virtualenv_from_working_dir() -> Result<Option<PathBuf>, Error> {
pub fn virtualenv_from_working_dir() -> Result<Option<PathBuf>, Error> {
let current_dir = crate::current_dir()?;

for dir in current_dir.ancestors() {
Expand Down
30 changes: 30 additions & 0 deletions crates/uv-settings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::path::{Path, PathBuf};
use etcetera::BaseStrategy;

use uv_fs::Simplified;
use uv_python::{virtualenv_from_env, virtualenv_from_working_dir};
use uv_static::EnvVars;
use uv_warnings::warn_user;

Expand Down Expand Up @@ -55,6 +56,14 @@ impl FilesystemOptions {
}
}

pub fn venv() -> Result<Option<Self>, Error> {
let Some(file) = venv_config_file() else {
return Ok(None);
};
tracing::debug!("Found venv configuration in: `{}`", file.display());
Ok(Some(Self(read_file(&file)?)))
}

pub fn system() -> Result<Option<Self>, Error> {
let Some(file) = system_config_file() else {
return Ok(None);
Expand Down Expand Up @@ -241,6 +250,27 @@ fn system_config_file() -> Option<PathBuf> {
}
}

/// Returns the path to a venv-specific configuration file if one exists.
fn venv_config_file() -> Option<PathBuf> {
// First check VIRTUAL_ENV environment variable
if let Some(venv_path) = virtualenv_from_env() {
let venv_config = venv_path.join("uv.toml");
if venv_config.is_file() {
return Some(venv_config);
}
}

// Then look for .venv in current or parent directories
if let Ok(Some(venv_path)) = virtualenv_from_working_dir() {
let venv_config = venv_path.join("uv.toml");
if venv_config.is_file() {
return Some(venv_config);
}
}

None
}

/// Load [`Options`] from a `uv.toml` file.
fn read_file(path: &Path) -> Result<Options, Error> {
let content = fs_err::read_to_string(path)?;
Expand Down
8 changes: 6 additions & 2 deletions crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,15 +124,19 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
} else if let Ok(workspace) =
Workspace::discover(&project_dir, &DiscoveryOptions::default()).await
{
// Combine in order of precedence: project -> venv -> user -> system
let project = FilesystemOptions::find(workspace.install_path())?;
let system = FilesystemOptions::system()?;
let venv = FilesystemOptions::venv()?;
let user = FilesystemOptions::user()?;
project.combine(user).combine(system)
project.combine(venv).combine(user).combine(system)
} else {
// Same precedence for non-workspace projects
let project = FilesystemOptions::find(&project_dir)?;
let system = FilesystemOptions::system()?;
let venv = FilesystemOptions::venv()?;
let user = FilesystemOptions::user()?;
project.combine(user).combine(system)
project.combine(venv).combine(user).combine(system)
};

// Parse the external command, if necessary.
Expand Down
32 changes: 17 additions & 15 deletions docs/configuration/files.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,23 @@ default = true
`[tool.uv]` section in the accompanying `pyproject.toml` will be ignored.

uv will also discover user-level configuration at `~/.config/uv/uv.toml` (or
`$XDG_CONFIG_HOME/uv/uv.toml`) on macOS and Linux, or `%APPDATA%\uv\uv.toml` on Windows; and
system-level configuration at `/etc/uv/uv.toml` (or `$XDG_CONFIG_DIRS/uv/uv.toml`) on macOS and
Linux, or `%SYSTEMDRIVE%\ProgramData\uv\uv.toml` on Windows.

User-and system-level configuration must use the `uv.toml` format, rather than the `pyproject.toml`
format, as a `pyproject.toml` is intended to define a Python _project_.

If project-, user-, and system-level configuration files are found, the settings will be merged,
with project-level configuration taking precedence over the user-level configuration, and user-level
configuration taking precedence over the system-level configuration. (If multiple system-level
configuration files are found, e.g., at both `/etc/uv/uv.toml` and `$XDG_CONFIG_DIRS/uv/uv.toml`,
only the first-discovered file will be used, with XDG taking priority.)

For example, if a string, number, or boolean is present in both the project- and user-level
configuration tables, the project-level value will be used, and the user-level value will be
`$XDG_CONFIG_HOME/uv/uv.toml`) on macOS and Linux, or `%APPDATA%\uv\uv.toml` on Windows; venv-level
configuration at within the virtual environment directory; and system-level configuration at
`/etc/uv/uv.toml` (or `$XDG_CONFIG_DIRS/uv/uv.toml`) on macOS and Linux, or
`%SYSTEMDRIVE%\ProgramData\uv\uv.toml` on Windows.

User-, venv-, and system-level configuration must use the `uv.toml` format, rather than the
`pyproject.toml` format, as a `pyproject.toml` is intended to define a Python _project_.

If project-, venv-, user-, and system-level configuration files are found, the settings will be
merged, with project-level configuration taking precedence over venv-level configuration, venv-level
configuration taking precedence over user-level configuration, and user-level configuration taking
precedence over system-level configuration. (If multiple system-level configuration files are found,
e.g., at both `/etc/uv/uv.toml` and `$XDG_CONFIG_DIRS/uv/uv.toml`, only the first-discovered file
will be used, with XDG taking priority.)

For example, if a string, number, or boolean is present in both the project- and venv-level
configuration tables, the project-level value will be used, and the venv-level value will be
ignored. If an array is present in both tables, the arrays will be concatenated, with the
project-level settings appearing earlier in the merged array.

Expand Down
Loading