Skip to content

Commit

Permalink
Read config from venv
Browse files Browse the repository at this point in the history
  • Loading branch information
Haapalainen, Jonne committed Dec 16, 2024
1 parent 2b61a67 commit f99f84f
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 17 deletions.
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
29 changes: 29 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,26 @@ 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 = PathBuf::from(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
26 changes: 14 additions & 12 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
`$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 user-level
configuration tables, the project-level value will be used, and the user-level value will be
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

0 comments on commit f99f84f

Please sign in to comment.