Refactor adding bounds to dependencies in `pyproject.toml` (#12945)

Preparation work for making the kind of bound added configurable.

No functional changes, just moving some methods around.
This commit is contained in:
konsti 2025-04-21 12:25:27 +02:00 committed by GitHub
parent 77e0fbee4f
commit 2b96dbdd49
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 178 additions and 198 deletions

View File

@ -518,21 +518,25 @@ impl PyProjectTomlMut {
Ok(added) Ok(added)
} }
/// Set the minimum version for an existing dependency in `project.dependencies`. /// Set the minimum version for an existing dependency.
pub fn set_dependency_minimum_version( pub fn set_dependency_minimum_version(
&mut self, &mut self,
dependency_type: &DependencyType,
index: usize, index: usize,
version: Version, version: Version,
) -> Result<(), Error> { ) -> Result<(), Error> {
// Get or create `project.dependencies`. let group = match dependency_type {
let dependencies = self DependencyType::Production => self.set_project_dependency_minimum_version()?,
.project()? DependencyType::Dev => self.set_dev_dependency_minimum_version()?,
.entry("dependencies") DependencyType::Optional(ref extra) => {
.or_insert(Item::Value(Value::Array(Array::new()))) self.set_optional_dependency_minimum_version(extra)?
.as_array_mut() }
.ok_or(Error::MalformedDependencies)?; DependencyType::Group(ref group) => {
self.set_dependency_group_requirement_minimum_version(group)?
}
};
let Some(req) = dependencies.get(index) else { let Some(req) = group.get(index) else {
return Err(Error::MissingDependency(index)); return Err(Error::MissingDependency(index));
}; };
@ -543,17 +547,26 @@ impl PyProjectTomlMut {
req.version_or_url = Some(VersionOrUrl::VersionSpecifier(VersionSpecifiers::from( req.version_or_url = Some(VersionOrUrl::VersionSpecifier(VersionSpecifiers::from(
VersionSpecifier::greater_than_equal_version(version), VersionSpecifier::greater_than_equal_version(version),
))); )));
dependencies.replace(index, req.to_string()); group.replace(index, req.to_string());
Ok(()) Ok(())
} }
/// Set the minimum version for an existing dependency in `project.dependencies`.
fn set_project_dependency_minimum_version(&mut self) -> Result<&mut Array, Error> {
// Get or create `project.dependencies`.
let dependencies = self
.project()?
.entry("dependencies")
.or_insert(Item::Value(Value::Array(Array::new())))
.as_array_mut()
.ok_or(Error::MalformedDependencies)?;
Ok(dependencies)
}
/// Set the minimum version for an existing dependency in `tool.uv.dev-dependencies`. /// Set the minimum version for an existing dependency in `tool.uv.dev-dependencies`.
pub fn set_dev_dependency_minimum_version( fn set_dev_dependency_minimum_version(&mut self) -> Result<&mut Array, Error> {
&mut self,
index: usize,
version: Version,
) -> Result<(), Error> {
// Get or create `tool.uv.dev-dependencies`. // Get or create `tool.uv.dev-dependencies`.
let dev_dependencies = self let dev_dependencies = self
.doc .doc
@ -570,29 +583,14 @@ impl PyProjectTomlMut {
.as_array_mut() .as_array_mut()
.ok_or(Error::MalformedDependencies)?; .ok_or(Error::MalformedDependencies)?;
let Some(req) = dev_dependencies.get(index) else { Ok(dev_dependencies)
return Err(Error::MissingDependency(index));
};
let mut req = req
.as_str()
.and_then(try_parse_requirement)
.ok_or(Error::MalformedDependencies)?;
req.version_or_url = Some(VersionOrUrl::VersionSpecifier(VersionSpecifiers::from(
VersionSpecifier::greater_than_equal_version(version),
)));
dev_dependencies.replace(index, req.to_string());
Ok(())
} }
/// Set the minimum version for an existing dependency in `project.optional-dependencies`. /// Set the minimum version for an existing dependency in `project.optional-dependencies`.
pub fn set_optional_dependency_minimum_version( fn set_optional_dependency_minimum_version(
&mut self, &mut self,
group: &ExtraName, group: &ExtraName,
index: usize, ) -> Result<&mut Array, Error> {
version: Version,
) -> Result<(), Error> {
// Get or create `project.optional-dependencies`. // Get or create `project.optional-dependencies`.
let optional_dependencies = self let optional_dependencies = self
.project()? .project()?
@ -601,48 +599,30 @@ impl PyProjectTomlMut {
.as_table_like_mut() .as_table_like_mut()
.ok_or(Error::MalformedDependencies)?; .ok_or(Error::MalformedDependencies)?;
// Try to find the existing group. // Try to find the existing extra.
let existing_group = optional_dependencies.iter_mut().find_map(|(key, value)| { let existing_key = optional_dependencies.iter().find_map(|(key, _value)| {
if ExtraName::from_str(key.get()).is_ok_and(|g| g == *group) { if ExtraName::from_str(key).is_ok_and(|g| g == *group) {
Some(value) Some(key.to_string())
} else { } else {
None None
} }
}); });
// If the group doesn't exist, create it. // If the group doesn't exist, create it.
let group = match existing_group { let group = optional_dependencies
Some(value) => value, .entry(existing_key.as_deref().unwrap_or(group.as_ref()))
None => optional_dependencies .or_insert(Item::Value(Value::Array(Array::new())))
.entry(group.as_ref())
.or_insert(Item::Value(Value::Array(Array::new()))),
}
.as_array_mut() .as_array_mut()
.ok_or(Error::MalformedDependencies)?; .ok_or(Error::MalformedDependencies)?;
let Some(req) = group.get(index) else { Ok(group)
return Err(Error::MissingDependency(index));
};
let mut req = req
.as_str()
.and_then(try_parse_requirement)
.ok_or(Error::MalformedDependencies)?;
req.version_or_url = Some(VersionOrUrl::VersionSpecifier(VersionSpecifiers::from(
VersionSpecifier::greater_than_equal_version(version),
)));
group.replace(index, req.to_string());
Ok(())
} }
/// Set the minimum version for an existing dependency in `dependency-groups`. /// Set the minimum version for an existing dependency in `dependency-groups`.
pub fn set_dependency_group_requirement_minimum_version( fn set_dependency_group_requirement_minimum_version(
&mut self, &mut self,
group: &GroupName, group: &GroupName,
index: usize, ) -> Result<&mut Array, Error> {
version: Version,
) -> Result<(), Error> {
// Get or create `dependency-groups`. // Get or create `dependency-groups`.
let dependency_groups = self let dependency_groups = self
.doc .doc
@ -652,38 +632,22 @@ impl PyProjectTomlMut {
.ok_or(Error::MalformedDependencies)?; .ok_or(Error::MalformedDependencies)?;
// Try to find the existing group. // Try to find the existing group.
let existing_group = dependency_groups.iter_mut().find_map(|(key, value)| { let existing_key = dependency_groups.iter().find_map(|(key, _value)| {
if GroupName::from_str(key.get()).is_ok_and(|g| g == *group) { if GroupName::from_str(key).is_ok_and(|g| g == *group) {
Some(value) Some(key.to_string())
} else { } else {
None None
} }
}); });
// If the group doesn't exist, create it. // If the group doesn't exist, create it.
let group = match existing_group { let group = dependency_groups
Some(value) => value, .entry(existing_key.as_deref().unwrap_or(group.as_ref()))
None => dependency_groups .or_insert(Item::Value(Value::Array(Array::new())))
.entry(group.as_ref())
.or_insert(Item::Value(Value::Array(Array::new()))),
}
.as_array_mut() .as_array_mut()
.ok_or(Error::MalformedDependencies)?; .ok_or(Error::MalformedDependencies)?;
let Some(req) = group.get(index) else { Ok(group)
return Err(Error::MissingDependency(index));
};
let mut req = req
.as_str()
.and_then(try_parse_requirement)
.ok_or(Error::MalformedDependencies)?;
req.version_or_url = Some(VersionOrUrl::VersionSpecifier(VersionSpecifiers::from(
VersionSpecifier::greater_than_equal_version(version),
)));
group.replace(index, req.to_string());
Ok(())
} }
/// Adds a source to `tool.uv.sources`. /// Adds a source to `tool.uv.sources`.

View File

@ -438,6 +438,126 @@ pub(crate) async fn add(
DependencyTarget::PyProjectToml, DependencyTarget::PyProjectToml,
), ),
}?; }?;
let edits = edits(
requirements,
&target,
editable,
&dependency_type,
raw_sources,
rev.as_deref(),
tag.as_deref(),
branch.as_deref(),
&extras,
index,
&mut toml,
)?;
// Add any indexes that were provided on the command-line, in priority order.
if !raw_sources {
let urls = IndexUrls::from_indexes(indexes);
for index in urls.defined_indexes() {
toml.add_index(index)?;
}
}
let content = toml.to_string();
// Save the modified `pyproject.toml` or script.
let modified = target.write(&content)?;
// If `--frozen`, exit early. There's no reason to lock and sync, since we don't need a `uv.lock`
// to exist at all.
if frozen {
return Ok(ExitStatus::Success);
}
// If we're modifying a script, and lockfile doesn't exist, don't create it.
if let AddTarget::Script(ref script, _) = target {
if !LockTarget::from(script).lock_path().is_file() {
writeln!(
printer.stderr(),
"Updated `{}`",
script.path.user_display().cyan()
)?;
return Ok(ExitStatus::Success);
}
}
// Store the content prior to any modifications.
let snapshot = target.snapshot().await?;
// Update the `pypackage.toml` in-memory.
let target = target.update(&content)?;
// Set the Ctrl-C handler to revert changes on exit.
let _ = ctrlc::set_handler({
let snapshot = snapshot.clone();
move || {
if modified {
let _ = snapshot.revert();
}
#[allow(clippy::exit, clippy::cast_possible_wrap)]
std::process::exit(if cfg!(windows) {
0xC000_013A_u32 as i32
} else {
130
});
}
});
// Use separate state for locking and syncing.
let lock_state = state.fork();
let sync_state = state;
match Box::pin(lock_and_sync(
target,
&mut toml,
&edits,
lock_state,
sync_state,
locked,
&dependency_type,
raw_sources,
constraints,
&settings,
&network_settings,
installer_metadata,
concurrency,
cache,
printer,
preview,
))
.await
{
Ok(()) => Ok(ExitStatus::Success),
Err(err) => {
if modified {
let _ = snapshot.revert();
}
match err {
ProjectError::Operation(err) => diagnostics::OperationDiagnostic::native_tls(network_settings.native_tls).with_hint(format!("If you want to add the package regardless of the failed resolution, provide the `{}` flag to skip locking and syncing.", "--frozen".green()))
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into())),
err => Err(err.into()),
}
}
}
}
fn edits(
requirements: Vec<Requirement>,
target: &AddTarget,
editable: Option<bool>,
dependency_type: &DependencyType,
raw_sources: bool,
rev: Option<&str>,
tag: Option<&str>,
branch: Option<&str>,
extras: &[ExtraName],
index: Option<&IndexName>,
toml: &mut PyProjectTomlMut,
) -> Result<Vec<DependencyEdit>> {
let mut edits = Vec::<DependencyEdit>::with_capacity(requirements.len()); let mut edits = Vec::<DependencyEdit>::with_capacity(requirements.len());
for mut requirement in requirements { for mut requirement in requirements {
// Add the specified extras. // Add the specified extras.
@ -461,9 +581,9 @@ pub(crate) async fn add(
false, false,
editable, editable,
index.cloned(), index.cloned(),
rev.clone(), rev.map(ToString::to_string),
tag.clone(), tag.map(ToString::to_string),
branch.clone(), branch.map(ToString::to_string),
script_dir, script_dir,
existing_sources, existing_sources,
)? )?
@ -485,9 +605,9 @@ pub(crate) async fn add(
workspace, workspace,
editable, editable,
index.cloned(), index.cloned(),
rev.clone(), rev.map(ToString::to_string),
tag.clone(), tag.map(ToString::to_string),
branch.clone(), branch.map(ToString::to_string),
project.root(), project.root(),
existing_sources, existing_sources,
)? )?
@ -609,98 +729,7 @@ pub(crate) async fn add(
edit, edit,
}); });
} }
Ok(edits)
// Add any indexes that were provided on the command-line, in priority order.
if !raw_sources {
let urls = IndexUrls::from_indexes(indexes);
for index in urls.defined_indexes() {
toml.add_index(index)?;
}
}
let content = toml.to_string();
// Save the modified `pyproject.toml` or script.
let modified = target.write(&content)?;
// If `--frozen`, exit early. There's no reason to lock and sync, since we don't need a `uv.lock`
// to exist at all.
if frozen {
return Ok(ExitStatus::Success);
}
// If we're modifying a script, and lockfile doesn't exist, don't create it.
if let AddTarget::Script(ref script, _) = target {
if !LockTarget::from(script).lock_path().is_file() {
writeln!(
printer.stderr(),
"Updated `{}`",
script.path.user_display().cyan()
)?;
return Ok(ExitStatus::Success);
}
}
// Store the content prior to any modifications.
let snapshot = target.snapshot().await?;
// Update the `pypackage.toml` in-memory.
let target = target.update(&content)?;
// Set the Ctrl-C handler to revert changes on exit.
let _ = ctrlc::set_handler({
let snapshot = snapshot.clone();
move || {
if modified {
let _ = snapshot.revert();
}
#[allow(clippy::exit, clippy::cast_possible_wrap)]
std::process::exit(if cfg!(windows) {
0xC000_013A_u32 as i32
} else {
130
});
}
});
// Use separate state for locking and syncing.
let lock_state = state.fork();
let sync_state = state;
match Box::pin(lock_and_sync(
target,
&mut toml,
&edits,
lock_state,
sync_state,
locked,
&dependency_type,
raw_sources,
constraints,
&settings,
&network_settings,
installer_metadata,
concurrency,
cache,
printer,
preview,
))
.await
{
Ok(()) => Ok(ExitStatus::Success),
Err(err) => {
if modified {
let _ = snapshot.revert();
}
match err {
ProjectError::Operation(err) => diagnostics::OperationDiagnostic::native_tls(network_settings.native_tls).with_hint(format!("If you want to add the package regardless of the failed resolution, provide the `{}` flag to skip locking and syncing.", "--frozen".green()))
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into())),
err => Err(err.into()),
}
}
}
} }
/// Re-lock and re-sync the project after a series of edits. /// Re-lock and re-sync the project after a series of edits.
@ -801,20 +830,7 @@ async fn lock_and_sync(
// For example, convert `1.2.3+local` to `1.2.3`. // For example, convert `1.2.3+local` to `1.2.3`.
let minimum = (*minimum).clone().without_local(); let minimum = (*minimum).clone().without_local();
match edit.dependency_type { toml.set_dependency_minimum_version(&edit.dependency_type, *index, minimum)?;
DependencyType::Production => {
toml.set_dependency_minimum_version(*index, minimum)?;
}
DependencyType::Dev => {
toml.set_dev_dependency_minimum_version(*index, minimum)?;
}
DependencyType::Optional(ref extra) => {
toml.set_optional_dependency_minimum_version(extra, *index, minimum)?;
}
DependencyType::Group(ref group) => {
toml.set_dependency_group_requirement_minimum_version(group, *index, minimum)?;
}
}
modified = true; modified = true;
} }