Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/importlib_metadata/_adapters.py: 38%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

47 statements  

1import email.message 

2import email.policy 

3import re 

4import textwrap 

5 

6from ._text import FoldedCase 

7 

8 

9class RawPolicy(email.policy.EmailPolicy): 

10 def fold(self, name, value): 

11 folded = self.linesep.join( 

12 textwrap 

13 .indent(value, prefix=' ' * 8, predicate=lambda line: True) 

14 .lstrip() 

15 .splitlines() 

16 ) 

17 return f'{name}: {folded}{self.linesep}' 

18 

19 

20class Message(email.message.Message): 

21 r""" 

22 Specialized Message subclass to handle metadata naturally. 

23 

24 Reads values that may have newlines in them and converts the 

25 payload to the Description. 

26 

27 >>> msg_text = textwrap.dedent(''' 

28 ... Name: Foo 

29 ... Version: 3.0 

30 ... License: blah 

31 ... de-blah 

32 ... <BLANKLINE> 

33 ... First line of description. 

34 ... Second line of description. 

35 ... <BLANKLINE> 

36 ... Fourth line! 

37 ... ''').lstrip().replace('<BLANKLINE>', '') 

38 >>> msg = Message(email.message_from_string(msg_text)) 

39 >>> msg['Description'] 

40 'First line of description.\nSecond line of description.\n\nFourth line!\n' 

41 

42 Message should render even if values contain newlines. 

43 

44 >>> print(msg) 

45 Name: Foo 

46 Version: 3.0 

47 License: blah 

48 de-blah 

49 Description: First line of description. 

50 Second line of description. 

51 <BLANKLINE> 

52 Fourth line! 

53 <BLANKLINE> 

54 <BLANKLINE> 

55 """ 

56 

57 multiple_use_keys = frozenset( 

58 map( 

59 FoldedCase, 

60 [ 

61 'Classifier', 

62 'Import-Name', 

63 'Import-Namespace', 

64 'License-File', 

65 'Obsoletes-Dist', 

66 'Platform', 

67 'Project-URL', 

68 'Provides-Dist', 

69 'Provides-Extra', 

70 'Requires-Dist', 

71 'Requires-External', 

72 'Supported-Platform', 

73 'Dynamic', 

74 ], 

75 ) 

76 ) 

77 """ 

78 Keys that may be indicated multiple times in Core Metadata. 

79 """ 

80 

81 def __new__(cls, orig: email.message.Message): 

82 res = super().__new__(cls) 

83 vars(res).update(vars(orig)) 

84 return res 

85 

86 def __init__(self, *args, **kwargs): 

87 self._headers = self._repair_headers() 

88 

89 # suppress spurious error from mypy 

90 def __iter__(self): 

91 return super().__iter__() 

92 

93 def __getitem__(self, item): 

94 """ 

95 Override parent behavior to typical dict behavior. 

96 

97 ``email.message.Message`` will emit None values for missing 

98 keys. Typical mappings, including this ``Message``, will raise 

99 a key error for missing keys. 

100 

101 Ref python/importlib_metadata#371. 

102 """ 

103 res = super().__getitem__(item) 

104 if res is None: 

105 raise KeyError(item) 

106 return res 

107 

108 def _repair_headers(self): 

109 def redent(value): 

110 "Correct for RFC822 indentation" 

111 indent = ' ' * 8 

112 if not value or '\n' + indent not in value: 

113 return value 

114 return textwrap.dedent(indent + value) 

115 

116 headers = [(key, redent(value)) for key, value in vars(self)['_headers']] 

117 if self._payload: 

118 headers.append(('Description', self.get_payload())) 

119 self.set_payload('') 

120 return headers 

121 

122 def as_string(self): 

123 return super().as_string(policy=RawPolicy()) 

124 

125 @property 

126 def json(self): 

127 """ 

128 Convert PackageMetadata to a JSON-compatible format 

129 per PEP 0566. 

130 """ 

131 

132 def transform(key): 

133 value = self.get_all(key) if key in self.multiple_use_keys else self[key] 

134 if key == 'Keywords': 

135 value = re.split(r'\s+', value) 

136 tk = key.lower().replace('-', '_') 

137 return tk, value 

138 

139 return dict(map(transform, map(FoldedCase, self)))