_adapters.py 2.3 KB

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