Include newly-added optional dependencies in lockfile (#5686)

## Summary

When we add a new optional group in `uv add`, we never to update the
`pyproject.toml` before locking. Otherwise, we use the stale
`pyproject.toml` and omit the optional group.

Closes https://github.com/astral-sh/uv/issues/5687.
This commit is contained in:
Charlie Marsh 2024-08-01 12:41:37 -04:00 committed by GitHub
parent 64c6caa57b
commit b9b41d4a38
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 117 additions and 6 deletions

View file

@ -24,6 +24,8 @@ pub enum Error {
Parse(#[from] Box<TomlError>),
#[error("Failed to serialize `pyproject.toml`")]
Serialize(#[from] Box<toml::ser::Error>),
#[error("Failed to deserialize `pyproject.toml`")]
Deserialize(#[from] Box<toml::de::Error>),
#[error("Dependencies in `pyproject.toml` are malformed")]
MalformedDependencies,
#[error("Sources in `pyproject.toml` are malformed")]
@ -35,13 +37,18 @@ pub enum Error {
}
impl PyProjectTomlMut {
/// Initialize a `PyProjectTomlMut` from a `PyProjectToml`.
/// Initialize a [`PyProjectTomlMut`] from a [`PyProjectToml`].
pub fn from_toml(pyproject: &PyProjectToml) -> Result<Self, Error> {
Ok(Self {
doc: pyproject.raw.parse().map_err(Box::new)?,
})
}
/// Initialize a [`PyProjectToml`] from a [`PyProjectTomlMut`].
pub fn to_toml(&self) -> Result<PyProjectToml, Error> {
Ok(toml::from_str(&self.doc.to_string()).map_err(Box::new)?)
}
/// Adds a project to the workspace.
pub fn add_workspace(&mut self, path: impl AsRef<Path>) -> Result<(), Error> {
// Get or create `tool.uv.workspace.members`.

View file

@ -197,6 +197,48 @@ impl Workspace {
})
}
/// Set the [`ProjectWorkspace`] for a given workspace member.
///
/// Assumes that the project name is unchanged in the updated [`PyProjectToml`].
#[must_use]
pub fn with_pyproject_toml(
self,
package_name: &PackageName,
pyproject_toml: PyProjectToml,
) -> Option<Self> {
let mut packages = self.packages;
let member = packages.get_mut(package_name)?;
if member.root == self.install_path {
// If the member is also the workspace root, update _both_ the member entry and the
// root `pyproject.toml`.
let workspace_pyproject_toml = pyproject_toml.clone();
// Refresh the workspace sources.
let workspace_sources = workspace_pyproject_toml
.tool
.clone()
.and_then(|tool| tool.uv)
.and_then(|uv| uv.sources)
.unwrap_or_default();
// Set the `pyproject.toml` for the member.
member.pyproject_toml = pyproject_toml;
Some(Self {
pyproject_toml: workspace_pyproject_toml,
sources: workspace_sources,
packages,
..self
})
} else {
// Set the `pyproject.toml` for the member.
member.pyproject_toml = pyproject_toml;
Some(Self { packages, ..self })
}
}
/// Returns the set of requirements that include all packages in the workspace.
pub fn members_as_requirements(&self) -> Vec<Requirement> {
self.packages
@ -765,6 +807,19 @@ impl ProjectWorkspace {
&self.workspace().packages[&self.project_name]
}
/// Set the `pyproject.toml` for the current project.
///
/// Assumes that the project name is unchanged in the updated [`PyProjectToml`].
#[must_use]
pub fn with_pyproject_toml(self, pyproject_toml: PyProjectToml) -> Option<Self> {
Some(Self {
workspace: self
.workspace
.with_pyproject_toml(&self.project_name, pyproject_toml)?,
..self
})
}
/// Find the workspace for a project.
pub async fn from_project(
install_path: &Path,