Repository navigation
Treat an explicitly empty value as set, not absent, when writing Dynamic - #5323
Open
darrenhuai wants to merge 1 commit into
Open
darrenhuai wants to merge 1 commit into
darrenhuai wants to merge 1 commit into
Conversation
Distribution attributes that map to core metadata were tested for truthiness before being reported as Dynamic, so `setup(install_requires=[])` was indistinguishable from leaving install_requires out. Declaring an empty dependency list up front and filling it in later is exactly what `dynamic = ["dependencies"]` is for, and it silently lost `Dynamic: Requires-Dist`. Nineteen of the twenty-two possible dynamic fields already default to None, so presence is simply `is not None` for those. The three that don't - install_requires, extras_require and project_urls - default to an empty container, which is why truthiness was standing in for presence at all. Those defaults are now static, so an omitted value is still recognisable as one and keeps producing no Dynamic entry. Two places had to stop discarding that staticness. _normalize_requires collapsed the value with `or`, which swaps a falsy static default for a plain container, and _finalize_requires then copies the result over metadata. And license_files is derived by setuptools rather than given by the author, so an empty expansion means no license file was found, not that an empty list was declared; it stays static and out of Dynamic as before. Closes pypa#5120
Contributor
|
Tick the box to add this pull request to the merge queue (same as
|
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.
Summary of changes
Closes #5120
setup(install_requires=[])produced noDynamic: Requires-Dist, because the attributes that map to core metadata were tested for truthiness before being reported:An empty list is falsy, so "the author declared no dependencies yet" and "the author said nothing" collapse into the same branch. Declaring dependencies empty up front and filling them in later is the case
dynamic = ["dependencies"]exists for, and it silently lost the field.@abravalheri pointed at this line in the issue and flagged backwards compatibility as the hard part, so I measured it before changing anything. Of the 22 entries in
_POSSIBLE_DYNAMIC_FIELDS, 19 already default toNonewhen unset — for those,is not Noneis presence and nothing changes. Only three default to an empty container instead:install_requires[]extras_require{}project_urls{}Those three are exactly why truthiness was standing in for presence. They now default to
_static.List/_static.Dict, so an omitted value is still recognisable as omitted and still produces noDynamicentry — while a container the author actually passed arrives as a plainlist/dictand is reported.Two places were discarding that staticness and had to stop:
_normalize_requirescollapsed the value withor, which swaps a falsy static default for a plain container._finalize_requiresthen copies the result ontoself.metadata, so the static default was being overwritten before anything could read it._finalize_license_filesalways assigned a plainlist.license_filesis derived by setuptools rather than supplied by the author, so an empty expansion means no license file was found, not that an empty list was declared. It stays static and out ofDynamic, which is whattest_static_config_has_no_dynamicalready required.Behaviour change worth calling out
A field explicitly set to an empty string —
setup(author="")— is now reported asDynamic: Authorwhere it previously was not. That follows from the same rule rather than being a separate decision: the author set it, fromsetup.py, so it is dynamic. Values frompyproject.toml/setup.cfgareStaticand unaffected either way. I'd rather surface this than have it discovered later, and I'm happy to restrict the change to the three container fields if you'd prefer the narrower blast radius.I did not go for the
DYNAMIC_PLACEHOLDERmarker you floated. It would need a new public API and an opt-in from every affected project, whereas the staticness machinery already encodes "did this come from the author or from a default" — it just wasn't being applied to the defaults themselves.Verification
Six new tests in
TestPEP643, parametrised over all three container fields: explicitly-empty isDynamic, omitted is not. The three explicitly-empty cases fail onmain; the three omitted cases pass before and after and exist to pin the behaviour you were worried about.End to end, using the reproduction from the issue:
setup.pyDynamicin PKG-INFOmaininstall_requires=[]install_requires=[]Dynamic: requires-distinstall_requiresinstall_requires=["requests"]Dynamic: requires-dist— unchangedsetuptools/testsis green on Windows / 3.13 (965 passed, 21 skipped, 16 xfailed), along withruff check,ruff format, and the--doctest-modulespass over the changed modules. One unrelated pre-existing failure,test_windows_wrappers.py::TestCLI::test_symlink, needs symlink privileges this machine doesn't have; it fails identically on unmodifiedmain.Pull Request Checklist
newsfragments/.(See documentation for details)