Skip to content

Add TOML v1.1 -> v1.0 backwards compatibility for source distributions#18741

Open
konstin wants to merge 1 commit intomainfrom
konsti/toml-backwards-compatibility
Open

Add TOML v1.1 -> v1.0 backwards compatibility for source distributions#18741
konstin wants to merge 1 commit intomainfrom
konsti/toml-backwards-compatibility

Conversation

@konstin
Copy link
Copy Markdown
Member

@konstin konstin commented Mar 27, 2026

TOML 1.1 introduces support for new syntax that older tools with only TOML 1.0 support don't understand.

Usually, the user is either in control of which tools need to read the TOML files or the TOML gets converted before publishing (for wheels: pyproject.toml -> METADATA). The specific case where this doesn't work is when a package manager that only support TOML 1.0 tries to build the source distribution of a dependency. Build tools need to parse pyproject.toml in source distributions to extract the [build-system] table, and if any other part of the file contains TOML 1.1 syntax, they fail to build. This generally doesn't trigger backtracking, so the user is left with a failure when any (transitive) dependency in their dependency tree has started using a single instance of TOML 1.1. Most package managers, including pip, are implemented in Python and use stdlib's tomllib, which only support TOML 1.0 up to including Python 3.14.

To work around this, we do a best-effort rewrite of pyproject.toml to TOML 1.0 during source distribution builds.

This approach is inspired by Cargo, which has been successfully rewriting published Cargo.tomls for many versions. While the toml crate doesn't guarantee this downgrade always works (toml-rs/toml#1088), this crate is also used by Cargo, and this best effort rewrite handles the biggest failure case: Newlines and trailing commas in inline tables. Similarly following Cargo, we also add a pyproject.toml.orig to the source distribution.

https://discuss.python.org/t/adopting-toml-1-1/105624 was inconclusive, but a best-in-class tool should do this transformation.

@konstin konstin added the enhancement New feature or improvement to existing functionality label Mar 27, 2026
@konstin konstin force-pushed the konsti/toml-backwards-compatibility branch 2 times, most recently from f0d7ebe to d580ff9 Compare March 29, 2026 13:05
[TOML 1.1](https://github.com/toml-lang/toml/releases/tag/1.1.0) introduces support for new syntax that older tools with TOML 1.0 don't understand.

Usually, the user is either in control of which tools need to read the TOML files or the TOML gets converted before publishing (`pyproject.toml` -> `METADATA` for wheels). The specific case where this doesn't work is when a user builds the source distribution of the package with a tool that only support TOML 1.0. Build tools need to parse `pyproject.toml` in source distributions to extract the `[build-system]` table, and if any other part of the file contains TOML 1.1 syntax, they fail to build. This generally doesn't trigger backtracking, so the user is left if a failure when any (transitive) dependency in their dependency tree has started using a single instance of TOML 1.1. Most package managers, including pip, are implemented in Python and use stdlib's tomllib, which only support TOML 1.0 up to including Python 3.14.

To work around this, we do a best-effort rewrite of `pyproject.toml` to TOML 1.0 during source distribution builds.

This approach is inspired by Cargo, which is successfully rewriting published `Cargo.toml`s for many versions. While the `toml` crate doesn't guarantee this downgrade is always done (toml-rs/toml#1088), this crate is also used by Cargo, and this best effort rewrite is sufficient currently. Similarly following Cargo, we also add a `pyproject.toml.orig` to the source distribution.

https://discuss.python.org/t/adopting-toml-1-1/105624 went nowhere, but a best-in-class tool should do this transformation, so we're adding it.
@konstin konstin force-pushed the konsti/toml-backwards-compatibility branch from d580ff9 to 9edc16e Compare March 29, 2026 13:06
Copy link
Copy Markdown
Contributor

@Gankra Gankra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any cases where the prettyification will "arbitrarily" modify a pyproject.toml that isn't using 1.1 features?

Comment on lines +255 to +261
writer.write_file(
&Path::new(&top_level)
.join("pyproject.toml.orig")
.portable_display()
.to_string(),
&pyproject_path,
)?;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not totally opposed to just always doing this, but I can't help but wonder if we can detect when this rewrite is necessary and only write a .orig when it is?

Comment on lines +309 to +316
// `pyproject.toml` is handled separately.
if relative == "pyproject.toml" {
continue;
}
if relative == "pyproject.toml.orig" {
debug!("Ignoring existing `pyproject.toml.orig`");
continue;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slightly interesting that one of these merits a debug while the other doesn't

Comment on lines +1881 to +1885
assert!(
contents
.iter()
.any(|f| f.contains("nested_pyproject/pyproject.toml")),
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For clarity I would appreciate this test asserting that .orig's do or don't exist.

@Gankra
Copy link
Copy Markdown
Contributor

Gankra commented Apr 2, 2026

For additional context on this approach in Cargo, the .orig files actually primarily exist to handle the fact that Cargo workspaces allow a Cargo.toml to be defined such that things in the parent directory are required to resolve it properly -- things which should not be shipped in the equivalent of an sdist.

As such, Cargo has a step where it "resolves" these references in a Cargo.toml to actual concrete values.

i.e.version.workspace = true => version = "1.0.0"

This is the kind of thing we might also find desirable to be able to do, so establishing this precedent/idiom now is nice in and of itself.

@Gankra
Copy link
Copy Markdown
Contributor

Gankra commented Apr 2, 2026

Also I guess I should raise the question "do we want to ship this under --preview for a version or two, or with some kind of opt-out out of an abundance of caution"?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or improvement to existing functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants