_adapters.py 2.3 KB

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