fix(flit): don't emit null values when importing a flit project - #3832
Merged
Conversation
Two optional keys in `[tool.flit.*]` leaked `None` into the metadata handed
to tomlkit, which cannot represent it. In both cases the import aborts and
nothing is written.
flit_core reads `include` and `exclude` from `[tool.flit.sdist]`
independently, each defaulting to `[]` (config.py:153-161, flit_core
3.12.0), so setting only one is valid. The importer wrote both through
`dict.get`, so the missing one became `None` and the write of
`[tool.pdm.build]` failed. This hits modern PEP 621 flit projects, which
carry no `[tool.flit.metadata]` at all:
[build-system]
requires = ["flit_core >=3.2,<4"]
build-backend = "flit_core.buildapi"
[project]
name = "mymod"
...
[tool.flit.sdist]
exclude = ["doc/*.html"]
$ pdm import ../pyproject.toml
[ConvertError]: Unable to convert an object of <class 'NoneType'> to
a TOML item
`check_fingerprint` only looks for a `tool.flit` table, so this is what a
bare auto-detecting `pdm import` does with such a project.
Similarly, `author-email` and `maintainer-email` are in flit_core's
`metadata_allowed_fields` but not in `metadata_required_fields`, which is
only `{module, author}` (config.py:51-54), so a legacy-layout project with
an author name and no email is valid. `_get_author` built the inline table
unconditionally:
[tool.flit.metadata]
module = "mymod"
author = "Test Author"
$ pdm import -f flit ../pyproject.toml
[MetaConvertError]: name: Unable to convert an object of
<class 'NoneType'> to a TOML item
Only set the key when the source provides it, as the Poetry importer's
`parse_name_email` already does when dropping an absent email. The
`excludes`/`includes` insertion order is kept as it was so that a project
setting both keys generates a byte-identical `[tool.pdm.build]`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
shuvamk
force-pushed
the
fix/flit-import-null-values
branch
from
August 2, 2026 05:49
de73ffa to
717856f
Compare
frostming
approved these changes
Aug 10, 2026
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3832 +/- ##
==========================================
+ Coverage 88.16% 88.28% +0.12%
==========================================
Files 121 121
Lines 13221 13227 +6
Branches 2246 2249 +3
==========================================
+ Hits 11656 11678 +22
+ Misses 982 975 -7
+ Partials 583 574 -9
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pull Request Checklist
news/describing what is new.Describe what you have changed in this PR.
The problem
Two optional keys in
[tool.flit.*]makepdm importabort. In both cases the importerbuilds a value of
None, which tomlkit cannot represent, so the import fails and nothingis written to
pyproject.toml.1. A modern PEP 621 flit project that sets only
excludein[tool.flit.sdist]The project being imported — no
[tool.flit.metadata]at all, this is the layoutflit_core ≥ 3.2 recommends:
check_fingerprintonly looks for atool.flittable, so a barepdm importauto-detectsthis as flit — no
-fneeded:Setting only one of the two keys is valid: flit_core reads them independently, each
defaulting to
[]—flit_core/config.py:153-161, flit_core 3.12.0:2. A legacy-layout project with an author name and no email
Also valid:
author-emailis in flit_core'smetadata_allowed_fieldsbutmetadata_required_fieldsis only{module, author}—flit_core/config.py:51-54,flit_core 3.12.0:
Same for
maintainerwithoutmaintainer-email.main@b3cb02ed[tool.flit.sdist]with onlyexclude[tool.pdm.build] excludes = [...]ConvertError, import aborts, nothing written[tool.flit.sdist]with onlyinclude[tool.pdm.build] includes = [...]ConvertError, import aborts, nothing writtenauthorwithoutauthor-emailauthors = [{name = "Test Author"}]MetaConvertError, import abortsmaintainerwithoutmaintainer-emailmaintainers = [{name = "..."}]MetaConvertError, import abortsThe cause
Both sites write the optional key unconditionally.
src/pdm/formats/flit.py:38-40:src/pdm/formats/flit.py:140-142:When the source omits the key,
email— respectively the missingget()— isNone.tomlkit.inline_table().update(...)raises immediately for the author case; the sdistcase survives conversion and blows up when
[tool.pdm.build]is written inimport_cmd.do_import.The fix
Only set the key when the source provides it. This is what the Poetry importer already
does for a missing email:
parse_name_emailfilters out theNonegroups of thename/email regex match (
src/pdm/formats/poetry.py:116-126).The
excludes-before-includesinsertion order is deliberately preserved, so a projectthat sets both keys — the common case, and what the
flit-demofixture uses —generates a byte-identical file. Running
pdm import -f flitontests/fixtures/projects/flit-demo/pyproject.tomlunder both source versions:Tests
Two new tests in
tests/test_formats.py, next totest_convert_flit:test_convert_flit_author_and_maintainer_without_emailtest_convert_flit_sdist_with_one_of_include_and_exclude, parametrised overinclude-only and exclude-only
The sdist fixture carries an
author-emailon purpose, so it fails for the sdist reasonand not the author one — the two halves are proven independently.
I verified all three fail without the fix. Reverting only
src/pdm/formats/flit.pytoorigin/mainand keeping the tests:Restoring the file:
4 passed, 20 deselected in 0.74s(the three new ones plus thepre-existing
test_convert_flit).Beyond the unit tests I ran the real CLI against both inputs in a scratch project; the
transcripts above are that run.
What I ran
main@b3cb02edpytest -n auto -m "not integration and not network" -qprek run --all-filesThe one skip is
tests/test_utils.py:171(Windows-only) on both. I did not run theintegration/networkmarked tests or the tox matrix.Disclosure: I used an AI coding assistant (Claude Code) to find this and to help write
the fix.