Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/googleapiclient/mimeparse.py: 27%

56 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-08 06:51 +0000

1# Copyright 2014 Joe Gregorio 

2# 

3# Licensed under the MIT License 

4 

5"""MIME-Type Parser 

6 

7This module provides basic functions for handling mime-types. It can handle 

8matching mime-types against a list of media-ranges. See section 14.1 of the 

9HTTP specification [RFC 2616] for a complete explanation. 

10 

11 http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 

12 

13Contents: 

14 - parse_mime_type(): Parses a mime-type into its component parts. 

15 - parse_media_range(): Media-ranges are mime-types with wild-cards and a 'q' 

16 quality parameter. 

17 - quality(): Determines the quality ('q') of a mime-type when 

18 compared against a list of media-ranges. 

19 - quality_parsed(): Just like quality() except the second parameter must be 

20 pre-parsed. 

21 - best_match(): Choose the mime-type with the highest quality ('q') 

22 from a list of candidates. 

23""" 

24from __future__ import absolute_import 

25 

26from functools import reduce 

27 

28__version__ = "0.1.3" 

29__author__ = "Joe Gregorio" 

30__email__ = "joe@bitworking.org" 

31__license__ = "MIT License" 

32__credits__ = "" 

33 

34 

35def parse_mime_type(mime_type): 

36 """Parses a mime-type into its component parts. 

37 

38 Carves up a mime-type and returns a tuple of the (type, subtype, params) 

39 where 'params' is a dictionary of all the parameters for the media range. 

40 For example, the media range 'application/xhtml;q=0.5' would get parsed 

41 into: 

42 

43 ('application', 'xhtml', {'q', '0.5'}) 

44 """ 

45 parts = mime_type.split(";") 

46 params = dict( 

47 [tuple([s.strip() for s in param.split("=", 1)]) for param in parts[1:]] 

48 ) 

49 full_type = parts[0].strip() 

50 # Java URLConnection class sends an Accept header that includes a 

51 # single '*'. Turn it into a legal wildcard. 

52 if full_type == "*": 

53 full_type = "*/*" 

54 (type, subtype) = full_type.split("/") 

55 

56 return (type.strip(), subtype.strip(), params) 

57 

58 

59def parse_media_range(range): 

60 """Parse a media-range into its component parts. 

61 

62 Carves up a media range and returns a tuple of the (type, subtype, 

63 params) where 'params' is a dictionary of all the parameters for the media 

64 range. For example, the media range 'application/*;q=0.5' would get parsed 

65 into: 

66 

67 ('application', '*', {'q', '0.5'}) 

68 

69 In addition this function also guarantees that there is a value for 'q' 

70 in the params dictionary, filling it in with a proper default if 

71 necessary. 

72 """ 

73 (type, subtype, params) = parse_mime_type(range) 

74 if ( 

75 "q" not in params 

76 or not params["q"] 

77 or not float(params["q"]) 

78 or float(params["q"]) > 1 

79 or float(params["q"]) < 0 

80 ): 

81 params["q"] = "1" 

82 

83 return (type, subtype, params) 

84 

85 

86def fitness_and_quality_parsed(mime_type, parsed_ranges): 

87 """Find the best match for a mime-type amongst parsed media-ranges. 

88 

89 Find the best match for a given mime-type against a list of media_ranges 

90 that have already been parsed by parse_media_range(). Returns a tuple of 

91 the fitness value and the value of the 'q' quality parameter of the best 

92 match, or (-1, 0) if no match was found. Just as for quality_parsed(), 

93 'parsed_ranges' must be a list of parsed media ranges. 

94 """ 

95 best_fitness = -1 

96 best_fit_q = 0 

97 (target_type, target_subtype, target_params) = parse_media_range(mime_type) 

98 for (type, subtype, params) in parsed_ranges: 

99 type_match = type == target_type or type == "*" or target_type == "*" 

100 subtype_match = ( 

101 subtype == target_subtype or subtype == "*" or target_subtype == "*" 

102 ) 

103 if type_match and subtype_match: 

104 param_matches = reduce( 

105 lambda x, y: x + y, 

106 [ 

107 1 

108 for (key, value) in target_params.items() 

109 if key != "q" and key in params and value == params[key] 

110 ], 

111 0, 

112 ) 

113 fitness = (type == target_type) and 100 or 0 

114 fitness += (subtype == target_subtype) and 10 or 0 

115 fitness += param_matches 

116 if fitness > best_fitness: 

117 best_fitness = fitness 

118 best_fit_q = params["q"] 

119 

120 return best_fitness, float(best_fit_q) 

121 

122 

123def quality_parsed(mime_type, parsed_ranges): 

124 """Find the best match for a mime-type amongst parsed media-ranges. 

125 

126 Find the best match for a given mime-type against a list of media_ranges 

127 that have already been parsed by parse_media_range(). Returns the 'q' 

128 quality parameter of the best match, 0 if no match was found. This function 

129 bahaves the same as quality() except that 'parsed_ranges' must be a list of 

130 parsed media ranges. 

131 """ 

132 

133 return fitness_and_quality_parsed(mime_type, parsed_ranges)[1] 

134 

135 

136def quality(mime_type, ranges): 

137 """Return the quality ('q') of a mime-type against a list of media-ranges. 

138 

139 Returns the quality 'q' of a mime-type when compared against the 

140 media-ranges in ranges. For example: 

141 

142 >>> quality('text/html','text/*;q=0.3, text/html;q=0.7, 

143 text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5') 

144 0.7 

145 

146 """ 

147 parsed_ranges = [parse_media_range(r) for r in ranges.split(",")] 

148 

149 return quality_parsed(mime_type, parsed_ranges) 

150 

151 

152def best_match(supported, header): 

153 """Return mime-type with the highest quality ('q') from list of candidates. 

154 

155 Takes a list of supported mime-types and finds the best match for all the 

156 media-ranges listed in header. The value of header must be a string that 

157 conforms to the format of the HTTP Accept: header. The value of 'supported' 

158 is a list of mime-types. The list of supported mime-types should be sorted 

159 in order of increasing desirability, in case of a situation where there is 

160 a tie. 

161 

162 >>> best_match(['application/xbel+xml', 'text/xml'], 

163 'text/*;q=0.5,*/*; q=0.1') 

164 'text/xml' 

165 """ 

166 split_header = _filter_blank(header.split(",")) 

167 parsed_header = [parse_media_range(r) for r in split_header] 

168 weighted_matches = [] 

169 pos = 0 

170 for mime_type in supported: 

171 weighted_matches.append( 

172 (fitness_and_quality_parsed(mime_type, parsed_header), pos, mime_type) 

173 ) 

174 pos += 1 

175 weighted_matches.sort() 

176 

177 return weighted_matches[-1][0][1] and weighted_matches[-1][2] or "" 

178 

179 

180def _filter_blank(i): 

181 for s in i: 

182 if s.strip(): 

183 yield s