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

Add keyring support for uv #1016

Merged
merged 4 commits into from
May 6, 2024
Merged
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
34 changes: 26 additions & 8 deletions rye/src/cli/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use url::Url;
use crate::bootstrap::ensure_self_venv;
use crate::config::Config;
use crate::consts::VENV_BIN;
use crate::lock::KeyringProvider;
use crate::pyproject::{BuildSystem, DependencyKind, ExpandedSources, PyProject};
use crate::sources::py::PythonVersion;
use crate::sync::{autosync, sync, SyncOptions};
Expand Down Expand Up @@ -137,7 +138,7 @@ impl ReqExtras {
};
req.version_or_url = match req.version_or_url {
Some(_) => bail!("requirement already has a version marker"),
None => Some(pep508_rs::VersionOrUrl::Url(
None => Some(VersionOrUrl::Url(
format!("git+{}{}", git, suffix).parse().with_context(|| {
format!("unable to interpret '{}{}' as git reference", git, suffix)
})?,
Expand All @@ -146,10 +147,11 @@ impl ReqExtras {
} else if let Some(ref url) = self.url {
req.version_or_url = match req.version_or_url {
Some(_) => bail!("requirement already has a version marker"),
None => Some(pep508_rs::VersionOrUrl::Url(
url.parse()
.with_context(|| format!("unable to parse '{}' as url", url))?,
)),
None => {
Some(VersionOrUrl::Url(url.parse().with_context(|| {
format!("unable to parse '{}' as url", url)
})?))
}
};
} else if let Some(ref path) = self.path {
// For hatchling build backend, it use {root:uri} for file relative path,
Expand All @@ -175,7 +177,7 @@ impl ReqExtras {
};
req.version_or_url = match req.version_or_url {
Some(_) => bail!("requirement already has a version marker"),
None => Some(pep508_rs::VersionOrUrl::Url(file_url)),
None => Some(VersionOrUrl::Url(file_url)),
};
}
for feature in self.features.iter().flat_map(|x| x.split(',')) {
Expand Down Expand Up @@ -212,6 +214,9 @@ pub struct Args {
/// Overrides the pin operator
#[arg(long)]
pin: Option<Pin>,
/// Attempt to use `keyring` for authentication for index URLs.
#[arg(long, value_enum, default_value_t)]
keyring_provider: KeyringProvider,
/// Runs `sync` even if auto-sync is disabled.
#[arg(long)]
sync: bool,
Expand Down Expand Up @@ -259,6 +264,8 @@ pub fn execute(cmd: Args) -> Result<(), Error> {
requirements.push(requirement);
}

let keyring_provider = cmd.keyring_provider;

if !cmd.excluded {
if cfg.use_uv() {
sync(SyncOptions::python_only().pyproject(None))
Expand All @@ -270,8 +277,12 @@ pub fn execute(cmd: Args) -> Result<(), Error> {
cmd.pre,
output,
&default_operator,
keyring_provider,
)?;
} else {
if keyring_provider != KeyringProvider::Disabled {
bail!("`--keyring-provider` option requires the uv backend");
}
for requirement in &mut requirements {
resolve_requirements_with_unearth(
&pyproject_toml,
Expand Down Expand Up @@ -303,7 +314,7 @@ pub fn execute(cmd: Args) -> Result<(), Error> {
}

if (cfg.autosync() && !cmd.no_sync) || cmd.sync {
autosync(&pyproject_toml, output)?;
autosync(&pyproject_toml, output, keyring_provider)?;
}

Ok(())
Expand Down Expand Up @@ -448,6 +459,7 @@ fn resolve_requirements_with_uv(
pre: bool,
output: CommandOutput,
default_operator: &Operator,
keyring_provider: KeyringProvider,
) -> Result<(), Error> {
let venv_path = pyproject_toml.venv_path();
let py_bin = get_venv_python_bin(&venv_path);
Expand All @@ -460,7 +472,13 @@ fn resolve_requirements_with_uv(
.venv(&venv_path, &py_bin, py_ver, None)?;

for req in requirements {
let mut new_req = uv.resolve(py_ver, req, pre, env::var("__RYE_UV_EXCLUDE_NEWER").ok())?;
let mut new_req = uv.resolve(
py_ver,
req,
pre,
env::var("__RYE_UV_EXCLUDE_NEWER").ok(),
keyring_provider,
)?;

// if a version or URL is already provided we just use the normalized package name but
// retain all old information.
Expand Down
6 changes: 5 additions & 1 deletion rye/src/cli/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::path::PathBuf;
use anyhow::Error;
use clap::Parser;

use crate::lock::LockOptions;
use crate::lock::{KeyringProvider, LockOptions};
use crate::sync::{sync, SyncMode, SyncOptions};
use crate::utils::CommandOutput;

Expand Down Expand Up @@ -34,6 +34,9 @@ pub struct Args {
/// Set to true to lock with sources in the lockfile.
#[arg(long)]
with_sources: bool,
/// Attempt to use `keyring` for authentication for index URLs.
#[arg(long, value_enum, default_value_t)]
keyring_provider: KeyringProvider,
/// Reset prior lock options.
#[arg(long)]
reset: bool,
Expand All @@ -57,6 +60,7 @@ pub fn execute(cmd: Args) -> Result<(), Error> {
reset: cmd.reset,
},
pyproject: cmd.pyproject,
keyring_provider: cmd.keyring_provider,
..SyncOptions::default()
})?;
Ok(())
Expand Down
6 changes: 5 additions & 1 deletion rye/src/cli/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use clap::Parser;
use pep508_rs::Requirement;

use crate::config::Config;
use crate::lock::KeyringProvider;
use crate::pyproject::{DependencyKind, PyProject};
use crate::sync::autosync;
use crate::utils::{format_requirement, CommandOutput};
Expand All @@ -27,6 +28,9 @@ pub struct Args {
/// Does not run `sync` even if auto-sync is enabled.
#[arg(long, conflicts_with = "sync")]
no_sync: bool,
/// Attempt to use `keyring` for authentication for index URLs.
#[arg(long, value_enum, default_value_t)]
keyring_provider: KeyringProvider,
/// Enables verbose diagnostics.
#[arg(short, long)]
verbose: bool,
Expand Down Expand Up @@ -65,7 +69,7 @@ pub fn execute(cmd: Args) -> Result<(), Error> {
}

if (Config::current().autosync() && !cmd.no_sync) || cmd.sync {
autosync(&pyproject_toml, output)?;
autosync(&pyproject_toml, output, cmd.keyring_provider)?;
}

Ok(())
Expand Down
6 changes: 5 additions & 1 deletion rye/src/cli/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::path::PathBuf;
use anyhow::Error;
use clap::Parser;

use crate::lock::LockOptions;
use crate::lock::{KeyringProvider, LockOptions};
use crate::sync::{sync, SyncMode, SyncOptions};
use crate::utils::CommandOutput;

Expand Down Expand Up @@ -43,6 +43,9 @@ pub struct Args {
/// Set to true to lock with sources in the lockfile.
#[arg(long)]
with_sources: bool,
/// Attempt to use `keyring` for authentication for index URLs.
#[arg(long, value_enum, default_value_t)]
keyring_provider: KeyringProvider,
/// Use this pyproject.toml file
#[arg(long, value_name = "PYPROJECT_TOML")]
pyproject: Option<PathBuf>,
Expand Down Expand Up @@ -72,6 +75,7 @@ pub fn execute(cmd: Args) -> Result<(), Error> {
with_sources: cmd.with_sources,
reset: cmd.reset,
},
keyring_provider: cmd.keyring_provider,
pyproject: cmd.pyproject,
})?;
Ok(())
Expand Down
6 changes: 5 additions & 1 deletion rye/src/cli/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use same_file::is_same_file;

use crate::config::Config;
use crate::consts::VENV_BIN;
use crate::lock::KeyringProvider;
use crate::pyproject::{locate_projects, normalize_package_name, DependencyKind, PyProject};
use crate::sync::autosync;
use crate::utils::{CommandOutput, QuietExit};
Expand All @@ -28,6 +29,9 @@ pub struct Args {
/// Use this pyproject.toml file
#[arg(long, value_name = "PYPROJECT_TOML")]
pyproject: Option<PathBuf>,
/// Attempt to use `keyring` for authentication for index URLs.
#[arg(long, value_enum, default_value_t)]
keyring_provider: KeyringProvider,
// Disable test output capture to stdout
#[arg(long = "no-capture", short = 's')]
no_capture: bool,
Expand Down Expand Up @@ -73,7 +77,7 @@ pub fn execute(cmd: Args) -> Result<(), Error> {
let has_pytest = has_pytest_dependency(&projects)?;
if has_pytest {
if Config::current().autosync() {
autosync(&projects[0], output)?;
autosync(&projects[0], output, cmd.keyring_provider)?;
} else {
bail!("pytest not installed but in dependencies. Run `rye sync`.")
}
Expand Down
24 changes: 24 additions & 0 deletions rye/src/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::sync::Arc;
use std::{env, fmt, fs};

use anyhow::{anyhow, bail, Context, Error};
use clap::ValueEnum;
use minijinja::render;
use once_cell::sync::Lazy;
use pep508_rs::Requirement;
Expand Down Expand Up @@ -59,6 +60,18 @@ impl fmt::Display for LockMode {
}
}

/// Keyring provider type to use for credential lookup.
#[derive(ValueEnum, Copy, Clone, Serialize, Debug, Default, PartialEq)]
#[value(rename_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum KeyringProvider {
/// Do not use keyring for credential lookup.
#[default]
Disabled,
/// Use the `keyring` command for credential lookup.
Subprocess,
}

/// Controls how locking should work.
#[derive(Debug, Clone, Default, Serialize)]
pub struct LockOptions {
Expand Down Expand Up @@ -128,6 +141,7 @@ impl LockOptions {
}

/// Creates lockfiles for all projects in the workspace.
#[allow(clippy::too_many_arguments)]
pub fn update_workspace_lockfile(
py_ver: &PythonVersion,
workspace: &Arc<Workspace>,
Expand All @@ -136,6 +150,7 @@ pub fn update_workspace_lockfile(
output: CommandOutput,
sources: &ExpandedSources,
lock_options: &LockOptions,
keyring_provider: KeyringProvider,
emarsden-iso marked this conversation as resolved.
Show resolved Hide resolved
) -> Result<(), Error> {
echo!(if output, "Generating {} lockfile: {}", lock_mode, lockfile.display());

Expand Down Expand Up @@ -189,6 +204,7 @@ pub fn update_workspace_lockfile(
&lock_options,
&exclusions,
true,
keyring_provider,
)?;

Ok(())
Expand Down Expand Up @@ -308,6 +324,7 @@ fn dump_dependencies(
}

/// Updates the lockfile of the current project.
#[allow(clippy::too_many_arguments)]
pub fn update_single_project_lockfile(
py_ver: &PythonVersion,
pyproject: &PyProject,
Expand All @@ -316,6 +333,7 @@ pub fn update_single_project_lockfile(
output: CommandOutput,
sources: &ExpandedSources,
lock_options: &LockOptions,
keyring_provider: KeyringProvider,
) -> Result<(), Error> {
echo!(if output, "Generating {} lockfile: {}", lock_mode, lockfile.display());

Expand Down Expand Up @@ -356,6 +374,7 @@ pub fn update_single_project_lockfile(
&lock_options,
&exclusions,
false,
keyring_provider,
)?;

Ok(())
Expand All @@ -372,6 +391,7 @@ fn generate_lockfile(
lock_options: &LockOptions,
exclusions: &HashSet<Requirement>,
no_deps: bool,
keyring_provider: KeyringProvider,
) -> Result<(), Error> {
let use_uv = Config::current().use_uv();
let scratch = tempfile::tempdir()?;
Expand Down Expand Up @@ -409,8 +429,12 @@ fn generate_lockfile(
lock_options.pre,
env::var("__RYE_UV_EXCLUDE_NEWER").ok(),
upgrade,
keyring_provider,
)?;
} else {
if keyring_provider != KeyringProvider::Disabled {
bail!("`--keyring-provider` option requires the uv backend");
}
let mut cmd = Command::new(get_pip_compile(py_ver, output)?);
// legacy pip tools requires some extra parameters
if get_pip_tools_version(py_ver) == PipToolsVersion::Legacy {
Expand Down
15 changes: 13 additions & 2 deletions rye/src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::config::Config;
use crate::consts::VENV_BIN;
use crate::lock::{
make_project_root_fragment, update_single_project_lockfile, update_workspace_lockfile,
LockMode, LockOptions,
KeyringProvider, LockMode, LockOptions,
};
use crate::piptools::{get_pip_sync, get_pip_tools_venv_path};
use crate::platform::get_toolchain_python_bin;
Expand Down Expand Up @@ -56,6 +56,8 @@ pub struct SyncOptions {
pub lock_options: LockOptions,
/// Explicit pyproject location (Only usable by PythonOnly mode)
pub pyproject: Option<PathBuf>,
/// Keyring provider to use for credential lookup.
pub keyring_provider: KeyringProvider,
}

impl SyncOptions {
Expand Down Expand Up @@ -197,6 +199,7 @@ pub fn sync(mut cmd: SyncOptions) -> Result<(), Error> {
cmd.output,
&sources,
&cmd.lock_options,
cmd.keyring_provider,
)
.context("could not write production lockfile for workspace")?;
update_workspace_lockfile(
Expand All @@ -207,6 +210,7 @@ pub fn sync(mut cmd: SyncOptions) -> Result<(), Error> {
cmd.output,
&sources,
&cmd.lock_options,
cmd.keyring_provider,
)
.context("could not write dev lockfile for workspace")?;
} else {
Expand All @@ -219,6 +223,7 @@ pub fn sync(mut cmd: SyncOptions) -> Result<(), Error> {
cmd.output,
&sources,
&cmd.lock_options,
cmd.keyring_provider,
)
.context("could not write production lockfile for project")?;
update_single_project_lockfile(
Expand All @@ -229,6 +234,7 @@ pub fn sync(mut cmd: SyncOptions) -> Result<(), Error> {
cmd.output,
&sources,
&cmd.lock_options,
cmd.keyring_provider,
)
.context("could not write dev lockfile for project")?;
}
Expand Down Expand Up @@ -310,7 +316,11 @@ pub fn sync(mut cmd: SyncOptions) -> Result<(), Error> {
}

/// Performs an autosync.
pub fn autosync(pyproject: &PyProject, output: CommandOutput) -> Result<(), Error> {
pub fn autosync(
pyproject: &PyProject,
output: CommandOutput,
keyring_provider: KeyringProvider,
) -> Result<(), Error> {
sync(SyncOptions {
output,
dev: true,
Expand All @@ -319,6 +329,7 @@ pub fn autosync(pyproject: &PyProject, output: CommandOutput) -> Result<(), Erro
no_lock: false,
lock_options: LockOptions::default(),
pyproject: Some(pyproject.toml_path().to_path_buf()),
keyring_provider,
})
}

Expand Down
Loading
Loading