-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement PEP 643 (Dynamic
field for core metadata)
#4698
Open
abravalheri
wants to merge
12
commits into
pypa:main
Choose a base branch
from
abravalheri:issue-2685-pep643
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
763724d
Prepare test for PEP 643 by removing checks on Metadata-Version and D…
abravalheri 1135b2e
First draft implementation of '_static' in preparation for PEP 643
abravalheri 449e4f4
Modify Metadata-Version expectation in test_egg_info
abravalheri 8dc7277
Use _static.{List,Dict} and an attribute to track modifications inste…
abravalheri f2de422
Add tests for Dynamic core metadata for setup.cfg
abravalheri 41635a1
Add tests for Dynamic core metadata for pyproject.toml
abravalheri fc5ef0c
Add tests for static 'attr' directive
abravalheri e28b4b6
Fix spelling error
abravalheri b9af098
Mark values from pyproject.toml as static
abravalheri f4fb662
Remove test workaround for unmarked static values from pyproject.toml
abravalheri e6b718a
Add extra tests for static/dynamic metadata
abravalheri 38d6b05
Fix _static.Dict.__ior__ for Python 3.8
abravalheri File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
from functools import wraps | ||
from typing import Any, TypeVar | ||
|
||
import packaging.specifiers | ||
|
||
from .warnings import SetuptoolsDeprecationWarning | ||
|
||
|
||
class Static: | ||
""" | ||
Wrapper for built-in object types that are allow setuptools to identify | ||
static core metadata (in opposition to ``Dynamic``, as defined :pep:`643`). | ||
|
||
The trick is to mark values with :class:`Static` when they come from | ||
``pyproject.toml`` or ``setup.cfg``, so if any plugin overwrite the value | ||
with a built-in, setuptools will be able to recognise the change. | ||
|
||
We inherit from built-in classes, so that we don't need to change the existing | ||
code base to deal with the new types. | ||
We also should strive for immutability objects to avoid changes after the | ||
initial parsing. | ||
""" | ||
|
||
_mutated_: bool = False # TODO: Remove after deprecation warning is solved | ||
|
||
|
||
def _prevent_modification(target: type, method: str, copying: str) -> None: | ||
""" | ||
Because setuptools is very flexible we cannot fully prevent | ||
plugins and user customisations from modifying static values that were | ||
parsed from config files. | ||
But we can attempt to block "in-place" mutations and identify when they | ||
were done. | ||
""" | ||
fn = getattr(target, method, None) | ||
if fn is None: | ||
return | ||
|
||
@wraps(fn) | ||
def _replacement(self: Static, *args, **kwargs): | ||
# TODO: After deprecation period raise NotImplementedError instead of warning | ||
# which obviated the existence and checks of the `_mutated_` attribute. | ||
self._mutated_ = True | ||
SetuptoolsDeprecationWarning.emit( | ||
"Direct modification of value will be disallowed", | ||
f""" | ||
In an effort to implement PEP 643, direct/in-place changes of static values | ||
that come from configuration files are deprecated. | ||
If you need to modify this value, please first create a copy with {copying} | ||
and make sure conform to all relevant standards when overriding setuptools | ||
functionality (https://packaging.python.org/en/latest/specifications/). | ||
""", | ||
due_date=(2025, 10, 10), # Initially introduced in 2024-09-06 | ||
) | ||
return fn(self, *args, **kwargs) | ||
|
||
_replacement.__doc__ = "" # otherwise doctest may fail. | ||
setattr(target, method, _replacement) | ||
|
||
|
||
class Str(str, Static): | ||
pass | ||
|
||
|
||
class Tuple(tuple, Static): | ||
pass | ||
|
||
|
||
class List(list, Static): | ||
""" | ||
:meta private: | ||
>>> x = List([1, 2, 3]) | ||
>>> is_static(x) | ||
True | ||
>>> x += [0] # doctest: +IGNORE_EXCEPTION_DETAIL | ||
Traceback (most recent call last): | ||
SetuptoolsDeprecationWarning: Direct modification ... | ||
>>> is_static(x) # no longer static after modification | ||
False | ||
>>> y = list(x) | ||
>>> y.clear() | ||
>>> y | ||
[] | ||
>>> y == x | ||
False | ||
>>> is_static(List(y)) | ||
True | ||
""" | ||
|
||
|
||
# Make `List` immutable-ish | ||
# (certain places of setuptools/distutils issue a warn if we use tuple instead of list) | ||
for _method in ( | ||
'__delitem__', | ||
'__iadd__', | ||
'__setitem__', | ||
'append', | ||
'clear', | ||
'extend', | ||
'insert', | ||
'remove', | ||
'reverse', | ||
'pop', | ||
): | ||
_prevent_modification(List, _method, "`list(value)`") | ||
|
||
|
||
class Dict(dict, Static): | ||
""" | ||
:meta private: | ||
>>> x = Dict({'a': 1, 'b': 2}) | ||
>>> is_static(x) | ||
True | ||
>>> x['c'] = 0 # doctest: +IGNORE_EXCEPTION_DETAIL | ||
Traceback (most recent call last): | ||
SetuptoolsDeprecationWarning: Direct modification ... | ||
>>> x._mutated_ | ||
True | ||
>>> is_static(x) # no longer static after modification | ||
False | ||
>>> y = dict(x) | ||
>>> y.popitem() | ||
('b', 2) | ||
>>> y == x | ||
False | ||
>>> is_static(Dict(y)) | ||
True | ||
""" | ||
|
||
|
||
# Make `Dict` immutable-ish (we cannot inherit from types.MappingProxyType): | ||
for _method in ( | ||
'__delitem__', | ||
'__ior__', | ||
'__setitem__', | ||
'clear', | ||
'pop', | ||
'popitem', | ||
'setdefault', | ||
'update', | ||
): | ||
_prevent_modification(Dict, _method, "`dict(value)`") | ||
|
||
|
||
class SpecifierSet(packaging.specifiers.SpecifierSet, Static): | ||
"""Not exactly a built-in type but useful for ``requires-python``""" | ||
|
||
|
||
T = TypeVar("T") | ||
|
||
|
||
def noop(value: T) -> T: | ||
""" | ||
>>> noop(42) | ||
42 | ||
""" | ||
return value | ||
|
||
|
||
_CONVERSIONS = {str: Str, tuple: Tuple, list: List, dict: Dict} | ||
|
||
|
||
def attempt_conversion(value: T) -> T: | ||
""" | ||
>>> is_static(attempt_conversion("hello")) | ||
True | ||
>>> is_static(object()) | ||
False | ||
""" | ||
return _CONVERSIONS.get(type(value), noop)(value) # type: ignore[call-overload] | ||
|
||
|
||
def is_static(value: Any) -> bool: | ||
""" | ||
>>> is_static(a := Dict({'a': 1})) | ||
True | ||
>>> is_static(dict(a)) | ||
False | ||
>>> is_static(b := List([1, 2, 3])) | ||
True | ||
>>> is_static(list(b)) | ||
False | ||
""" | ||
return isinstance(value, Static) and not value._mutated_ | ||
|
||
|
||
EMPTY_LIST = List() | ||
EMPTY_DICT = Dict() |
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An object parameter should match anything. You should be able to avoid using
Any
here.You can also make this function a type-guard with: