Skip to content

Commit e9d17a9

Browse files
committed
Support uv --override option (#668)
1 parent 27e392b commit e9d17a9

File tree

7 files changed

+124
-12
lines changed

7 files changed

+124
-12
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ _Unreleased_
99

1010
- Use uv in `rye build` when uv is enabled. #978
1111

12+
- Support dependency overrides via `tool.rye.override-dependencies` when using uv. #668
13+
14+
1215
<!-- released start -->
1316

1417
## 0.33.0

rye/src/cli/add.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,11 +201,29 @@ pub struct Args {
201201
#[arg(short, long)]
202202
dev: bool,
203203
/// Add this as an excluded dependency that will not be installed even if it's a sub dependency.
204-
#[arg(long, conflicts_with = "dev", conflicts_with = "optional")]
204+
#[arg(
205+
long,
206+
conflicts_with = "dev",
207+
conflicts_with = "optional",
208+
conflicts_with = "override"
209+
)]
205210
excluded: bool,
206211
/// Add this to an optional dependency group.
207-
#[arg(long, conflicts_with = "dev", conflicts_with = "excluded")]
212+
#[arg(
213+
long,
214+
conflicts_with = "dev",
215+
conflicts_with = "excluded",
216+
conflicts_with = "override"
217+
)]
208218
optional: Option<String>,
219+
/// Add this as an override dependency.
220+
#[arg(
221+
long,
222+
conflicts_with = "dev",
223+
conflicts_with = "optional",
224+
conflicts_with = "excluded"
225+
)]
226+
r#override: bool,
209227
/// Include pre-releases when finding a package version.
210228
#[arg(long)]
211229
pre: bool,
@@ -240,6 +258,8 @@ pub fn execute(cmd: Args) -> Result<(), Error> {
240258
DependencyKind::Excluded
241259
} else if let Some(ref section) = cmd.optional {
242260
DependencyKind::Optional(section.into())
261+
} else if cmd.r#override {
262+
DependencyKind::Override
243263
} else {
244264
DependencyKind::Normal
245265
};

