1 """Functions for generating and parsing HTTP Accept: headers for
2 supporting server-directed content negotiation.
3 """
4
6 """Generate an accept header value
7
8 [str or (str, float)] -> str
9 """
10 parts = []
11 for element in elements:
12 if type(element) is str:
13 qs = "1.0"
14 mtype = element
15 else:
16 mtype, q = element
17 q = float(q)
18 if q > 1 or q <= 0:
19 raise ValueError('Invalid preference factor: %r' % q)
20
21 qs = '%0.1f' % (q,)
22
23 parts.append((qs, mtype))
24
25 parts.sort()
26 chunks = []
27 for q, mtype in parts:
28 if q == '1.0':
29 chunks.append(mtype)
30 else:
31 chunks.append('%s; q=%s' % (mtype, q))
32
33 return ', '.join(chunks)
34
36 """Parse an accept header, ignoring any accept-extensions
37
38 returns a list of tuples containing main MIME type, MIME subtype,
39 and quality markdown.
40
41 str -> [(str, str, float)]
42 """
43 chunks = [chunk.strip() for chunk in value.split(',')]
44 accept = []
45 for chunk in chunks:
46 parts = [s.strip() for s in chunk.split(';')]
47
48 mtype = parts.pop(0)
49 if '/' not in mtype:
50
51 continue
52
53 main, sub = mtype.split('/', 1)
54
55 for ext in parts:
56 if '=' in ext:
57 k, v = ext.split('=', 1)
58 if k == 'q':
59 try:
60 q = float(v)
61 break
62 except ValueError:
63
64 pass
65 else:
66 q = 1.0
67
68 accept.append((q, main, sub))
69
70 accept.sort()
71 accept.reverse()
72 return [(main, sub, q) for (q, main, sub) in accept]
73
75 """Given the result of parsing an Accept: header, and the
76 available MIME types, return the acceptable types with their
77 quality markdowns.
78
79 For example:
80
81 >>> acceptable = parseAcceptHeader('text/html, text/plain; q=0.5')
82 >>> matchTypes(acceptable, ['text/plain', 'text/html', 'image/jpeg'])
83 [('text/html', 1.0), ('text/plain', 0.5)]
84
85
86 Type signature: ([(str, str, float)], [str]) -> [(str, float)]
87 """
88 if not accept_types:
89
90 default = 1
91 else:
92 default = 0
93
94 match_main = {}
95 match_sub = {}
96 for (main, sub, q) in accept_types:
97 if main == '*':
98 default = max(default, q)
99 continue
100 elif sub == '*':
101 match_main[main] = max(match_main.get(main, 0), q)
102 else:
103 match_sub[(main, sub)] = max(match_sub.get((main, sub), 0), q)
104
105 accepted_list = []
106 order_maintainer = 0
107 for mtype in have_types:
108 main, sub = mtype.split('/')
109 if (main, sub) in match_sub:
110 q = match_sub[(main, sub)]
111 else:
112 q = match_main.get(main, default)
113
114 if q:
115 accepted_list.append((1 - q, order_maintainer, q, mtype))
116 order_maintainer += 1
117
118 accepted_list.sort()
119 return [(mtype, q) for (_, _, q, mtype) in accepted_list]
120
122 """Parse the accept header and return a list of available types in
123 preferred order. If a type is unacceptable, it will not be in the
124 resulting list.
125
126 This is a convenience wrapper around matchTypes and
127 parseAcceptHeader.
128
129 (str, [str]) -> [str]
130 """
131 accepted = parseAcceptHeader(accept_header)
132 preferred = matchTypes(accepted, have_types)
133 return [mtype for (mtype, _) in preferred]
134