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.indent(value, prefix=' ' * 8, predicate=lambda line: True) 

13 .lstrip() 

14 .splitlines() 

15 ) 

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

17 

18 

19class Message(email.message.Message): 

20 r""" 

21 Specialized Message subclass to handle metadata naturally. 

22 

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

24 payload to the Description. 

25 

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

27 ... Name: Foo 

28 ... Version: 3.0 

29 ... License: blah 

30 ... de-blah 

31 ... <BLANKLINE> 

32 ... First line of description. 

33 ... Second line of description. 

34 ... <BLANKLINE> 

35 ... Fourth line! 

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

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

38 >>> msg['Description'] 

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

40 

41 Message should render even if values contain newlines. 

42 

43 >>> print(msg) 

44 Name: Foo 

45 Version: 3.0 

46 License: blah 

47 de-blah 

48 Description: First line of description. 

49 Second line of description. 

50 <BLANKLINE> 

51 Fourth line! 

52 <BLANKLINE> 

53 <BLANKLINE> 

54 """ 

55 

56 multiple_use_keys = set( 

57 map( 

58 FoldedCase, 

59 [ 

60 'Classifier', 

61 'Obsoletes-Dist', 

62 'Platform', 

63 'Project-URL', 

64 'Provides-Dist', 

65 'Provides-Extra', 

66 'Requires-Dist', 

67 'Requires-External', 

68 'Supported-Platform', 

69 'Dynamic', 

70 ], 

71 ) 

72 ) 

73 """ 

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

75 """ 

76 

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

78 res = super().__new__(cls) 

79 vars(res).update(vars(orig)) 

80 return res 

81 

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

83 self._headers = self._repair_headers() 

84 

85 # suppress spurious error from mypy 

86 def __iter__(self): 

87 return super().__iter__() 

88 

89 def __getitem__(self, item): 

90 """ 

91 Override parent behavior to typical dict behavior. 

92 

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

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

95 a key error for missing keys. 

96 

97 Ref python/importlib_metadata#371. 

98 """ 

99 res = super().__getitem__(item) 

100 if res is None: 

101 raise KeyError(item) 

102 return res 

103 

104 def _repair_headers(self): 

105 def redent(value): 

106 "Correct for RFC822 indentation" 

107 indent = ' ' * 8 

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

109 return value 

110 return textwrap.dedent(indent + value) 

111 

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

113 if self._payload: 

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

115 self.set_payload('') 

116 return headers 

117 

118 def as_string(self): 

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

120 

121 @property 

122 def json(self): 

123 """ 

124 Convert PackageMetadata to a JSON-compatible format 

125 per PEP 0566. 

126 """ 

127 

128 def transform(key): 

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

130 if key == 'Keywords': 

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

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

133 return tk, value 

134 

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