rye/src/cli/test.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,8 @@ pub fn execute(cmd: Args) -> Result<(), Error> {
137137
fn has_pytest_dependency(projects: &[PyProject]) -> Result<bool, Error> {
138138
for project in projects {
139139
for dep in project
140-
.iter_dependencies(DependencyKind::Dev)
141-
.chain(project.iter_dependencies(DependencyKind::Normal))
140+
.iter_dependencies(&DependencyKind::Dev)
141+
.chain(project.iter_dependencies(&DependencyKind::Normal))
142142
{
143143
if let Ok(req) = dep.expand(|name| std::env::var(name).ok()) {
144144
if normalize_package_name(&req.name) == "pytest" {

rye/src/lock.rs

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -178,12 +178,15 @@ pub fn update_workspace_lockfile(
178178

179179
req_file.flush()?;
180180

181-
let exclusions = find_exclusions(&projects)?;
181+
let exclusions = find_requirements(&projects, &DependencyKind::Excluded)?;
182+
let overrides = find_requirements(&projects, &DependencyKind::Override)?;
183+
let overrides_file = maybe_write_requirements_to_temp(&overrides)?;
182184
generate_lockfile(
183185
output,
184186
py_ver,
185187
&workspace.path(),
186188
req_file.path(),
189+
overrides_file.as_ref().map(|v| v.path()),
187190
lockfile,
188191
sources,
189192
&lock_options,
@@ -194,6 +197,21 @@ pub fn update_workspace_lockfile(
194197
Ok(())
195198
}
196199

200+
fn maybe_write_requirements_to_temp(
201+
requirements: &HashSet<Requirement>,
202+
) -> Result<Option<NamedTempFile>, Error> {
203+
if requirements.is_empty() {
204+
Ok(None)
205+
} else {
206+
let mut nt_file = NamedTempFile::new()?;
207+
for dep in requirements {
208+
writeln!(&nt_file, "{}", dep)?;
209+
}
210+
nt_file.flush()?;
211+
Ok(Some(nt_file))
212+
}
213+
}
214+
197215
/// Tries to restore the lock options from the given lockfile.
198216
fn restore_lock_options<'o>(
199217
lockfile: &Path,
@@ -263,10 +281,13 @@ fn collect_workspace_features(
263281
Some(features_by_project)
264282
}
265283

266-
fn find_exclusions(projects: &[PyProject]) -> Result<HashSet<Requirement>, Error> {
284+
fn find_requirements(
285+
projects: &[PyProject],
286+
kind: &DependencyKind,
287+
) -> Result<HashSet<Requirement>, Error> {
267288
let mut rv = HashSet::new();
268289
for project in projects {
269-
for dep in project.iter_dependencies(DependencyKind::Excluded) {
290+
for dep in project.iter_dependencies(kind) {
270291
rv.insert(dep.expand(|name: &str| {
271292
if name == "PROJECT_ROOT" {
272293
Some(project.workspace_path().to_string_lossy().to_string())
@@ -285,7 +306,7 @@ fn dump_dependencies(
285306
out: &mut fs::File,
286307
dep_kind: DependencyKind,
287308
) -> Result<(), Error> {
288-
for dep in pyproject.iter_dependencies(dep_kind) {
309+
for dep in pyproject.iter_dependencies(&dep_kind) {
289310
if let Ok(expanded_dep) = dep.expand(|_| {
290311
// we actually do not care what it expands to much, for as long
291312
// as the end result parses
@@ -334,23 +355,26 @@ pub fn update_single_project_lockfile(
334355
)?;
335356
}
336357

337-
for dep in pyproject.iter_dependencies(DependencyKind::Normal) {
358+
for dep in pyproject.iter_dependencies(&DependencyKind::Normal) {
338359
writeln!(req_file, "{}", dep)?;
339360
}
340361
if lock_mode == LockMode::Dev {
341-
for dep in pyproject.iter_dependencies(DependencyKind::Dev) {
362+
for dep in pyproject.iter_dependencies(&DependencyKind::Dev) {
342363
writeln!(req_file, "{}", dep)?;
343364
}
344365
}
345366

346367
req_file.flush()?;
347368

348-
let exclusions = find_exclusions(std::slice::from_ref(pyproject))?;
369+
let exclusions = find_requirements(std::slice::from_ref(pyproject), &DependencyKind::Excluded)?;
370+
let overrides = find_requirements(std::slice::from_ref(pyproject), &DependencyKind::Override)?;
371+
let overrides_file = maybe_write_requirements_to_temp(&overrides)?;
349372
generate_lockfile(
350373
output,
351374
py_ver,
352375
&pyproject.workspace_path(),
353376
req_file.path(),
377+
overrides_file.as_ref().map(|v| v.path()),
354378
lockfile,
355379
sources,
356380
&lock_options,
@@ -367,6 +391,7 @@ fn generate_lockfile(
367391
py_ver: &PythonVersion,
368392
workspace_path: &Path,
369393
requirements_file_in: &Path,
394+
overrides_file_in: Option<&Path>,
370395
lockfile: &Path,
371396
sources: &ExpandedSources,
372397
lock_options: &LockOptions,
@@ -405,12 +430,16 @@ fn generate_lockfile(
405430
.lockfile(
406431
py_ver,
407432
requirements_file_in,
433+
overrides_file_in,
408434
&requirements_file,
409435
lock_options.pre,
410436
env::var("__RYE_UV_EXCLUDE_NEWER").ok(),
411437
upgrade,
412438
)?;
413439
} else {
440+
if overrides_file_in.is_some() {
441+
bail!("dependency overrides are only supported by uv");
442+
}
414443
let mut cmd = Command::new(get_pip_compile(py_ver, output)?);
415444
// legacy pip tools requires some extra parameters
416445
if get_pip_tools_version(py_ver) == PipToolsVersion::Legacy {

rye/src/pyproject.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ pub enum DependencyKind<'a> {
5555
Normal,
5656
Dev,
5757
Excluded,
58+
Override,
5859
Optional(Cow<'a, str>),
5960
}
6061

@@ -64,6 +65,7 @@ impl<'a> fmt::Display for DependencyKind<'a> {
6465
DependencyKind::Normal => f.write_str("regular"),
6566
DependencyKind::Dev => f.write_str("dev"),
6667
DependencyKind::Excluded => f.write_str("excluded"),
68+
DependencyKind::Override => f.write_str("override"),
6769
DependencyKind::Optional(ref sect) => write!(f, "optional ({})", sect),
6870
}
6971
}
@@ -903,6 +905,7 @@ impl PyProject {
903905
DependencyKind::Normal => &mut self.doc["project"]["dependencies"],
904906
DependencyKind::Dev => &mut self.doc["tool"]["rye"]["dev-dependencies"],
905907
DependencyKind::Excluded => &mut self.doc["tool"]["rye"]["excluded-dependencies"],
908+
DependencyKind::Override => &mut self.doc["tool"]["rye"]["override-dependencies"],
906909
DependencyKind::Optional(ref section) => {
907910
// add this as a proper non-inline table if it's missing
908911
let table = &mut self.doc["project"]["optional-dependencies"];
@@ -934,6 +937,7 @@ impl PyProject {
934937
DependencyKind::Normal => &mut self.doc["project"]["dependencies"],
935938
DependencyKind::Dev => &mut self.doc["tool"]["rye"]["dev-dependencies"],
936939
DependencyKind::Excluded => &mut self.doc["tool"]["rye"]["excluded-dependencies"],
940+
DependencyKind::Override => &mut self.doc["tool"]["rye"]["override-dependencies"],
937941
DependencyKind::Optional(ref section) => {
938942
&mut self.doc["project"]["optional-dependencies"][section as &str]
939943
}
@@ -953,7 +957,7 @@ impl PyProject {
953957
/// Iterates over all dependencies.
954958
pub fn iter_dependencies(
955959
&self,
956-
kind: DependencyKind,
960+
kind: &DependencyKind,
957961
) -> impl Iterator<Item = DependencyRef> + '_ {
958962
let sec = match kind {
959963
DependencyKind::Normal => self.doc.get("project").and_then(|x| x.get("dependencies")),
@@ -967,6 +971,11 @@ impl PyProject {
967971
.get("tool")
968972
.and_then(|x| x.get("rye"))
969973
.and_then(|x| x.get("excluded-dependencies")),
974+
DependencyKind::Override => self
975+
.doc
976+
.get("tool")
977+
.and_then(|x| x.get("rye"))
978+
.and_then(|x| x.get("override-dependencies")),
970979
DependencyKind::Optional(ref section) => self
971980
.doc
972981
.get("project")

rye/src/uv.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,10 +311,12 @@ impl Uv {
311311
Ok(UvWithVenv::new(self.clone(), venv_dir, version))
312312
}
313313

314+
#[allow(clippy::too_many_arguments)]
314315
pub fn lockfile(
315316
&self,
316317
py_version: &PythonVersion,
317318
source: &Path,
319+
overrides: Option<&Path>,
318320
target: &Path,
319321
allow_prerelease: bool,
320322
exclude_newer: Option<String>,
@@ -341,6 +343,8 @@ impl Uv {
341343

342344
cmd.arg(source);
343345

346+
overrides.map(|ref value| cmd.arg("--override").arg(value));
347+
344348
let status = cmd.status().with_context(|| {
345349
format!(
346350
"Unable to run uv pip compile and generate {}",

rye/tests/test_sync.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,3 +261,50 @@ fn test_autosync_remember() {
261261
werkzeug==3.0.1
262262
"###);
263263
}
264+
265+
#[test]
266+
fn test_overrides() {
267+
// enforce werkzeug==2.3.8 when flask==3.0.0 requires Werkzeug>=3.0.0
268+
269+
let space = Space::new();
270+
space.init("my-project");
271+
272+
rye_cmd_snapshot!(space.rye_cmd().arg("add").arg("werkzeug==2.3.8").arg("--override").arg("--no-sync"), @r###"
273+
success: true
274+
exit_code: 0
275+
----- stdout -----
276+
Initializing new virtualenv in [TEMP_PATH]/project/.venv
277+
Python version: [email protected]
278+
Added werkzeug==2.3.8 as override dependency
279+
280+
----- stderr -----
281+
"###);
282+
283+
rye_cmd_snapshot!(space.rye_cmd().arg("add").arg("flask==3.0.0").arg("colorama==0.4.6"), @r###"
284+
success: true
285+
exit_code: 0
286+
----- stdout -----
287+
Added flask==3.0.0 as regular dependency
288+
Added colorama==0.4.6 as regular dependency
289+
Reusing already existing virtualenv
290+
Generating production lockfile: [TEMP_PATH]/project/requirements.lock
291+
Generating dev lockfile: [TEMP_PATH]/project/requirements-dev.lock
292+
Installing dependencies
293+
Done!
294+
295+
----- stderr -----
296+
Built 1 editable in [EXECUTION_TIME]
297+
Resolved 8 packages in [EXECUTION_TIME]
298+
Downloaded 8 packages in [EXECUTION_TIME]
299+
Installed 9 packages in [EXECUTION_TIME]
300+
+ blinker==1.7.0
301+
+ click==8.1.7
302+
+ colorama==0.4.6
303+
+ flask==3.0.0
304+
+ itsdangerous==2.1.2
305+
+ jinja2==3.1.2
306+
+ markupsafe==2.1.3
307+
+ my-project==0.1.0 (from file:[TEMP_PATH]/project)
308+
+ werkzeug==2.3.8
309+
"###);
310+
}

0 commit comments

Comments
 (0)