Improve static metadata extraction for Poetry projects (#4182)

## Summary

Adds handling for a few cases to improve interoperability with Poetry:

- If the `project` schema is invalid, we now raise a hard error, rather
than treating the metadata as dynamic and then falling back to the build
backend. This could cause problems, I'm not sure. It's stricter than
before.
- If the project contains `tool.poetry` but omits
`project.dependencies`, we now treat it as dynamic. We could go even
further and treat _any_ Poetry project as dynamic, but then we'd be
ignoring user-declared dependencies, which is also confusing.

Closes https://github.com/astral-sh/uv/issues/4142.
This commit is contained in:
Charlie Marsh 2024-06-10 07:26:38 -07:00 committed by GitHub
parent c6da4f15b7
commit 125a4b220e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 166 additions and 14 deletions

View file

@ -60,6 +60,8 @@ pub enum MetadataError {
UnsupportedMetadataVersion(String),
#[error("The following field was marked as dynamic: {0}")]
DynamicField(&'static str),
#[error("The project uses Poetry's syntax to declare its dependencies, despite including a `project` table in `pyproject.toml`")]
PoetrySyntax,
}
impl From<Pep508Error<VerbatimParsedUrl>> for MetadataError {
@ -210,6 +212,15 @@ impl Metadata23 {
}
}
// If dependencies are declared with Poetry, and `project.dependencies` is omitted, treat
// the dependencies as dynamic. The inclusion of a `project` table without defining
// `project.dependencies` is almost certainly an error.
if project.dependencies.is_none()
&& pyproject_toml.tool.and_then(|tool| tool.poetry).is_some()
{
return Err(MetadataError::PoetrySyntax);
}
let name = project.name;
let version = project
.version
@ -257,11 +268,11 @@ impl Metadata23 {
}
/// A `pyproject.toml` as specified in PEP 517.
#[derive(Deserialize, Debug, Clone)]
#[derive(Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
struct PyProjectToml {
/// Project metadata
project: Option<Project>,
tool: Option<Tool>,
}
/// PEP 621 project metadata.
@ -270,7 +281,7 @@ struct PyProjectToml {
/// relevant for dependency resolution.
///
/// See <https://packaging.python.org/en/latest/specifications/pyproject-toml>.
#[derive(Deserialize, Debug, Clone)]
#[derive(Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
struct Project {
/// The name of the project
@ -288,6 +299,17 @@ struct Project {
dynamic: Option<Vec<String>>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
struct Tool {
poetry: Option<ToolPoetry>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
#[allow(clippy::empty_structs_with_brackets)]
struct ToolPoetry {}
/// Python Package Metadata 1.0 and later as specified in
/// <https://peps.python.org/pep-0241/>.
///
@ -369,6 +391,15 @@ impl RequiresDist {
}
}
// If dependencies are declared with Poetry, and `project.dependencies` is omitted, treat
// the dependencies as dynamic. The inclusion of a `project` table without defining
// `project.dependencies` is almost certainly an error.
if project.dependencies.is_none()
&& pyproject_toml.tool.and_then(|tool| tool.poetry).is_some()
{
return Err(MetadataError::PoetrySyntax);
}
let name = project.name;
// Extract the requirements.