-
-
Notifications
You must be signed in to change notification settings - Fork 32.7k
bpo-43926: Cleaner metadata with PEP 566 JSON support. #25565
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
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
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 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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import re | ||
import textwrap | ||
import email.message | ||
|
||
from ._text import FoldedCase | ||
|
||
|
||
class Message(email.message.Message): | ||
multiple_use_keys = set( | ||
map( | ||
FoldedCase, | ||
[ | ||
'Classifier', | ||
'Obsoletes-Dist', | ||
'Platform', | ||
'Project-URL', | ||
'Provides-Dist', | ||
'Provides-Extra', | ||
'Requires-Dist', | ||
'Requires-External', | ||
'Supported-Platform', | ||
], | ||
) | ||
) | ||
""" | ||
Keys that may be indicated multiple times per PEP 566. | ||
""" | ||
|
||
def __new__(cls, orig: email.message.Message): | ||
res = super().__new__(cls) | ||
vars(res).update(vars(orig)) | ||
return res | ||
|
||
def __init__(self, *args, **kwargs): | ||
self._headers = self._repair_headers() | ||
|
||
# suppress spurious error from mypy | ||
def __iter__(self): | ||
return super().__iter__() | ||
|
||
def _repair_headers(self): | ||
def redent(value): | ||
"Correct for RFC822 indentation" | ||
if not value or '\n' not in value: | ||
return value | ||
return textwrap.dedent(' ' * 8 + value) | ||
|
||
headers = [(key, redent(value)) for key, value in vars(self)['_headers']] | ||
if self._payload: | ||
headers.append(('Description', self.get_payload())) | ||
return headers | ||
|
||
@property | ||
def json(self): | ||
""" | ||
Convert PackageMetadata to a JSON-compatible format | ||
per PEP 0566. | ||
""" | ||
|
||
def transform(key): | ||
value = self.get_all(key) if key in self.multiple_use_keys else self[key] | ||
if key == 'Keywords': | ||
value = re.split(r'\s+', value) | ||
tk = key.lower().replace('-', '_') | ||
return tk, value | ||
|
||
return dict(map(transform, map(FoldedCase, self))) |
File renamed without changes.
File renamed without changes.
File renamed without changes.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
from typing import Any, Dict, Iterator, List, Protocol, TypeVar, Union | ||
|
||
|
||
_T = TypeVar("_T") | ||
|
||
|
||
class PackageMetadata(Protocol): | ||
def __len__(self) -> int: | ||
... # pragma: no cover | ||
|
||
def __contains__(self, item: str) -> bool: | ||
... # pragma: no cover | ||
|
||
def __getitem__(self, key: str) -> str: | ||
... # pragma: no cover | ||
|
||
def __iter__(self) -> Iterator[str]: | ||
... # pragma: no cover | ||
|
||
def get_all(self, name: str, failobj: _T = ...) -> Union[List[Any], _T]: | ||
""" | ||
Return all values associated with a possibly multi-valued key. | ||
""" | ||
|
||
@property | ||
def json(self) -> Dict[str, Union[str, List[str]]]: | ||
""" | ||
A JSON-compatible form of the metadata. | ||
""" |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import re | ||
|
||
from ._functools import method_cache | ||
|
||
|
||
# from jaraco.text 3.5 | ||
class FoldedCase(str): | ||
""" | ||
A case insensitive string class; behaves just like str | ||
except compares equal when the only variation is case. | ||
|
||
>>> s = FoldedCase('hello world') | ||
|
||
>>> s == 'Hello World' | ||
True | ||
|
||
>>> 'Hello World' == s | ||
True | ||
|
||
>>> s != 'Hello World' | ||
False | ||
|
||
>>> s.index('O') | ||
4 | ||
|
||
>>> s.split('O') | ||
['hell', ' w', 'rld'] | ||
|
||
>>> sorted(map(FoldedCase, ['GAMMA', 'alpha', 'Beta'])) | ||
['alpha', 'Beta', 'GAMMA'] | ||
|
||
Sequence membership is straightforward. | ||
|
||
>>> "Hello World" in [s] | ||
True | ||
>>> s in ["Hello World"] | ||
True | ||
|
||
You may test for set inclusion, but candidate and elements | ||
must both be folded. | ||
|
||
>>> FoldedCase("Hello World") in {s} | ||
True | ||
>>> s in {FoldedCase("Hello World")} | ||
True | ||
|
||
String inclusion works as long as the FoldedCase object | ||
is on the right. | ||
|
||
>>> "hello" in FoldedCase("Hello World") | ||
True | ||
|
||
But not if the FoldedCase object is on the left: | ||
|
||
>>> FoldedCase('hello') in 'Hello World' | ||
False | ||
|
||
In that case, use in_: | ||
|
||
>>> FoldedCase('hello').in_('Hello World') | ||
True | ||
|
||
>>> FoldedCase('hello') > FoldedCase('Hello') | ||
False | ||
""" | ||
|
||
def __lt__(self, other): | ||
return self.lower() < other.lower() | ||
|
||
def __gt__(self, other): | ||
return self.lower() > other.lower() | ||
|
||
def __eq__(self, other): | ||
return self.lower() == other.lower() | ||
|
||
def __ne__(self, other): | ||
return self.lower() != other.lower() | ||
|
||
def __hash__(self): | ||
return hash(self.lower()) | ||
|
||
def __contains__(self, other): | ||
return super(FoldedCase, self).lower().__contains__(other.lower()) | ||
|
||
def in_(self, other): | ||
"Does self appear in other?" | ||
return self in FoldedCase(other) | ||
|
||
# cache lower since it's likely to be called frequently. | ||
@method_cache | ||
def lower(self): | ||
return super(FoldedCase, self).lower() | ||
|
||
def index(self, sub): | ||
return self.lower().index(sub.lower()) | ||
|
||
def split(self, splitter=' ', maxsplit=0): | ||
pattern = re.compile(re.escape(splitter), re.I) | ||
return pattern.split(self, maxsplit) |
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
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
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
4 changes: 4 additions & 0 deletions
4
Misc/NEWS.d/next/Library/2021-04-23-17-48-55.bpo-43926.HMUlGU.rst
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
In ``importlib.metadata``, provide a uniform interface to ``Description``, | ||
allow for any field to be encoded with multiline values, remove continuation | ||
lines from multiline values, and add a ``.json`` property for easy access to | ||
the PEP 566 JSON-compatible form. Sync with ``importlib_metadata 4.0``. |
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.
This is a Python 3.10 only feature and hence a versionadded directive can be added.
Slightly out of topic, are
Distribution
objects andmetadata
attribute immutable? Accessing json attribute from metadata objects repeats the computation though it looks like a computed once attribute so just curious ifmetadata
attribute of Distribution,json
attribute etc. can be made a cached_property.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.
Fixed in 289665029e (also
versionchanged
for changes to the interpretation of the metadata).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.
Although technically the PackageMetadata object is mutable, the protocol for it doesn't allow for mutation, so it's effectively immutable, so yes, a
cached_property
could be employed. Such an optimization may be premature, so I'm slightly inclined to defer that concern until a use-case presents itself. I don't feel strongly about it though and would likely accept a PR toimportlib_metadata
.