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

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 = set( 

58 map( 

59 FoldedCase, 

60 [ 

61 'Classifier', 

62 'Obsoletes-Dist', 

63 'Platform', 

64 'Project-URL', 

65 'Provides-Dist', 

66 'Provides-Extra', 

67 'Requires-Dist', 

68 'Requires-External', 

69 'Supported-Platform', 

70 'Dynamic', 

71 ], 

72 ) 

73 ) 

74 """ 

75 Keys that may be indicated multiple times per PEP 566. 

76 """ 

77 

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

79 res = super().__new__(cls) 

80 vars(res).update(vars(orig)) 

81 return res 

82 

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

84 self._headers = self._repair_headers() 

85 

86 # suppress spurious error from mypy 

87 def __iter__(self): 

88 return super().__iter__() 

89 

90 def __getitem__(self, item): 

91 """ 

92 Override parent behavior to typical dict behavior. 

93 

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

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

96 a key error for missing keys. 

97 

98 Ref python/importlib_metadata#371. 

99 """ 

100 res = super().__getitem__(item) 

101 if res is None: 

102 raise KeyError(item) 

103 return res 

104 

105 def _repair_headers(self): 

106 def redent(value): 

107 "Correct for RFC822 indentation" 

108 indent = ' ' * 8 

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

110 return value 

111 return textwrap.dedent(indent + value) 

112 

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

114 if self._payload: 

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

116 self.set_payload('') 

117 return headers 

118 

119 def as_string(self): 

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

121 

122 @property 

123 def json(self): 

124 """ 

125 Convert PackageMetadata to a JSON-compatible format 

126 per PEP 0566. 

127 """ 

128 

129 def transform(key): 

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

131 if key == 'Keywords': 

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

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

134 return tk, value 

135 

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