1import functools 2import warnings 3import re 4import textwrap 5import email.message 6 7from ._text import FoldedCase 8 9 10# Do not remove prior to 2024-01-01 or Python 3.14 11_warn = functools.partial( 12 warnings.warn, 13 "Implicit None on return values is deprecated and will raise KeyErrors.", 14 DeprecationWarning, 15 stacklevel=2, 16) 17 18 19class Message(email.message.Message): 20 multiple_use_keys = set( 21 map( 22 FoldedCase, 23 [ 24 'Classifier', 25 'Obsoletes-Dist', 26 'Platform', 27 'Project-URL', 28 'Provides-Dist', 29 'Provides-Extra', 30 'Requires-Dist', 31 'Requires-External', 32 'Supported-Platform', 33 'Dynamic', 34 ], 35 ) 36 ) 37 """ 38 Keys that may be indicated multiple times per PEP 566. 39 """ 40 41 def __new__(cls, orig: email.message.Message): 42 res = super().__new__(cls) 43 vars(res).update(vars(orig)) 44 return res 45 46 def __init__(self, *args, **kwargs): 47 self._headers = self._repair_headers() 48 49 # suppress spurious error from mypy 50 def __iter__(self): 51 return super().__iter__() 52 53 def __getitem__(self, item): 54 """ 55 Warn users that a ``KeyError`` can be expected when a 56 missing key is supplied. Ref python/importlib_metadata#371. 57 """ 58 res = super().__getitem__(item) 59 if res is None: 60 _warn() 61 return res 62 63 def _repair_headers(self): 64 def redent(value): 65 "Correct for RFC822 indentation" 66 if not value or '\n' not in value: 67 return value 68 return textwrap.dedent(' ' * 8 + value) 69 70 headers = [(key, redent(value)) for key, value in vars(self)['_headers']] 71 if self._payload: 72 headers.append(('Description', self.get_payload())) 73 return headers 74 75 @property 76 def json(self): 77 """ 78 Convert PackageMetadata to a JSON-compatible format 79 per PEP 0566. 80 """ 81 82 def transform(key): 83 value = self.get_all(key) if key in self.multiple_use_keys else self[key] 84 if key == 'Keywords': 85 value = re.split(r'\s+', value) 86 tk = key.lower().replace('-', '_') 87 return tk, value 88 89 return dict(map(transform, map(FoldedCase, self))) 90