123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135 |
- import email.message
- import email.policy
- import re
- import textwrap
- from ._text import FoldedCase
- class RawPolicy(email.policy.EmailPolicy):
- def fold(self, name, value):
- folded = self.linesep.join(
- textwrap.indent(value, prefix=' ' * 8, predicate=lambda line: True)
- .lstrip()
- .splitlines()
- )
- return f'{name}: {folded}{self.linesep}'
- class Message(email.message.Message):
- r"""
- Specialized Message subclass to handle metadata naturally.
- Reads values that may have newlines in them and converts the
- payload to the Description.
- >>> msg_text = textwrap.dedent('''
- ... Name: Foo
- ... Version: 3.0
- ... License: blah
- ... de-blah
- ... <BLANKLINE>
- ... First line of description.
- ... Second line of description.
- ... <BLANKLINE>
- ... Fourth line!
- ... ''').lstrip().replace('<BLANKLINE>', '')
- >>> msg = Message(email.message_from_string(msg_text))
- >>> msg['Description']
- 'First line of description.\nSecond line of description.\n\nFourth line!\n'
- Message should render even if values contain newlines.
- >>> print(msg)
- Name: Foo
- Version: 3.0
- License: blah
- de-blah
- Description: First line of description.
- Second line of description.
- <BLANKLINE>
- Fourth line!
- <BLANKLINE>
- <BLANKLINE>
- """
- multiple_use_keys = set(
- map(
- FoldedCase,
- [
- 'Classifier',
- 'Obsoletes-Dist',
- 'Platform',
- 'Project-URL',
- 'Provides-Dist',
- 'Provides-Extra',
- 'Requires-Dist',
- 'Requires-External',
- 'Supported-Platform',
- 'Dynamic',
- ],
- )
- )
- """
- 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 __getitem__(self, item):
- """
- Override parent behavior to typical dict behavior.
- ``email.message.Message`` will emit None values for missing
- keys. Typical mappings, including this ``Message``, will raise
- a key error for missing keys.
- Ref python/importlib_metadata#371.
- """
- res = super().__getitem__(item)
- if res is None:
- raise KeyError(item)
- return res
- def _repair_headers(self):
- def redent(value):
- "Correct for RFC822 indentation"
- indent = ' ' * 8
- if not value or '\n' + indent not in value:
- return value
- return textwrap.dedent(indent + value)
- headers = [(key, redent(value)) for key, value in vars(self)['_headers']]
- if self._payload:
- headers.append(('Description', self.get_payload()))
- self.set_payload('')
- return headers
- def as_string(self):
- return super().as_string(policy=RawPolicy())
- @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)))
|