_adapters.py 2.4 KB

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