Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2Parse four ``Accept*`` headers used in server-driven content negotiation. 

3 

4The four headers are ``Accept``, ``Accept-Charset``, ``Accept-Encoding`` and 

5``Accept-Language``. 

6""" 

7 

8from collections import namedtuple 

9import re 

10import textwrap 

11import warnings 

12 

13 

14# RFC 7230 Section 3.2.3 "Whitespace" 

15# OWS = *( SP / HTAB ) 

16# ; optional whitespace 

17OWS_re = '[ \t]*' 

18 

19# RFC 7230 Section 3.2.6 "Field Value Components": 

20# tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" 

21# / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" 

22# / DIGIT / ALPHA 

23tchar_re = r"[!#$%&'*+\-.^_`|~0-9A-Za-z]" 

24 

25# token = 1*tchar 

26token_re = tchar_re + '+' 

27token_compiled_re = re.compile('^' + token_re + '$') 

28 

29# RFC 7231 Section 5.3.1 "Quality Values" 

30# qvalue = ( "0" [ "." 0*3DIGIT ] ) 

31# / ( "1" [ "." 0*3("0") ] ) 

32qvalue_re = ( 

33 r'(?:0(?:\.[0-9]{0,3})?)' 

34 '|' 

35 r'(?:1(?:\.0{0,3})?)' 

36) 

37# weight = OWS ";" OWS "q=" qvalue 

38weight_re = OWS_re + ';' + OWS_re + '[qQ]=(' + qvalue_re + ')' 

39 

40 

41def _item_n_weight_re(item_re): 

42 return '(' + item_re + ')(?:' + weight_re + ')?' 

43 

44 

45def _item_qvalue_pair_to_header_element(pair): 

46 item, qvalue = pair 

47 if qvalue == 1.0: 

48 element = item 

49 elif qvalue == 0.0: 

50 element = '{};q=0'.format(item) 

51 else: 

52 element = '{};q={}'.format(item, qvalue) 

53 return element 

54 

55 

56def _list_0_or_more__compiled_re(element_re): 

57 # RFC 7230 Section 7 "ABNF List Extension: #rule": 

58 # #element => [ ( "," / element ) *( OWS "," [ OWS element ] ) ] 

59 return re.compile( 

60 '^(?:$)|' + 

61 '(?:' + 

62 '(?:,|(?:' + element_re + '))' + 

63 '(?:' + OWS_re + ',(?:' + OWS_re + element_re + ')?)*' + 

64 ')$', 

65 ) 

66 

67 

68def _list_1_or_more__compiled_re(element_re): 

69 # RFC 7230 Section 7 "ABNF List Extension: #rule": 

70 # 1#element => *( "," OWS ) element *( OWS "," [ OWS element ] ) 

71 # and RFC 7230 Errata ID: 4169 

72 return re.compile( 

73 '^(?:,' + OWS_re + ')*' + element_re + 

74 '(?:' + OWS_re + ',(?:' + OWS_re + element_re + ')?)*$', 

75 ) 

76 

77 

78class AcceptOffer(namedtuple('AcceptOffer', ['type', 'subtype', 'params'])): 

79 """ 

80 A pre-parsed offer tuple represeting a value in the format 

81 ``type/subtype;param0=value0;param1=value1``. 

82 

83 :ivar type: The media type's root category. 

84 :ivar subtype: The media type's subtype. 

85 :ivar params: A tuple of 2-tuples containing parameter names and values. 

86 

87 """ 

88 __slots__ = () 

89 

90 def __str__(self): 

91 """ 

92 Return the properly quoted media type string. 

93 

94 """ 

95 value = self.type + '/' + self.subtype 

96 return Accept._form_media_range(value, self.params) 

97 

98 

99class Accept(object): 

100 """ 

101 Represent an ``Accept`` header. 

102 

103 Base class for :class:`AcceptValidHeader`, :class:`AcceptNoHeader`, and 

104 :class:`AcceptInvalidHeader`. 

105 """ 

106 

107 # RFC 6838 describes syntax rules for media types that are different to 

108 # (and stricter than) those in RFC 7231, but if RFC 7231 intended us to 

109 # follow the rules in RFC 6838 for media ranges, it would not have 

110 # specified its own syntax rules for media ranges, so it appears we should 

111 # use the rules in RFC 7231 for now. 

112 

113 # RFC 5234 Appendix B.1 "Core Rules": 

114 # VCHAR = %x21-7E 

115 # ; visible (printing) characters 

116 vchar_re = '\x21-\x7e' 

117 # RFC 7230 Section 3.2.6 "Field Value Components": 

118 # quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE 

119 # qdtext = HTAB / SP /%x21 / %x23-5B / %x5D-7E / obs-text 

120 # obs-text = %x80-FF 

121 # quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) 

122 obs_text_re = '\x80-\xff' 

123 qdtext_re = '[\t \x21\x23-\x5b\\\x5d-\x7e' + obs_text_re + ']' 

124 # The '\\' between \x5b and \x5d is needed to escape \x5d (']') 

125 quoted_pair_re = r'\\' + '[\t ' + vchar_re + obs_text_re + ']' 

126 quoted_string_re = \ 

127 '"(?:(?:' + qdtext_re + ')|(?:' + quoted_pair_re + '))*"' 

128 

129 # RFC 7231 Section 3.1.1.1 "Media Type": 

130 # type = token 

131 # subtype = token 

132 # parameter = token "=" ( token / quoted-string ) 

133 type_re = token_re 

134 subtype_re = token_re 

135 parameter_re = token_re + '=' + \ 

136 '(?:(?:' + token_re + ')|(?:' + quoted_string_re + '))' 

137 

138 # Section 5.3.2 "Accept": 

139 # media-range = ( "*/*" 

140 # / ( type "/" "*" ) 

141 # / ( type "/" subtype ) 

142 # ) *( OWS ";" OWS parameter ) 

143 media_range_re = ( 

144 '(' + 

145 '(?:' + type_re + '/' + subtype_re + ')' + 

146 # '*' is included through type_re and subtype_re, so this covers */* 

147 # and type/* 

148 ')' + 

149 '(' + 

150 '(?:' + OWS_re + ';' + OWS_re + 

151 '(?![qQ]=)' + # media type parameter cannot be named "q" 

152 parameter_re + ')*' + 

153 ')' 

154 ) 

155 # accept-params = weight *( accept-ext ) 

156 # accept-ext = OWS ";" OWS token [ "=" ( token / quoted-string ) ] 

157 accept_ext_re = ( 

158 OWS_re + ';' + OWS_re + token_re + '(?:' + 

159 '=(?:' + 

160 '(?:' + token_re + ')|(?:' + quoted_string_re + ')' + 

161 ')' + 

162 ')?' 

163 ) 

164 accept_params_re = weight_re + '((?:' + accept_ext_re + ')*)' 

165 

166 media_range_n_accept_params_re = media_range_re + '(?:' + \ 

167 accept_params_re + ')?' 

168 media_range_n_accept_params_compiled_re = re.compile( 

169 media_range_n_accept_params_re, 

170 ) 

171 

172 accept_compiled_re = _list_0_or_more__compiled_re( 

173 element_re=media_range_n_accept_params_re, 

174 ) 

175 

176 # For parsing repeated groups within the media type parameters and 

177 # extension parameters segments 

178 parameters_compiled_re = re.compile( 

179 OWS_re + ';' + OWS_re + '(' + token_re + ')=(' + token_re + '|' + 

180 quoted_string_re + ')', 

181 ) 

182 accept_ext_compiled_re = re.compile( 

183 OWS_re + ';' + OWS_re + '(' + token_re + ')' + 

184 '(?:' + 

185 '=(' + 

186 '(?:' + 

187 '(?:' + token_re + ')|(?:' + quoted_string_re + ')' + 

188 ')' + 

189 ')' + 

190 ')?', 

191 ) 

192 

193 # For parsing the media types in the `offers` argument to 

194 # .acceptable_offers(), we re-use the media range regex for media types. 

195 # This is not intended to be a validation of the offers; its main purpose 

196 # is to extract the media type and any media type parameters. 

197 media_type_re = media_range_re 

198 media_type_compiled_re = re.compile('^' + media_type_re + '$') 

199 

200 @classmethod 

201 def _escape_and_quote_parameter_value(cls, param_value): 

202 """ 

203 Escape and quote parameter value where necessary. 

204 

205 For media type and extension parameter values. 

206 """ 

207 if param_value == '': 

208 param_value = '""' 

209 else: 

210 param_value = param_value.replace('\\', '\\\\').replace( 

211 '"', r'\"', 

212 ) 

213 if not token_compiled_re.match(param_value): 

214 param_value = '"' + param_value + '"' 

215 return param_value 

216 

217 @classmethod 

218 def _form_extension_params_segment(cls, extension_params): 

219 """ 

220 Convert iterable of extension parameters to str segment for header. 

221 

222 `extension_params` is an iterable where each item is either a parameter 

223 string or a (name, value) tuple. 

224 """ 

225 extension_params_segment = '' 

226 for item in extension_params: 

227 try: 

228 extension_params_segment += (';' + item) 

229 except TypeError: 

230 param_name, param_value = item 

231 param_value = cls._escape_and_quote_parameter_value( 

232 param_value=param_value, 

233 ) 

234 extension_params_segment += ( 

235 ';' + param_name + '=' + param_value 

236 ) 

237 return extension_params_segment 

238 

239 @classmethod 

240 def _form_media_range(cls, type_subtype, media_type_params): 

241 """ 

242 Combine `type_subtype` and `media_type_params` to form a media range. 

243 

244 `type_subtype` is a ``str``, and `media_type_params` is an iterable of 

245 (parameter name, parameter value) tuples. 

246 """ 

247 media_type_params_segment = '' 

248 for param_name, param_value in media_type_params: 

249 param_value = cls._escape_and_quote_parameter_value( 

250 param_value=param_value, 

251 ) 

252 media_type_params_segment += (';' + param_name + '=' + param_value) 

253 return type_subtype + media_type_params_segment 

254 

255 @classmethod 

256 def _iterable_to_header_element(cls, iterable): 

257 """ 

258 Convert iterable of tuples into header element ``str``. 

259 

260 Each tuple is expected to be in one of two forms: (media_range, qvalue, 

261 extension_params_segment), or (media_range, qvalue). 

262 """ 

263 try: 

264 media_range, qvalue, extension_params_segment = iterable 

265 except ValueError: 

266 media_range, qvalue = iterable 

267 extension_params_segment = '' 

268 

269 if qvalue == 1.0: 

270 if extension_params_segment: 

271 element = '{};q=1{}'.format( 

272 media_range, extension_params_segment, 

273 ) 

274 else: 

275 element = media_range 

276 elif qvalue == 0.0: 

277 element = '{};q=0{}'.format(media_range, extension_params_segment) 

278 else: 

279 element = '{};q={}{}'.format( 

280 media_range, qvalue, extension_params_segment, 

281 ) 

282 return element 

283 

284 @classmethod 

285 def _parse_media_type_params(cls, media_type_params_segment): 

286 """ 

287 Parse media type parameters segment into list of (name, value) tuples. 

288 """ 

289 media_type_params = cls.parameters_compiled_re.findall( 

290 media_type_params_segment, 

291 ) 

292 for index, (name, value) in enumerate(media_type_params): 

293 if value.startswith('"') and value.endswith('"'): 

294 value = cls._process_quoted_string_token(token=value) 

295 media_type_params[index] = (name, value) 

296 return media_type_params 

297 

298 @classmethod 

299 def _process_quoted_string_token(cls, token): 

300 """ 

301 Return unescaped and unquoted value from quoted token. 

302 """ 

303 # RFC 7230, section 3.2.6 "Field Value Components": "Recipients that 

304 # process the value of a quoted-string MUST handle a quoted-pair as if 

305 # it were replaced by the octet following the backslash." 

306 return re.sub(r'\\(?![\\])', '', token[1:-1]).replace('\\\\', '\\') 

307 

308 @classmethod 

309 def _python_value_to_header_str(cls, value): 

310 """ 

311 Convert Python value to header string for __add__/__radd__. 

312 """ 

313 if isinstance(value, str): 

314 return value 

315 if hasattr(value, 'items'): 

316 if value == {}: 

317 value = [] 

318 else: 

319 value_list = [] 

320 for media_range, item in value.items(): 

321 # item is either (media range, (qvalue, extension 

322 # parameters segment)), or (media range, qvalue) (supported 

323 # for backward compatibility) 

324 if isinstance(item, (float, int)): 

325 value_list.append((media_range, item, '')) 

326 else: 

327 value_list.append((media_range, item[0], item[1])) 

328 value = sorted( 

329 value_list, 

330 key=lambda item: item[1], # qvalue 

331 reverse=True, 

332 ) 

333 if isinstance(value, (tuple, list)): 

334 header_elements = [] 

335 for item in value: 

336 if isinstance(item, (tuple, list)): 

337 item = cls._iterable_to_header_element(iterable=item) 

338 header_elements.append(item) 

339 header_str = ', '.join(header_elements) 

340 else: 

341 header_str = str(value) 

342 return header_str 

343 

344 @classmethod 

345 def parse(cls, value): 

346 """ 

347 Parse an ``Accept`` header. 

348 

349 :param value: (``str``) header value 

350 :return: If `value` is a valid ``Accept`` header, returns an iterator 

351 of (*media_range*, *qvalue*, *media_type_params*, 

352 *extension_params*) tuples, as parsed from the header from 

353 left to right. 

354 

355 | *media_range* is the media range, including any media type 

356 parameters. The media range is returned in a canonicalised 

357 form (except the case of the characters are unchanged): 

358 unnecessary spaces around the semicolons before media type 

359 parameters are removed; the parameter values are returned in 

360 a form where only the '``\\``' and '``"``' characters are 

361 escaped, and the values are quoted with double quotes only 

362 if they need to be quoted. 

363 

364 | *qvalue* is the quality value of the media range. 

365 

366 | *media_type_params* is the media type parameters, as a list 

367 of (parameter name, value) tuples. 

368 

369 | *extension_params* is the extension parameters, as a list 

370 where each item is either a parameter string or a (parameter 

371 name, value) tuple. 

372 :raises ValueError: if `value` is an invalid header 

373 """ 

374 # Check if header is valid 

375 # Using Python stdlib's `re` module, there is currently no way to check 

376 # the match *and* get all the groups using the same regex, so we have 

377 # to do this in steps using multiple regexes. 

378 if cls.accept_compiled_re.match(value) is None: 

379 raise ValueError('Invalid value for an Accept header.') 

380 def generator(value): 

381 for match in ( 

382 cls.media_range_n_accept_params_compiled_re.finditer(value) 

383 ): 

384 groups = match.groups() 

385 

386 type_subtype = groups[0] 

387 

388 media_type_params = cls._parse_media_type_params( 

389 media_type_params_segment=groups[1], 

390 ) 

391 

392 media_range = cls._form_media_range( 

393 type_subtype=type_subtype, 

394 media_type_params=media_type_params, 

395 ) 

396 

397 # qvalue (groups[2]) and extension_params (groups[3]) are both 

398 # None if neither qvalue or extension parameters are found in 

399 # the match. 

400 

401 qvalue = groups[2] 

402 qvalue = float(qvalue) if qvalue else 1.0 

403 

404 extension_params = groups[3] 

405 if extension_params: 

406 extension_params = cls.accept_ext_compiled_re.findall( 

407 extension_params, 

408 ) 

409 for index, (token_key, token_value) in enumerate( 

410 extension_params 

411 ): 

412 if token_value: 

413 if ( 

414 token_value.startswith('"') and 

415 token_value.endswith('"') 

416 ): 

417 token_value = cls._process_quoted_string_token( 

418 token=token_value, 

419 ) 

420 extension_params[index] = ( 

421 token_key, token_value, 

422 ) 

423 else: 

424 extension_params[index] = token_key 

425 else: 

426 extension_params = [] 

427 

428 yield ( 

429 media_range, qvalue, media_type_params, extension_params, 

430 ) 

431 return generator(value=value) 

432 

433 @classmethod 

434 def parse_offer(cls, offer): 

435 """ 

436 Parse an offer into its component parts. 

437 

438 :param offer: A media type or range in the format 

439 ``type/subtype[;params]``. 

440 :return: A named tuple containing ``(*type*, *subtype*, *params*)``. 

441 

442 | *params* is a list containing ``(*parameter name*, *value*)`` 

443 values. 

444 

445 :raises ValueError: If the offer does not match the required format. 

446 

447 """ 

448 if isinstance(offer, AcceptOffer): 

449 return offer 

450 match = cls.media_type_compiled_re.match(offer) 

451 if not match: 

452 raise ValueError('Invalid value for an Accept offer.') 

453 

454 groups = match.groups() 

455 offer_type, offer_subtype = groups[0].split('/') 

456 offer_params = cls._parse_media_type_params( 

457 media_type_params_segment=groups[1], 

458 ) 

459 if offer_type == '*' or offer_subtype == '*': 

460 raise ValueError('Invalid value for an Accept offer.') 

461 return AcceptOffer( 

462 offer_type.lower(), 

463 offer_subtype.lower(), 

464 tuple((name.lower(), value) for name, value in offer_params), 

465 ) 

466 

467 @classmethod 

468 def _parse_and_normalize_offers(cls, offers): 

469 """ 

470 Throw out any offers that do not match the media range ABNF. 

471 

472 :return: A list of offers split into the format ``[offer_index, 

473 parsed_offer]``. 

474 

475 """ 

476 parsed_offers = [] 

477 for index, offer in enumerate(offers): 

478 try: 

479 parsed_offer = cls.parse_offer(offer) 

480 except ValueError: 

481 continue 

482 parsed_offers.append([index, parsed_offer]) 

483 return parsed_offers 

484 

485 

486class AcceptValidHeader(Accept): 

487 """ 

488 Represent a valid ``Accept`` header. 

489 

490 A valid header is one that conforms to :rfc:`RFC 7231, section 5.3.2 

491 <7231#section-5.3.2>`. 

492 

493 This object should not be modified. To add to the header, we can use the 

494 addition operators (``+`` and ``+=``), which return a new object (see the 

495 docstring for :meth:`AcceptValidHeader.__add__`). 

496 """ 

497 

498 @property 

499 def header_value(self): 

500 """(``str`` or ``None``) The header value.""" 

501 return self._header_value 

502 

503 @property 

504 def parsed(self): 

505 """ 

506 (``list`` or ``None``) Parsed form of the header. 

507 

508 A list of (*media_range*, *qvalue*, *media_type_params*, 

509 *extension_params*) tuples, where 

510 

511 *media_range* is the media range, including any media type parameters. 

512 The media range is returned in a canonicalised form (except the case of 

513 the characters are unchanged): unnecessary spaces around the semicolons 

514 before media type parameters are removed; the parameter values are 

515 returned in a form where only the '``\\``' and '``"``' characters are 

516 escaped, and the values are quoted with double quotes only if they need 

517 to be quoted. 

518 

519 *qvalue* is the quality value of the media range. 

520 

521 *media_type_params* is the media type parameters, as a list of 

522 (parameter name, value) tuples. 

523 

524 *extension_params* is the extension parameters, as a list where each 

525 item is either a parameter string or a (parameter name, value) tuple. 

526 """ 

527 return self._parsed 

528 

529 def __init__(self, header_value): 

530 """ 

531 Create an :class:`AcceptValidHeader` instance. 

532 

533 :param header_value: (``str``) header value. 

534 :raises ValueError: if `header_value` is an invalid value for an 

535 ``Accept`` header. 

536 """ 

537 self._header_value = header_value 

538 self._parsed = list(self.parse(header_value)) 

539 self._parsed_nonzero = [item for item in self.parsed if item[1]] 

540 # item[1] is the qvalue 

541 

542 def copy(self): 

543 """ 

544 Create a copy of the header object. 

545 

546 """ 

547 return self.__class__(self._header_value) 

548 

549 def __add__(self, other): 

550 """ 

551 Add to header, creating a new header object. 

552 

553 `other` can be: 

554 

555 * ``None`` 

556 * a ``str`` header value 

557 * a ``dict``, with media ranges ``str``'s (including any media type 

558 parameters) as keys, and either qvalues ``float``'s or (*qvalues*, 

559 *extension_params*) tuples as values, where *extension_params* is a 

560 ``str`` of the extension parameters segment of the header element, 

561 starting with the first '``;``' 

562 * a ``tuple`` or ``list``, where each item is either a header element 

563 ``str``, or a (*media_range*, *qvalue*, *extension_params*) ``tuple`` 

564 or ``list`` where *media_range* is a ``str`` of the media range 

565 including any media type parameters, and *extension_params* is a 

566 ``str`` of the extension parameters segment of the header element, 

567 starting with the first '``;``' 

568 * an :class:`AcceptValidHeader`, :class:`AcceptNoHeader`, or 

569 :class:`AcceptInvalidHeader` instance 

570 * object of any other type that returns a value for ``__str__`` 

571 

572 If `other` is a valid header value or another 

573 :class:`AcceptValidHeader` instance, and the header value it represents 

574 is not `''`, then the two header values are joined with ``', '``, and a 

575 new :class:`AcceptValidHeader` instance with the new header value is 

576 returned. 

577 

578 If `other` is a valid header value or another 

579 :class:`AcceptValidHeader` instance representing a header value of 

580 `''`; or if it is ``None`` or an :class:`AcceptNoHeader` instance; or 

581 if it is an invalid header value, or an :class:`AcceptInvalidHeader` 

582 instance, then a new :class:`AcceptValidHeader` instance with the same 

583 header value as ``self`` is returned. 

584 """ 

585 if isinstance(other, AcceptValidHeader): 

586 if other.header_value == '': 

587 return self.__class__(header_value=self.header_value) 

588 else: 

589 return create_accept_header( 

590 header_value=self.header_value + ', ' + other.header_value, 

591 ) 

592 

593 if isinstance(other, (AcceptNoHeader, AcceptInvalidHeader)): 

594 return self.__class__(header_value=self.header_value) 

595 

596 return self._add_instance_and_non_accept_type( 

597 instance=self, other=other, 

598 ) 

599 

600 def __bool__(self): 

601 """ 

602 Return whether ``self`` represents a valid ``Accept`` header. 

603 

604 Return ``True`` if ``self`` represents a valid header, and ``False`` if 

605 it represents an invalid header, or the header not being in the 

606 request. 

607 

608 For this class, it always returns ``True``. 

609 """ 

610 return True 

611 __nonzero__ = __bool__ # Python 2 

612 

613 def __contains__(self, offer): 

614 """ 

615 Return ``bool`` indicating whether `offer` is acceptable. 

616 

617 .. warning:: 

618 

619 The behavior of :meth:`AcceptValidHeader.__contains__` is currently 

620 being maintained for backward compatibility, but it will change in 

621 the future to better conform to the RFC. 

622 

623 :param offer: (``str``) media type offer 

624 :return: (``bool``) Whether ``offer`` is acceptable according to the 

625 header. 

626 

627 This uses the old criterion of a match in 

628 :meth:`AcceptValidHeader._old_match`, which is not as specified in 

629 :rfc:`RFC 7231, section 5.3.2 <7231#section-5.3.2>`. It does not 

630 correctly take into account media type parameters: 

631 

632 >>> 'text/html;p=1' in AcceptValidHeader('text/html') 

633 False 

634 

635 or media ranges with ``q=0`` in the header:: 

636 

637 >>> 'text/html' in AcceptValidHeader('text/*, text/html;q=0') 

638 True 

639 >>> 'text/html' in AcceptValidHeader('text/html;q=0, */*') 

640 True 

641 

642 (See the docstring for :meth:`AcceptValidHeader._old_match` for other 

643 problems with the old criterion for matching.) 

644 """ 

645 warnings.warn( 

646 'The behavior of AcceptValidHeader.__contains__ is ' 

647 'currently being maintained for backward compatibility, but it ' 

648 'will change in the future to better conform to the RFC.', 

649 DeprecationWarning, 

650 ) 

651 for ( 

652 media_range, quality, media_type_params, extension_params 

653 ) in self._parsed_nonzero: 

654 if self._old_match(media_range, offer): 

655 return True 

656 return False 

657 

658 def __iter__(self): 

659 """ 

660 Return all the ranges with non-0 qvalues, in order of preference. 

661 

662 .. warning:: 

663 

664 The behavior of this method is currently maintained for backward 

665 compatibility, but will change in the future. 

666 

667 :return: iterator of all the media ranges in the header with non-0 

668 qvalues, in descending order of qvalue. If two ranges have the 

669 same qvalue, they are returned in the order of their positions 

670 in the header, from left to right. 

671 

672 Please note that this is a simple filter for the ranges in the header 

673 with non-0 qvalues, and is not necessarily the same as what the client 

674 prefers, e.g. ``'audio/basic;q=0, */*'`` means 'everything but 

675 audio/basic', but ``list(instance)`` would return only ``['*/*']``. 

676 """ 

677 warnings.warn( 

678 'The behavior of AcceptLanguageValidHeader.__iter__ is currently ' 

679 'maintained for backward compatibility, but will change in the ' 

680 'future.', 

681 DeprecationWarning, 

682 ) 

683 

684 for media_range, qvalue, media_type_params, extension_params in sorted( 

685 self._parsed_nonzero, 

686 key=lambda i: i[1], 

687 reverse=True 

688 ): 

689 yield media_range 

690 

691 def __radd__(self, other): 

692 """ 

693 Add to header, creating a new header object. 

694 

695 See the docstring for :meth:`AcceptValidHeader.__add__`. 

696 """ 

697 return self._add_instance_and_non_accept_type( 

698 instance=self, other=other, instance_on_the_right=True, 

699 ) 

700 

701 def __repr__(self): 

702 return '<{} ({!r})>'.format(self.__class__.__name__, str(self)) 

703 

704 def __str__(self): 

705 r""" 

706 Return a tidied up version of the header value. 

707 

708 e.g. If ``self.header_value`` is ``r',,text/html ; p1="\"\1\"" ; 

709 q=0.50; e1=1 ;e2 , text/plain ,'``, ``str(instance)`` returns 

710 ``r'text/html;p1="\"1\"";q=0.5;e1=1;e2, text/plain'``. 

711 """ 

712 # self.parsed tuples are in the form: (media_range, qvalue, 

713 # media_type_params, extension_params) 

714 # self._iterable_to_header_element() requires iterable to be in the 

715 # form: (media_range, qvalue, extension_params_segment). 

716 return ', '.join( 

717 self._iterable_to_header_element( 

718 iterable=( 

719 tuple_[0], # media_range 

720 tuple_[1], # qvalue 

721 self._form_extension_params_segment( 

722 extension_params=tuple_[3], # extension_params 

723 ) 

724 ), 

725 ) for tuple_ in self.parsed 

726 ) 

727 

728 def _add_instance_and_non_accept_type( 

729 self, instance, other, instance_on_the_right=False, 

730 ): 

731 if not other: 

732 return self.__class__(header_value=instance.header_value) 

733 

734 other_header_value = self._python_value_to_header_str(value=other) 

735 

736 if other_header_value == '': 

737 # if ``other`` is an object whose type we don't recognise, and 

738 # str(other) returns '' 

739 return self.__class__(header_value=instance.header_value) 

740 

741 try: 

742 self.parse(value=other_header_value) 

743 except ValueError: # invalid header value 

744 return self.__class__(header_value=instance.header_value) 

745 

746 new_header_value = ( 

747 (other_header_value + ', ' + instance.header_value) 

748 if instance_on_the_right 

749 else (instance.header_value + ', ' + other_header_value) 

750 ) 

751 return self.__class__(header_value=new_header_value) 

752 

753 def _old_match(self, mask, offer): 

754 """ 

755 Check if the offer is covered by the mask 

756 

757 ``offer`` may contain wildcards to facilitate checking if a ``mask`` 

758 would match a 'permissive' offer. 

759 

760 Wildcard matching forces the match to take place against the type or 

761 subtype of the mask and offer (depending on where the wildcard matches) 

762 

763 .. warning:: 

764 

765 This is maintained for backward compatibility, and will be 

766 deprecated in the future. 

767 

768 This method was WebOb's old criterion for deciding whether a media type 

769 matches a media range, used in 

770 

771 - :meth:`AcceptValidHeader.__contains__` 

772 - :meth:`AcceptValidHeader.best_match` 

773 - :meth:`AcceptValidHeader.quality` 

774 

775 It allows offers of *, */*, type/*, */subtype and types with no 

776 subtypes, which are not media types as specified in :rfc:`RFC 7231, 

777 section 5.3.2 <7231#section-5.3.2>`. This is also undocumented in any 

778 of the public APIs that uses this method. 

779 """ 

780 # Match if comparisons are the same or either is a complete wildcard 

781 if (mask.lower() == offer.lower() or 

782 '*/*' in (mask, offer) or 

783 '*' == offer): 

784 return True 

785 

786 # Set mask type with wildcard subtype for malformed masks 

787 try: 

788 mask_type, mask_subtype = [x.lower() for x in mask.split('/')] 

789 except ValueError: 

790 mask_type = mask 

791 mask_subtype = '*' 

792 

793 # Set offer type with wildcard subtype for malformed offers 

794 try: 

795 offer_type, offer_subtype = [x.lower() for x in offer.split('/')] 

796 except ValueError: 

797 offer_type = offer 

798 offer_subtype = '*' 

799 

800 if mask_subtype == '*': 

801 # match on type only 

802 if offer_type == '*': 

803 return True 

804 else: 

805 return mask_type.lower() == offer_type.lower() 

806 

807 if mask_type == '*': 

808 # match on subtype only 

809 if offer_subtype == '*': 

810 return True 

811 else: 

812 return mask_subtype.lower() == offer_subtype.lower() 

813 

814 if offer_subtype == '*': 

815 # match on type only 

816 return mask_type.lower() == offer_type.lower() 

817 

818 if offer_type == '*': 

819 # match on subtype only 

820 return mask_subtype.lower() == offer_subtype.lower() 

821 

822 return offer.lower() == mask.lower() 

823 

824 def accept_html(self): 

825 """ 

826 Return ``True`` if any HTML-like type is accepted. 

827 

828 The HTML-like types are 'text/html', 'application/xhtml+xml', 

829 'application/xml' and 'text/xml'. 

830 """ 

831 return bool( 

832 self.acceptable_offers( 

833 offers=[ 

834 'text/html', 

835 'application/xhtml+xml', 

836 'application/xml', 

837 'text/xml', 

838 ], 

839 ) 

840 ) 

841 accepts_html = property(fget=accept_html, doc=accept_html.__doc__) 

842 # note the plural 

843 

844 def acceptable_offers(self, offers): 

845 """ 

846 Return the offers that are acceptable according to the header. 

847 

848 The offers are returned in descending order of preference, where 

849 preference is indicated by the qvalue of the media range in the header 

850 that best matches the offer. 

851 

852 This uses the matching rules described in :rfc:`RFC 7231, section 5.3.2 

853 <7231#section-5.3.2>`. 

854 

855 Any offers that cannot be parsed via 

856 :meth:`.Accept.parse_offer` will be ignored. 

857 

858 :param offers: ``iterable`` of ``str`` media types (media types can 

859 include media type parameters) or pre-parsed instances 

860 of :class:`.AcceptOffer`. 

861 :return: A list of tuples of the form (media type, qvalue), in 

862 descending order of qvalue. Where two offers have the same 

863 qvalue, they are returned in the same order as their order in 

864 `offers`. 

865 """ 

866 parsed = self.parsed 

867 

868 # RFC 7231, section 3.1.1.1 "Media Type": 

869 # "The type, subtype, and parameter name tokens are case-insensitive. 

870 # Parameter values might or might not be case-sensitive, depending on 

871 # the semantics of the parameter name." 

872 lowercased_ranges = [ 

873 ( 

874 media_range.partition(';')[0].lower(), 

875 qvalue, 

876 tuple( 

877 (name.lower(), value) 

878 for name, value in media_type_params 

879 ), 

880 ) 

881 for media_range, qvalue, media_type_params, __ in 

882 parsed 

883 ] 

884 lowercased_offers_parsed = self._parse_and_normalize_offers(offers) 

885 

886 acceptable_offers_n_quality_factors = {} 

887 for offer_index, parsed_offer in lowercased_offers_parsed: 

888 offer = offers[offer_index] 

889 offer_type, offer_subtype, offer_media_type_params = parsed_offer 

890 for ( 

891 range_type_subtype, range_qvalue, range_media_type_params, 

892 ) in lowercased_ranges: 

893 range_type, range_subtype = range_type_subtype.split('/', 1) 

894 

895 # The specificity values below are based on the list in the 

896 # example in RFC 7231 section 5.3.2 explaining how "media 

897 # ranges can be overridden by more specific media ranges or 

898 # specific media types". We assign specificity to the list 

899 # items in reverse order, so specificity 4, 3, 2, 1 correspond 

900 # to 1, 2, 3, 4 in the list, respectively (so that higher 

901 # specificity has higher precedence). 

902 if ( 

903 offer_type == range_type 

904 and offer_subtype == range_subtype 

905 ): 

906 if range_media_type_params == (): 

907 # If offer_media_type_params == () the offer and the 

908 # range match exactly, with neither having media type 

909 # parameters. 

910 # If offer_media_type_params is not (), the offer and 

911 # the range are a match. See the table towards the end 

912 # of RFC 7231 section 5.3.2, where the media type 

913 # 'text/html;level=3' matches the range 'text/html' in 

914 # the header. 

915 # Both cases are a match with a specificity of 3. 

916 specificity = 3 

917 elif offer_media_type_params == range_media_type_params: 

918 specificity = 4 

919 else: # pragma: no cover 

920 # no cover because of 

921 # https://bitbucket.org/ned/coveragepy/issues/254/incorrect-coverage-on-continue-statement 

922 continue 

923 else: 

924 if range_subtype == '*' and offer_type == range_type: 

925 specificity = 2 

926 elif range_type_subtype == '*/*': 

927 specificity = 1 

928 else: # pragma: no cover 

929 # no cover because of 

930 # https://bitbucket.org/ned/coveragepy/issues/254/incorrect-coverage-on-continue-statement 

931 continue 

932 try: 

933 if specificity <= ( 

934 acceptable_offers_n_quality_factors[offer][2] 

935 ): 

936 continue 

937 except KeyError: 

938 # the entry for the offer is not already in 

939 # acceptable_offers_n_quality_factors 

940 pass 

941 acceptable_offers_n_quality_factors[offer] = ( 

942 range_qvalue, # qvalue of matched range 

943 offer_index, 

944 specificity, # specifity of matched range 

945 ) 

946 

947 acceptable_offers_n_quality_factors = [ 

948 # key is offer, value[0] is qvalue, value[1] is offer_index 

949 (key, value[0], value[1]) 

950 for key, value in acceptable_offers_n_quality_factors.items() 

951 if value[0] # != 0.0 

952 # We have to filter out the offers with qvalues of 0 here instead 

953 # of just skipping them early in the large ``for`` loop because 

954 # that would not work for e.g. when the header is 'text/html;q=0, 

955 # text/html' (which does not make sense, but is nonetheless valid), 

956 # and offers is ['text/html'] 

957 ] 

958 # sort by offer_index, ascending 

959 acceptable_offers_n_quality_factors.sort(key=lambda tuple_: tuple_[2]) 

960 # (stable) sort by qvalue, descending 

961 acceptable_offers_n_quality_factors.sort( 

962 key=lambda tuple_: tuple_[1], reverse=True, 

963 ) 

964 # drop offer_index 

965 acceptable_offers_n_quality_factors = [ 

966 (item[0], item[1]) for item in acceptable_offers_n_quality_factors 

967 ] 

968 return acceptable_offers_n_quality_factors 

969 # If a media range is repeated in the header (which would not make 

970 # sense, but would be valid according to the rules in the RFC), an 

971 # offer for which the media range is the most specific match would take 

972 # its qvalue from the first appearance of the range in the header. 

973 

974 def best_match(self, offers, default_match=None): 

975 """ 

976 Return the best match from the sequence of media type `offers`. 

977 

978 .. warning:: 

979 

980 This is currently maintained for backward compatibility, and will be 

981 deprecated in the future. 

982 

983 :meth:`AcceptValidHeader.best_match` uses its own algorithm (one not 

984 specified in :rfc:`RFC 7231 <7231>`) to determine what is a best 

985 match. The algorithm has many issues, and does not conform to 

986 :rfc:`RFC 7231 <7231>`. 

987 

988 Each media type in `offers` is checked against each non-``q=0`` range 

989 in the header. If the two are a match according to WebOb's old 

990 criterion for a match, the quality value of the match is the qvalue of 

991 the media range from the header multiplied by the server quality value 

992 of the offer (if the server quality value is not supplied, it is 1). 

993 

994 The offer in the match with the highest quality value is the best 

995 match. If there is more than one match with the highest qvalue, the 

996 match where the media range has a lower number of '*'s is the best 

997 match. If the two have the same number of '*'s, the one that shows up 

998 first in `offers` is the best match. 

999 

1000 :param offers: (iterable) 

1001 

1002 | Each item in the iterable may be a ``str`` media type, 

1003 or a (media type, server quality value) ``tuple`` or 

1004 ``list``. (The two may be mixed in the iterable.) 

1005 

1006 :param default_match: (optional, any type) the value to be returned if 

1007 there is no match 

1008 

1009 :return: (``str``, or the type of `default_match`) 

1010 

1011 | The offer that is the best match. If there is no match, the 

1012 value of `default_match` is returned. 

1013 

1014 This uses the old criterion of a match in 

1015 :meth:`AcceptValidHeader._old_match`, which is not as specified in 

1016 :rfc:`RFC 7231, section 5.3.2 <7231#section-5.3.2>`. It does not 

1017 correctly take into account media type parameters: 

1018 

1019 >>> instance = AcceptValidHeader('text/html') 

1020 >>> instance.best_match(offers=['text/html;p=1']) is None 

1021 True 

1022 

1023 or media ranges with ``q=0`` in the header:: 

1024 

1025 >>> instance = AcceptValidHeader('text/*, text/html;q=0') 

1026 >>> instance.best_match(offers=['text/html']) 

1027 'text/html' 

1028 

1029 >>> instance = AcceptValidHeader('text/html;q=0, */*') 

1030 >>> instance.best_match(offers=['text/html']) 

1031 'text/html' 

1032 

1033 (See the docstring for :meth:`AcceptValidHeader._old_match` for other 

1034 problems with the old criterion for matching.) 

1035 

1036 Another issue is that this method considers the best matching range for 

1037 an offer to be the matching range with the highest quality value, 

1038 (where quality values are tied, the most specific media range is 

1039 chosen); whereas :rfc:`RFC 7231, section 5.3.2 <7231#section-5.3.2>` 

1040 specifies that we should consider the best matching range for a media 

1041 type offer to be the most specific matching range.:: 

1042 

1043 >>> instance = AcceptValidHeader('text/html;q=0.5, text/*') 

1044 >>> instance.best_match(offers=['text/html', 'text/plain']) 

1045 'text/html' 

1046 """ 

1047 warnings.warn( 

1048 'The behavior of AcceptValidHeader.best_match is currently being ' 

1049 'maintained for backward compatibility, but it will be deprecated' 

1050 ' in the future, as it does not conform to the RFC.', 

1051 DeprecationWarning, 

1052 ) 

1053 best_quality = -1 

1054 best_offer = default_match 

1055 matched_by = '*/*' 

1056 for offer in offers: 

1057 if isinstance(offer, (tuple, list)): 

1058 offer, server_quality = offer 

1059 else: 

1060 server_quality = 1 

1061 for item in self._parsed_nonzero: 

1062 mask = item[0] 

1063 quality = item[1] 

1064 possible_quality = server_quality * quality 

1065 if possible_quality < best_quality: 

1066 continue 

1067 elif possible_quality == best_quality: 

1068 # 'text/plain' overrides 'message/*' overrides '*/*' 

1069 # (if all match w/ the same q=) 

1070 if matched_by.count('*') <= mask.count('*'): 

1071 continue 

1072 if self._old_match(mask, offer): 

1073 best_quality = possible_quality 

1074 best_offer = offer 

1075 matched_by = mask 

1076 return best_offer 

1077 

1078 def quality(self, offer): 

1079 """ 

1080 Return quality value of given offer, or ``None`` if there is no match. 

1081 

1082 .. warning:: 

1083 

1084 This is currently maintained for backward compatibility, and will be 

1085 deprecated in the future. 

1086 

1087 :param offer: (``str``) media type offer 

1088 :return: (``float`` or ``None``) 

1089 

1090 | The highest quality value from the media range(s) that match 

1091 the `offer`, or ``None`` if there is no match. 

1092 

1093 This uses the old criterion of a match in 

1094 :meth:`AcceptValidHeader._old_match`, which is not as specified in 

1095 :rfc:`RFC 7231, section 5.3.2 <7231#section-5.3.2>`. It does not 

1096 correctly take into account media type parameters: 

1097 

1098 >>> instance = AcceptValidHeader('text/html') 

1099 >>> instance.quality('text/html;p=1') is None 

1100 True 

1101 

1102 or media ranges with ``q=0`` in the header:: 

1103 

1104 >>> instance = AcceptValidHeader('text/*, text/html;q=0') 

1105 >>> instance.quality('text/html') 

1106 1.0 

1107 >>> AcceptValidHeader('text/html;q=0, */*').quality('text/html') 

1108 1.0 

1109 

1110 (See the docstring for :meth:`AcceptValidHeader._old_match` for other 

1111 problems with the old criterion for matching.) 

1112 

1113 Another issue is that this method considers the best matching range for 

1114 an offer to be the matching range with the highest quality value, 

1115 whereas :rfc:`RFC 7231, section 5.3.2 <7231#section-5.3.2>` specifies 

1116 that we should consider the best matching range for a media type offer 

1117 to be the most specific matching range.:: 

1118 

1119 >>> instance = AcceptValidHeader('text/html;q=0.5, text/*') 

1120 >>> instance.quality('text/html') 

1121 1.0 

1122 """ 

1123 warnings.warn( 

1124 'The behavior of AcceptValidHeader.quality is currently being ' 

1125 'maintained for backward compatibility, but it will be deprecated ' 

1126 'in the future, as it does not conform to the RFC.', 

1127 DeprecationWarning, 

1128 ) 

1129 bestq = 0 

1130 for item in self.parsed: 

1131 media_range = item[0] 

1132 qvalue = item[1] 

1133 if self._old_match(media_range, offer): 

1134 bestq = max(bestq, qvalue) 

1135 return bestq or None 

1136 

1137 

1138class MIMEAccept(Accept): 

1139 """ 

1140 Backwards compatibility shim for the new functionality provided by 

1141 AcceptValidHeader, AcceptInvalidHeader, or AcceptNoHeader, that acts like 

1142 the old MIMEAccept from WebOb version 1.7 or lower. 

1143 

1144 This shim does use the newer Accept header parsing, which will mean your 

1145 application may be less liberal in what Accept headers are correctly 

1146 parsed. It is recommended that user agents be updated to send appropriate 

1147 Accept headers that are valid according to rfc:`RFC 7231, section 5.3.2 

1148 <7231#section-5.3.2>` 

1149 

1150 .. deprecated:: 1.8 

1151 

1152 Instead of directly creating the Accept object, please see: 

1153 :func:`create_accept_header(header_value) 

1154 <webob.acceptparse.create_accept_header>`, which will create the 

1155 appropriate object. 

1156 

1157 This shim has an extended deprecation period to allow for application 

1158 developers to switch the to new API. 

1159 

1160 """ 

1161 

1162 def __init__(self, header_value): 

1163 warnings.warn( 

1164 'The MIMEAccept class has been replaced by ' 

1165 'webob.acceptparse.create_accept_header. This compatibility shim ' 

1166 'will be deprecated in a future version of WebOb.', 

1167 DeprecationWarning 

1168 ) 

1169 self._accept = create_accept_header(header_value) 

1170 if self._accept.parsed: 

1171 self._parsed = [(media, q) for (media, q, _, _) in self._accept.parsed] 

1172 self._parsed_nonzero = [(m, q) for (m, q) in self._parsed if q] 

1173 else: 

1174 self._parsed = [] 

1175 self._parsed_nonzero = [] 

1176 

1177 @staticmethod 

1178 def parse(value): 

1179 try: 

1180 parsed_accepted = Accept.parse(value) 

1181 

1182 for (media, q, _, _) in parsed_accepted: 

1183 yield (media, q) 

1184 except ValueError: 

1185 pass 

1186 

1187 def __repr__(self): 

1188 return self._accept.__repr__() 

1189 

1190 def __iter__(self): 

1191 return self._accept.__iter__() 

1192 

1193 def __str__(self): 

1194 return self._accept.__str__() 

1195 

1196 def __add__(self, other): 

1197 if isinstance(other, self.__class__): 

1198 return self.__class__(str(self._accept.__add__(other._accept))) 

1199 else: 

1200 return self.__class__(str(self._accept.__add__(other))) 

1201 

1202 def __radd__(self, other): 

1203 return self.__class__(str(self._accept.__radd__(other))) 

1204 

1205 def __contains__(self, offer): 

1206 return offer in self._accept 

1207 

1208 def quality(self, offer): 

1209 return self._accept.quality(offer) 

1210 

1211 def best_match(self, offers, default_match=None): 

1212 return self._accept.best_match(offers, default_match=default_match) 

1213 

1214 def accept_html(self): 

1215 return self._accept.accept_html() 

1216 

1217 

1218class _AcceptInvalidOrNoHeader(Accept): 

1219 """ 

1220 Represent when an ``Accept`` header is invalid or not in request. 

1221 

1222 This is the base class for the behaviour that :class:`.AcceptInvalidHeader` 

1223 and :class:`.AcceptNoHeader` have in common. 

1224 

1225 :rfc:`7231` does not provide any guidance on what should happen if the 

1226 ``Accept`` header has an invalid value. This implementation disregards the 

1227 header when the header is invalid, so :class:`.AcceptInvalidHeader` and 

1228 :class:`.AcceptNoHeader` have much behaviour in common. 

1229 """ 

1230 

1231 def __bool__(self): 

1232 """ 

1233 Return whether ``self`` represents a valid ``Accept`` header. 

1234 

1235 Return ``True`` if ``self`` represents a valid header, and ``False`` if 

1236 it represents an invalid header, or the header not being in the 

1237 request. 

1238 

1239 For this class, it always returns ``False``. 

1240 """ 

1241 return False 

1242 __nonzero__ = __bool__ # Python 2 

1243 

1244 def __contains__(self, offer): 

1245 """ 

1246 Return ``bool`` indicating whether `offer` is acceptable. 

1247 

1248 .. warning:: 

1249 

1250 The behavior of ``.__contains__`` for the ``Accept`` classes is 

1251 currently being maintained for backward compatibility, but it will 

1252 change in the future to better conform to the RFC. 

1253 

1254 :param offer: (``str``) media type offer 

1255 :return: (``bool``) Whether ``offer`` is acceptable according to the 

1256 header. 

1257 

1258 For this class, either there is no ``Accept`` header in the request, or 

1259 the header is invalid, so any media type is acceptable, and this always 

1260 returns ``True``. 

1261 """ 

1262 warnings.warn( 

1263 'The behavior of .__contains__ for the Accept classes is ' 

1264 'currently being maintained for backward compatibility, but it ' 

1265 'will change in the future to better conform to the RFC.', 

1266 DeprecationWarning, 

1267 ) 

1268 return True 

1269 

1270 def __iter__(self): 

1271 """ 

1272 Return all the ranges with non-0 qvalues, in order of preference. 

1273 

1274 .. warning:: 

1275 

1276 The behavior of this method is currently maintained for backward 

1277 compatibility, but will change in the future. 

1278 

1279 :return: iterator of all the media ranges in the header with non-0 

1280 qvalues, in descending order of qvalue. If two ranges have the 

1281 same qvalue, they are returned in the order of their positions 

1282 in the header, from left to right. 

1283 

1284 When there is no ``Accept`` header in the request or the header is 

1285 invalid, there are no media ranges, so this always returns an empty 

1286 iterator. 

1287 """ 

1288 warnings.warn( 

1289 'The behavior of AcceptValidHeader.__iter__ is currently ' 

1290 'maintained for backward compatibility, but will change in the ' 

1291 'future.', 

1292 DeprecationWarning, 

1293 ) 

1294 return iter(()) 

1295 

1296 def accept_html(self): 

1297 """ 

1298 Return ``True`` if any HTML-like type is accepted. 

1299 

1300 The HTML-like types are 'text/html', 'application/xhtml+xml', 

1301 'application/xml' and 'text/xml'. 

1302 

1303 When the header is invalid, or there is no `Accept` header in the 

1304 request, all `offers` are considered acceptable, so this always returns 

1305 ``True``. 

1306 """ 

1307 return bool( 

1308 self.acceptable_offers( 

1309 offers=[ 

1310 'text/html', 

1311 'application/xhtml+xml', 

1312 'application/xml', 

1313 'text/xml', 

1314 ], 

1315 ) 

1316 ) 

1317 accepts_html = property(fget=accept_html, doc=accept_html.__doc__) 

1318 # note the plural 

1319 

1320 def acceptable_offers(self, offers): 

1321 """ 

1322 Return the offers that are acceptable according to the header. 

1323 

1324 Any offers that cannot be parsed via 

1325 :meth:`.Accept.parse_offer` will be ignored. 

1326 

1327 :param offers: ``iterable`` of ``str`` media types (media types can 

1328 include media type parameters) 

1329 :return: When the header is invalid, or there is no ``Accept`` header 

1330 in the request, all `offers` are considered acceptable, so 

1331 this method returns a list of (media type, qvalue) tuples 

1332 where each offer in `offers` is paired with the qvalue of 1.0, 

1333 in the same order as in `offers`. 

1334 """ 

1335 return [ 

1336 (offers[offer_index], 1.0) 

1337 for offer_index, _ 

1338 # avoid returning any offers that don't match the grammar so 

1339 # that the return values here are consistent with what would be 

1340 # returned in AcceptValidHeader 

1341 in self._parse_and_normalize_offers(offers) 

1342 ] 

1343 

1344 def best_match(self, offers, default_match=None): 

1345 """ 

1346 Return the best match from the sequence of language tag `offers`. 

1347 

1348 This is the ``.best_match()`` method for when the header is invalid or 

1349 not found in the request, corresponding to 

1350 :meth:`AcceptValidHeader.best_match`. 

1351 

1352 .. warning:: 

1353 

1354 This is currently maintained for backward compatibility, and will be 

1355 deprecated in the future (see the documentation for 

1356 :meth:`AcceptValidHeader.best_match`). 

1357 

1358 When the header is invalid, or there is no `Accept` header in the 

1359 request, all `offers` are considered acceptable, so the best match is 

1360 the media type in `offers` with the highest server quality value (if 

1361 the server quality value is not supplied for a media type, it is 1). 

1362 

1363 If more than one media type in `offers` have the same highest server 

1364 quality value, then the one that shows up first in `offers` is the best 

1365 match. 

1366 

1367 :param offers: (iterable) 

1368 

1369 | Each item in the iterable may be a ``str`` media type, 

1370 or a (media type, server quality value) ``tuple`` or 

1371 ``list``. (The two may be mixed in the iterable.) 

1372 

1373 :param default_match: (optional, any type) the value to be returned if 

1374 `offers` is empty. 

1375 

1376 :return: (``str``, or the type of `default_match`) 

1377 

1378 | The offer that has the highest server quality value. If 

1379 `offers` is empty, the value of `default_match` is returned. 

1380 """ 

1381 warnings.warn( 

1382 'The behavior of .best_match for the Accept classes is currently ' 

1383 'being maintained for backward compatibility, but the method will' 

1384 ' be deprecated in the future, as its behavior is not specified ' 

1385 'in (and currently does not conform to) RFC 7231.', 

1386 DeprecationWarning, 

1387 ) 

1388 best_quality = -1 

1389 best_offer = default_match 

1390 for offer in offers: 

1391 if isinstance(offer, (list, tuple)): 

1392 offer, quality = offer 

1393 else: 

1394 quality = 1 

1395 if quality > best_quality: 

1396 best_offer = offer 

1397 best_quality = quality 

1398 return best_offer 

1399 

1400 def quality(self, offer): 

1401 """ 

1402 Return quality value of given offer, or ``None`` if there is no match. 

1403 

1404 This is the ``.quality()`` method for when the header is invalid or not 

1405 found in the request, corresponding to 

1406 :meth:`AcceptValidHeader.quality`. 

1407 

1408 .. warning:: 

1409 

1410 This is currently maintained for backward compatibility, and will be 

1411 deprecated in the future (see the documentation for 

1412 :meth:`AcceptValidHeader.quality`). 

1413 

1414 :param offer: (``str``) media type offer 

1415 :return: (``float``) ``1.0``. 

1416 

1417 When the ``Accept`` header is invalid or not in the request, all offers 

1418 are equally acceptable, so 1.0 is always returned. 

1419 """ 

1420 warnings.warn( 

1421 'The behavior of .quality for the Accept classes is currently ' 

1422 'being maintained for backward compatibility, but the method will' 

1423 ' be deprecated in the future, as its behavior does not conform to' 

1424 'RFC 7231.', 

1425 DeprecationWarning, 

1426 ) 

1427 return 1.0 

1428 

1429 

1430class AcceptNoHeader(_AcceptInvalidOrNoHeader): 

1431 """ 

1432 Represent when there is no ``Accept`` header in the request. 

1433 

1434 This object should not be modified. To add to the header, we can use the 

1435 addition operators (``+`` and ``+=``), which return a new object (see the 

1436 docstring for :meth:`AcceptNoHeader.__add__`). 

1437 """ 

1438 

1439 @property 

1440 def header_value(self): 

1441 """ 

1442 (``str`` or ``None``) The header value. 

1443 

1444 As there is no header in the request, this is ``None``. 

1445 """ 

1446 return self._header_value 

1447 

1448 @property 

1449 def parsed(self): 

1450 """ 

1451 (``list`` or ``None``) Parsed form of the header. 

1452 

1453 As there is no header in the request, this is ``None``. 

1454 """ 

1455 return self._parsed 

1456 

1457 def __init__(self): 

1458 """ 

1459 Create an :class:`AcceptNoHeader` instance. 

1460 """ 

1461 self._header_value = None 

1462 self._parsed = None 

1463 self._parsed_nonzero = None 

1464 

1465 def copy(self): 

1466 """ 

1467 Create a copy of the header object. 

1468 

1469 """ 

1470 return self.__class__() 

1471 

1472 def __add__(self, other): 

1473 """ 

1474 Add to header, creating a new header object. 

1475 

1476 `other` can be: 

1477 

1478 * ``None`` 

1479 * a ``str`` header value 

1480 * a ``dict``, with media ranges ``str``'s (including any media type 

1481 parameters) as keys, and either qvalues ``float``'s or (*qvalues*, 

1482 *extension_params*) tuples as values, where *extension_params* is a 

1483 ``str`` of the extension parameters segment of the header element, 

1484 starting with the first '``;``' 

1485 * a ``tuple`` or ``list``, where each item is either a header element 

1486 ``str``, or a (*media_range*, *qvalue*, *extension_params*) ``tuple`` 

1487 or ``list`` where *media_range* is a ``str`` of the media range 

1488 including any media type parameters, and *extension_params* is a 

1489 ``str`` of the extension parameters segment of the header element, 

1490 starting with the first '``;``' 

1491 * an :class:`AcceptValidHeader`, :class:`AcceptNoHeader`, or 

1492 :class:`AcceptInvalidHeader` instance 

1493 * object of any other type that returns a value for ``__str__`` 

1494 

1495 If `other` is a valid header value or an :class:`AcceptValidHeader` 

1496 instance, a new :class:`AcceptValidHeader` instance with the valid 

1497 header value is returned. 

1498 

1499 If `other` is ``None``, an :class:`AcceptNoHeader` instance, an invalid 

1500 header value, or an :class:`AcceptInvalidHeader` instance, a new 

1501 :class:`AcceptNoHeader` instance is returned. 

1502 """ 

1503 if isinstance(other, AcceptValidHeader): 

1504 return AcceptValidHeader(header_value=other.header_value) 

1505 

1506 if isinstance(other, (AcceptNoHeader, AcceptInvalidHeader)): 

1507 return self.__class__() 

1508 

1509 return self._add_instance_and_non_accept_type( 

1510 instance=self, other=other, 

1511 ) 

1512 

1513 def __radd__(self, other): 

1514 """ 

1515 Add to header, creating a new header object. 

1516 

1517 See the docstring for :meth:`AcceptNoHeader.__add__`. 

1518 """ 

1519 return self.__add__(other=other) 

1520 

1521 def __repr__(self): 

1522 return '<{}>'.format(self.__class__.__name__) 

1523 

1524 def __str__(self): 

1525 """Return the ``str`` ``'<no header in request>'``.""" 

1526 return '<no header in request>' 

1527 

1528 def _add_instance_and_non_accept_type(self, instance, other): 

1529 if other is None: 

1530 return self.__class__() 

1531 

1532 other_header_value = self._python_value_to_header_str(value=other) 

1533 

1534 try: 

1535 return AcceptValidHeader(header_value=other_header_value) 

1536 except ValueError: # invalid header value 

1537 return self.__class__() 

1538 

1539 

1540class AcceptInvalidHeader(_AcceptInvalidOrNoHeader): 

1541 """ 

1542 Represent an invalid ``Accept`` header. 

1543 

1544 An invalid header is one that does not conform to 

1545 :rfc:`7231#section-5.3.2`. 

1546 

1547 :rfc:`7231` does not provide any guidance on what should happen if the 

1548 ``Accept`` header has an invalid value. This implementation disregards the 

1549 header, and treats it as if there is no ``Accept`` header in the request. 

1550 

1551 This object should not be modified. To add to the header, we can use the 

1552 addition operators (``+`` and ``+=``), which return a new object (see the 

1553 docstring for :meth:`AcceptInvalidHeader.__add__`). 

1554 """ 

1555 

1556 @property 

1557 def header_value(self): 

1558 """(``str`` or ``None``) The header value.""" 

1559 return self._header_value 

1560 

1561 @property 

1562 def parsed(self): 

1563 """ 

1564 (``list`` or ``None``) Parsed form of the header. 

1565 

1566 As the header is invalid and cannot be parsed, this is ``None``. 

1567 """ 

1568 return self._parsed 

1569 

1570 def __init__(self, header_value): 

1571 """ 

1572 Create an :class:`AcceptInvalidHeader` instance. 

1573 """ 

1574 self._header_value = header_value 

1575 self._parsed = None 

1576 self._parsed_nonzero = None 

1577 

1578 def copy(self): 

1579 """ 

1580 Create a copy of the header object. 

1581 

1582 """ 

1583 return self.__class__(self._header_value) 

1584 

1585 def __add__(self, other): 

1586 """ 

1587 Add to header, creating a new header object. 

1588 

1589 `other` can be: 

1590 

1591 * ``None`` 

1592 * a ``str`` header value 

1593 * a ``dict``, with media ranges ``str``'s (including any media type 

1594 parameters) as keys, and either qvalues ``float``'s or (*qvalues*, 

1595 *extension_params*) tuples as values, where *extension_params* is a 

1596 ``str`` of the extension parameters segment of the header element, 

1597 starting with the first '``;``' 

1598 * a ``tuple`` or ``list``, where each item is either a header element 

1599 ``str``, or a (*media_range*, *qvalue*, *extension_params*) ``tuple`` 

1600 or ``list`` where *media_range* is a ``str`` of the media range 

1601 including any media type parameters, and *extension_params* is a 

1602 ``str`` of the extension parameters segment of the header element, 

1603 starting with the first '``;``' 

1604 * an :class:`AcceptValidHeader`, :class:`AcceptNoHeader`, or 

1605 :class:`AcceptInvalidHeader` instance 

1606 * object of any other type that returns a value for ``__str__`` 

1607 

1608 If `other` is a valid header value or an :class:`AcceptValidHeader` 

1609 instance, then a new :class:`AcceptValidHeader` instance with the valid 

1610 header value is returned. 

1611 

1612 If `other` is ``None``, an :class:`AcceptNoHeader` instance, an invalid 

1613 header value, or an :class:`AcceptInvalidHeader` instance, a new 

1614 :class:`AcceptNoHeader` instance is returned. 

1615 """ 

1616 if isinstance(other, AcceptValidHeader): 

1617 return AcceptValidHeader(header_value=other.header_value) 

1618 

1619 if isinstance(other, (AcceptNoHeader, AcceptInvalidHeader)): 

1620 return AcceptNoHeader() 

1621 

1622 return self._add_instance_and_non_accept_type( 

1623 instance=self, other=other, 

1624 ) 

1625 

1626 def __radd__(self, other): 

1627 """ 

1628 Add to header, creating a new header object. 

1629 

1630 See the docstring for :meth:`AcceptValidHeader.__add__`. 

1631 """ 

1632 return self._add_instance_and_non_accept_type( 

1633 instance=self, other=other, instance_on_the_right=True, 

1634 ) 

1635 

1636 def __repr__(self): 

1637 return '<{}>'.format(self.__class__.__name__) 

1638 # We do not display the header_value, as it is untrusted input. The 

1639 # header_value could always be easily obtained from the .header_value 

1640 # property. 

1641 

1642 def __str__(self): 

1643 """Return the ``str`` ``'<invalid header value>'``.""" 

1644 return '<invalid header value>' 

1645 

1646 def _add_instance_and_non_accept_type( 

1647 self, instance, other, instance_on_the_right=False, 

1648 ): 

1649 if other is None: 

1650 return AcceptNoHeader() 

1651 

1652 other_header_value = self._python_value_to_header_str(value=other) 

1653 

1654 try: 

1655 return AcceptValidHeader(header_value=other_header_value) 

1656 except ValueError: # invalid header value 

1657 return AcceptNoHeader() 

1658 

1659 

1660def create_accept_header(header_value): 

1661 """ 

1662 Create an object representing the ``Accept`` header in a request. 

1663 

1664 :param header_value: (``str``) header value 

1665 :return: If `header_value` is ``None``, an :class:`AcceptNoHeader` 

1666 instance. 

1667 

1668 | If `header_value` is a valid ``Accept`` header, an 

1669 :class:`AcceptValidHeader` instance. 

1670 

1671 | If `header_value` is an invalid ``Accept`` header, an 

1672 :class:`AcceptInvalidHeader` instance. 

1673 """ 

1674 if header_value is None: 

1675 return AcceptNoHeader() 

1676 if isinstance(header_value, Accept): 

1677 return header_value.copy() 

1678 try: 

1679 return AcceptValidHeader(header_value=header_value) 

1680 except ValueError: 

1681 return AcceptInvalidHeader(header_value=header_value) 

1682 

1683 

1684def accept_property(): 

1685 doc = """ 

1686 Property representing the ``Accept`` header. 

1687 

1688 (:rfc:`RFC 7231, section 5.3.2 <7231#section-5.3.2>`) 

1689 

1690 The header value in the request environ is parsed and a new object 

1691 representing the header is created every time we *get* the value of the 

1692 property. (*set* and *del* change the header value in the request 

1693 environ, and do not involve parsing.) 

1694 """ 

1695 

1696 ENVIRON_KEY = 'HTTP_ACCEPT' 

1697 

1698 def fget(request): 

1699 """Get an object representing the header in the request.""" 

1700 return create_accept_header( 

1701 header_value=request.environ.get(ENVIRON_KEY) 

1702 ) 

1703 

1704 def fset(request, value): 

1705 """ 

1706 Set the corresponding key in the request environ. 

1707 

1708 `value` can be: 

1709 

1710 * ``None`` 

1711 * a ``str`` header value 

1712 * a ``dict``, with media ranges ``str``'s (including any media type 

1713 parameters) as keys, and either qvalues ``float``'s or (*qvalues*, 

1714 *extension_params*) tuples as values, where *extension_params* is a 

1715 ``str`` of the extension parameters segment of the header element, 

1716 starting with the first '``;``' 

1717 * a ``tuple`` or ``list``, where each item is either a header element 

1718 ``str``, or a (*media_range*, *qvalue*, *extension_params*) ``tuple`` 

1719 or ``list`` where *media_range* is a ``str`` of the media range 

1720 including any media type parameters, and *extension_params* is a 

1721 ``str`` of the extension parameters segment of the header element, 

1722 starting with the first '``;``' 

1723 * an :class:`AcceptValidHeader`, :class:`AcceptNoHeader`, or 

1724 :class:`AcceptInvalidHeader` instance 

1725 * object of any other type that returns a value for ``__str__`` 

1726 """ 

1727 if value is None or isinstance(value, AcceptNoHeader): 

1728 fdel(request=request) 

1729 else: 

1730 if isinstance(value, (AcceptValidHeader, AcceptInvalidHeader)): 

1731 header_value = value.header_value 

1732 else: 

1733 header_value = Accept._python_value_to_header_str(value=value) 

1734 request.environ[ENVIRON_KEY] = header_value 

1735 

1736 def fdel(request): 

1737 """Delete the corresponding key from the request environ.""" 

1738 try: 

1739 del request.environ[ENVIRON_KEY] 

1740 except KeyError: 

1741 pass 

1742 

1743 return property(fget, fset, fdel, textwrap.dedent(doc)) 

1744 

1745 

1746class AcceptCharset(object): 

1747 """ 

1748 Represent an ``Accept-Charset`` header. 

1749 

1750 Base class for :class:`AcceptCharsetValidHeader`, 

1751 :class:`AcceptCharsetNoHeader`, and :class:`AcceptCharsetInvalidHeader`. 

1752 """ 

1753 

1754 # RFC 7231 Section 3.1.1.2 "Charset": 

1755 # charset = token 

1756 charset_re = token_re 

1757 # RFC 7231 Section 5.3.3 "Accept-Charset": 

1758 # Accept-Charset = 1#( ( charset / "*" ) [ weight ] ) 

1759 charset_n_weight_re = _item_n_weight_re(item_re=charset_re) 

1760 charset_n_weight_compiled_re = re.compile(charset_n_weight_re) 

1761 accept_charset_compiled_re = _list_1_or_more__compiled_re( 

1762 element_re=charset_n_weight_re, 

1763 ) 

1764 

1765 @classmethod 

1766 def _python_value_to_header_str(cls, value): 

1767 if isinstance(value, str): 

1768 header_str = value 

1769 else: 

1770 if hasattr(value, 'items'): 

1771 value = sorted( 

1772 value.items(), 

1773 key=lambda item: item[1], 

1774 reverse=True, 

1775 ) 

1776 if isinstance(value, (tuple, list)): 

1777 result = [] 

1778 for item in value: 

1779 if isinstance(item, (tuple, list)): 

1780 item = _item_qvalue_pair_to_header_element(pair=item) 

1781 result.append(item) 

1782 header_str = ', '.join(result) 

1783 else: 

1784 header_str = str(value) 

1785 return header_str 

1786 

1787 @classmethod 

1788 def parse(cls, value): 

1789 """ 

1790 Parse an ``Accept-Charset`` header. 

1791 

1792 :param value: (``str``) header value 

1793 :return: If `value` is a valid ``Accept-Charset`` header, returns an 

1794 iterator of (charset, quality value) tuples, as parsed from 

1795 the header from left to right. 

1796 :raises ValueError: if `value` is an invalid header 

1797 """ 

1798 # Check if header is valid 

1799 # Using Python stdlib's `re` module, there is currently no way to check 

1800 # the match *and* get all the groups using the same regex, so we have 

1801 # to use one regex to check the match, and another to get the groups. 

1802 if cls.accept_charset_compiled_re.match(value) is None: 

1803 raise ValueError('Invalid value for an Accept-Charset header.') 

1804 def generator(value): 

1805 for match in (cls.charset_n_weight_compiled_re.finditer(value)): 

1806 charset = match.group(1) 

1807 qvalue = match.group(2) 

1808 qvalue = float(qvalue) if qvalue else 1.0 

1809 yield (charset, qvalue) 

1810 return generator(value=value) 

1811 

1812 

1813class AcceptCharsetValidHeader(AcceptCharset): 

1814 """ 

1815 Represent a valid ``Accept-Charset`` header. 

1816 

1817 A valid header is one that conforms to :rfc:`RFC 7231, section 5.3.3 

1818 <7231#section-5.3.3>`. 

1819 

1820 This object should not be modified. To add to the header, we can use the 

1821 addition operators (``+`` and ``+=``), which return a new object (see the 

1822 docstring for :meth:`AcceptCharsetValidHeader.__add__`). 

1823 """ 

1824 

1825 @property 

1826 def header_value(self): 

1827 """(``str``) The header value.""" 

1828 return self._header_value 

1829 

1830 @property 

1831 def parsed(self): 

1832 """ 

1833 (``list``) Parsed form of the header. 

1834 

1835 A list of (charset, quality value) tuples. 

1836 """ 

1837 return self._parsed 

1838 

1839 def __init__(self, header_value): 

1840 """ 

1841 Create an :class:`AcceptCharsetValidHeader` instance. 

1842 

1843 :param header_value: (``str``) header value. 

1844 :raises ValueError: if `header_value` is an invalid value for an 

1845 ``Accept-Charset`` header. 

1846 """ 

1847 self._header_value = header_value 

1848 self._parsed = list(self.parse(header_value)) 

1849 self._parsed_nonzero = [ 

1850 item for item in self.parsed if item[1] # item[1] is the qvalue 

1851 ] 

1852 

1853 def copy(self): 

1854 """ 

1855 Create a copy of the header object. 

1856 

1857 """ 

1858 return self.__class__(self._header_value) 

1859 

1860 def __add__(self, other): 

1861 """ 

1862 Add to header, creating a new header object. 

1863 

1864 `other` can be: 

1865 

1866 * ``None`` 

1867 * a ``str`` header value 

1868 * a ``dict``, where keys are charsets and values are qvalues 

1869 * a ``tuple`` or ``list``, where each item is a charset ``str`` or a 

1870 ``tuple`` or ``list`` (charset, qvalue) pair (``str``'s and pairs 

1871 can be mixed within the ``tuple`` or ``list``) 

1872 * an :class:`AcceptCharsetValidHeader`, :class:`AcceptCharsetNoHeader`, 

1873 or :class:`AcceptCharsetInvalidHeader` instance 

1874 * object of any other type that returns a value for ``__str__`` 

1875 

1876 If `other` is a valid header value or another 

1877 :class:`AcceptCharsetValidHeader` instance, the two header values are 

1878 joined with ``', '``, and a new :class:`AcceptCharsetValidHeader` 

1879 instance with the new header value is returned. 

1880 

1881 If `other` is ``None``, an :class:`AcceptCharsetNoHeader` instance, an 

1882 invalid header value, or an :class:`AcceptCharsetInvalidHeader` 

1883 instance, a new :class:`AcceptCharsetValidHeader` instance with the 

1884 same header value as ``self`` is returned. 

1885 """ 

1886 if isinstance(other, AcceptCharsetValidHeader): 

1887 return create_accept_charset_header( 

1888 header_value=self.header_value + ', ' + other.header_value, 

1889 ) 

1890 

1891 if isinstance( 

1892 other, (AcceptCharsetNoHeader, AcceptCharsetInvalidHeader) 

1893 ): 

1894 return self.__class__(header_value=self.header_value) 

1895 

1896 return self._add_instance_and_non_accept_charset_type( 

1897 instance=self, other=other, 

1898 ) 

1899 

1900 def __bool__(self): 

1901 """ 

1902 Return whether ``self`` represents a valid ``Accept-Charset`` header. 

1903 

1904 Return ``True`` if ``self`` represents a valid header, and ``False`` if 

1905 it represents an invalid header, or the header not being in the 

1906 request. 

1907 

1908 For this class, it always returns ``True``. 

1909 """ 

1910 return True 

1911 __nonzero__ = __bool__ # Python 2 

1912 

1913 def __contains__(self, offer): 

1914 """ 

1915 Return ``bool`` indicating whether `offer` is acceptable. 

1916 

1917 .. warning:: 

1918 

1919 The behavior of :meth:`AcceptCharsetValidHeader.__contains__` is 

1920 currently being maintained for backward compatibility, but it will 

1921 change in the future to better conform to the RFC. 

1922 

1923 :param offer: (``str``) charset offer 

1924 :return: (``bool``) Whether ``offer`` is acceptable according to the 

1925 header. 

1926 

1927 This does not fully conform to :rfc:`RFC 7231, section 5.3.3 

1928 <7231#section-5.3.3>`: it incorrect interprets ``*`` to mean 'match any 

1929 charset in the header', rather than 'match any charset that is not 

1930 mentioned elsewhere in the header':: 

1931 

1932 >>> 'UTF-8' in AcceptCharsetValidHeader('UTF-8;q=0, *') 

1933 True 

1934 """ 

1935 warnings.warn( 

1936 'The behavior of AcceptCharsetValidHeader.__contains__ is ' 

1937 'currently being maintained for backward compatibility, but it ' 

1938 'will change in the future to better conform to the RFC.', 

1939 DeprecationWarning, 

1940 ) 

1941 for mask, quality in self._parsed_nonzero: 

1942 if self._old_match(mask, offer): 

1943 return True 

1944 return False 

1945 

1946 def __iter__(self): 

1947 """ 

1948 Return all the items with non-0 qvalues, in order of preference. 

1949 

1950 .. warning:: 

1951 

1952 The behavior of this method is currently maintained for backward 

1953 compatibility, but will change in the future. 

1954 

1955 :return: iterator of all the items (charset or ``*``) in the header 

1956 with non-0 qvalues, in descending order of qvalue. If two 

1957 items have the same qvalue, they are returned in the order of 

1958 their positions in the header, from left to right. 

1959 

1960 Please note that this is a simple filter for the items in the header 

1961 with non-0 qvalues, and is not necessarily the same as what the client 

1962 prefers, e.g. ``'utf-7;q=0, *'`` means 'everything but utf-7', but 

1963 ``list(instance)`` would return only ``['*']``. 

1964 """ 

1965 warnings.warn( 

1966 'The behavior of AcceptCharsetValidHeader.__iter__ is currently ' 

1967 'maintained for backward compatibility, but will change in the ' 

1968 'future.', 

1969 DeprecationWarning, 

1970 ) 

1971 for m,q in sorted( 

1972 self._parsed_nonzero, 

1973 key=lambda i: i[1], 

1974 reverse=True 

1975 ): 

1976 yield m 

1977 

1978 def __radd__(self, other): 

1979 """ 

1980 Add to header, creating a new header object. 

1981 

1982 See the docstring for :meth:`AcceptCharsetValidHeader.__add__`. 

1983 """ 

1984 return self._add_instance_and_non_accept_charset_type( 

1985 instance=self, other=other, instance_on_the_right=True, 

1986 ) 

1987 

1988 def __repr__(self): 

1989 return '<{} ({!r})>'.format(self.__class__.__name__, str(self)) 

1990 

1991 def __str__(self): 

1992 r""" 

1993 Return a tidied up version of the header value. 

1994 

1995 e.g. If the ``header_value`` is ``', \t,iso-8859-5;q=0.000 \t, 

1996 utf-8;q=1.000, UTF-7, unicode-1-1;q=0.210 ,'``, ``str(instance)`` 

1997 returns ``'iso-8859-5;q=0, utf-8, UTF-7, unicode-1-1;q=0.21'``. 

1998 """ 

1999 return ', '.join( 

2000 _item_qvalue_pair_to_header_element(pair=tuple_) 

2001 for tuple_ in self.parsed 

2002 ) 

2003 

2004 def _add_instance_and_non_accept_charset_type( 

2005 self, instance, other, instance_on_the_right=False, 

2006 ): 

2007 if not other: 

2008 return self.__class__(header_value=instance.header_value) 

2009 

2010 other_header_value = self._python_value_to_header_str(value=other) 

2011 

2012 try: 

2013 self.parse(value=other_header_value) 

2014 except ValueError: # invalid header value 

2015 return self.__class__(header_value=instance.header_value) 

2016 

2017 new_header_value = ( 

2018 (other_header_value + ', ' + instance.header_value) 

2019 if instance_on_the_right 

2020 else (instance.header_value + ', ' + other_header_value) 

2021 ) 

2022 return self.__class__(header_value=new_header_value) 

2023 

2024 def _old_match(self, mask, offer): 

2025 """ 

2026 Return whether charset offer matches header item (charset or ``*``). 

2027 

2028 .. warning:: 

2029 

2030 This is maintained for backward compatibility, and will be 

2031 deprecated in the future. 

2032 

2033 This method was WebOb's old criterion for deciding whether a charset 

2034 matches a header item (charset or ``*``), used in 

2035 

2036 - :meth:`AcceptCharsetValidHeader.__contains__` 

2037 - :meth:`AcceptCharsetValidHeader.best_match` 

2038 - :meth:`AcceptCharsetValidHeader.quality` 

2039 

2040 It does not conform to :rfc:`RFC 7231, section 5.3.3 

2041 <7231#section-5.3.3>` in that it does not interpret ``*`` values in the 

2042 header correctly: ``*`` should only match charsets not mentioned 

2043 elsewhere in the header. 

2044 """ 

2045 return mask == '*' or offer.lower() == mask.lower() 

2046 

2047 def acceptable_offers(self, offers): 

2048 """ 

2049 Return the offers that are acceptable according to the header. 

2050 

2051 The offers are returned in descending order of preference, where 

2052 preference is indicated by the qvalue of the charset or ``*`` in the 

2053 header matching the offer. 

2054 

2055 This uses the matching rules described in :rfc:`RFC 7231, section 5.3.3 

2056 <7231#section-5.3.3>`. 

2057 

2058 :param offers: ``iterable`` of ``str`` charsets 

2059 :return: A list of tuples of the form (charset, qvalue), in descending 

2060 order of qvalue. Where two offers have the same qvalue, they 

2061 are returned in the same order as their order in `offers`. 

2062 """ 

2063 lowercased_parsed = [ 

2064 (charset.lower(), qvalue) for (charset, qvalue) in self.parsed 

2065 ] 

2066 lowercased_offers = [offer.lower() for offer in offers] 

2067 

2068 not_acceptable_charsets = set() 

2069 acceptable_charsets = dict() 

2070 asterisk_qvalue = None 

2071 

2072 for charset, qvalue in lowercased_parsed: 

2073 if charset == '*': 

2074 if asterisk_qvalue is None: 

2075 asterisk_qvalue = qvalue 

2076 elif ( 

2077 charset not in acceptable_charsets and charset not in 

2078 not_acceptable_charsets 

2079 # if we have not already encountered this charset in the header 

2080 ): 

2081 if qvalue == 0.0: 

2082 not_acceptable_charsets.add(charset) 

2083 else: 

2084 acceptable_charsets[charset] = qvalue 

2085 acceptable_charsets = list(acceptable_charsets.items()) 

2086 # Sort acceptable_charsets by qvalue, descending order 

2087 acceptable_charsets.sort(key=lambda tuple_: tuple_[1], reverse=True) 

2088 

2089 filtered_offers = [] 

2090 for index, offer in enumerate(lowercased_offers): 

2091 # If offer matches a non-* charset with q=0, it is filtered out 

2092 if any(( 

2093 (offer == charset) for charset in not_acceptable_charsets 

2094 )): 

2095 continue 

2096 

2097 matched_charset_qvalue = None 

2098 for charset, qvalue in acceptable_charsets: 

2099 if offer == charset: 

2100 matched_charset_qvalue = qvalue 

2101 break 

2102 else: 

2103 if asterisk_qvalue: 

2104 matched_charset_qvalue = asterisk_qvalue 

2105 if matched_charset_qvalue is not None: # if there was a match 

2106 filtered_offers.append(( 

2107 offers[index], matched_charset_qvalue, index 

2108 )) 

2109 

2110 # sort by position in `offers` argument, ascending 

2111 filtered_offers.sort(key=lambda tuple_: tuple_[2]) 

2112 # When qvalues are tied, position in `offers` is the tiebreaker. 

2113 

2114 # sort by qvalue, descending 

2115 filtered_offers.sort(key=lambda tuple_: tuple_[1], reverse=True) 

2116 

2117 return [(item[0], item[1]) for item in filtered_offers] 

2118 # (offer, qvalue), dropping the position 

2119 

2120 def best_match(self, offers, default_match=None): 

2121 """ 

2122 Return the best match from the sequence of charset `offers`. 

2123 

2124 .. warning:: 

2125 

2126 This is currently maintained for backward compatibility, and will be 

2127 deprecated in the future. 

2128 

2129 :meth:`AcceptCharsetValidHeader.best_match` has many issues, and 

2130 does not conform to :rfc:`RFC 7231 <7231>`. 

2131 

2132 Each charset in `offers` is checked against each non-``q=0`` item 

2133 (charset or ``*``) in the header. If the two are a match according to 

2134 WebOb's old criterion for a match, the quality value of the match is 

2135 the qvalue of the item from the header multiplied by the server quality 

2136 value of the offer (if the server quality value is not supplied, it is 

2137 1). 

2138 

2139 The offer in the match with the highest quality value is the best 

2140 match. If there is more than one match with the highest qvalue, the one 

2141 that shows up first in `offers` is the best match. 

2142 

2143 :param offers: (iterable) 

2144 

2145 | Each item in the iterable may be a ``str`` charset, or 

2146 a (charset, server quality value) ``tuple`` or 

2147 ``list``. (The two may be mixed in the iterable.) 

2148 

2149 :param default_match: (optional, any type) the value to be returned if 

2150 there is no match 

2151 

2152 :return: (``str``, or the type of `default_match`) 

2153 

2154 | The offer that is the best match. If there is no match, the 

2155 value of `default_match` is returned. 

2156 

2157 The algorithm behind this method was written for the ``Accept`` header 

2158 rather than the ``Accept-Charset`` header. It uses the old criterion of 

2159 a match in :meth:`AcceptCharsetValidHeader._old_match`, which does not 

2160 conform to :rfc:`RFC 7231, section 5.3.3 <7231#section-5.3.3>`, in that 

2161 it does not interpret ``*`` values in the header correctly: ``*`` 

2162 should only match charsets not mentioned elsewhere in the header:: 

2163 

2164 >>> AcceptCharsetValidHeader('utf-8;q=0, *').best_match(['utf-8']) 

2165 'utf-8' 

2166 """ 

2167 warnings.warn( 

2168 'The behavior of AcceptCharsetValidHeader.best_match is currently' 

2169 ' being maintained for backward compatibility, but it will be ' 

2170 'deprecated in the future, as it does not conform to the RFC.', 

2171 DeprecationWarning, 

2172 ) 

2173 best_quality = -1 

2174 best_offer = default_match 

2175 matched_by = '*/*' 

2176 for offer in offers: 

2177 if isinstance(offer, (tuple, list)): 

2178 offer, server_quality = offer 

2179 else: 

2180 server_quality = 1 

2181 for mask, quality in self._parsed_nonzero: 

2182 possible_quality = server_quality * quality 

2183 if possible_quality < best_quality: 

2184 continue 

2185 elif possible_quality == best_quality: 

2186 # 'text/plain' overrides 'message/*' overrides '*/*' 

2187 # (if all match w/ the same q=) 

2188 # [We can see that this was written for the Accept header, 

2189 # not the Accept-Charset header.] 

2190 if matched_by.count('*') <= mask.count('*'): 

2191 continue 

2192 if self._old_match(mask, offer): 

2193 best_quality = possible_quality 

2194 best_offer = offer 

2195 matched_by = mask 

2196 return best_offer 

2197 

2198 def quality(self, offer): 

2199 """ 

2200 Return quality value of given offer, or ``None`` if there is no match. 

2201 

2202 .. warning:: 

2203 

2204 This is currently maintained for backward compatibility, and will be 

2205 deprecated in the future. 

2206 

2207 :param offer: (``str``) charset offer 

2208 :return: (``float`` or ``None``) 

2209 

2210 | The quality value from the charset that matches the `offer`, 

2211 or ``None`` if there is no match. 

2212 

2213 This uses the old criterion of a match in 

2214 :meth:`AcceptCharsetValidHeader._old_match`, which does not conform to 

2215 :rfc:`RFC 7231, section 5.3.3 <7231#section-5.3.3>`, in that it does 

2216 not interpret ``*`` values in the header correctly: ``*`` should only 

2217 match charsets not mentioned elsewhere in the header:: 

2218 

2219 >>> AcceptCharsetValidHeader('utf-8;q=0, *').quality('utf-8') 

2220 1.0 

2221 >>> AcceptCharsetValidHeader('utf-8;q=0.9, *').quality('utf-8') 

2222 1.0 

2223 """ 

2224 warnings.warn( 

2225 'The behavior of AcceptCharsetValidHeader.quality is currently ' 

2226 'being maintained for backward compatibility, but it will be ' 

2227 'deprecated in the future, as it does not conform to the RFC.', 

2228 DeprecationWarning, 

2229 ) 

2230 bestq = 0 

2231 for mask, q in self.parsed: 

2232 if self._old_match(mask, offer): 

2233 bestq = max(bestq, q) 

2234 return bestq or None 

2235 

2236 

2237class _AcceptCharsetInvalidOrNoHeader(AcceptCharset): 

2238 """ 

2239 Represent when an ``Accept-Charset`` header is invalid or not in request. 

2240 

2241 This is the base class for the behaviour that 

2242 :class:`.AcceptCharsetInvalidHeader` and :class:`.AcceptCharsetNoHeader` 

2243 have in common. 

2244 

2245 :rfc:`7231` does not provide any guidance on what should happen if the 

2246 ``Accept-Charset`` header has an invalid value. This implementation 

2247 disregards the header when the header is invalid, so 

2248 :class:`.AcceptCharsetInvalidHeader` and :class:`.AcceptCharsetNoHeader` 

2249 have much behaviour in common. 

2250 """ 

2251 

2252 def __bool__(self): 

2253 """ 

2254 Return whether ``self`` represents a valid ``Accept-Charset`` header. 

2255 

2256 Return ``True`` if ``self`` represents a valid header, and ``False`` if 

2257 it represents an invalid header, or the header not being in the 

2258 request. 

2259 

2260 For this class, it always returns ``False``. 

2261 """ 

2262 return False 

2263 __nonzero__ = __bool__ # Python 2 

2264 

2265 def __contains__(self, offer): 

2266 """ 

2267 Return ``bool`` indicating whether `offer` is acceptable. 

2268 

2269 .. warning:: 

2270 

2271 The behavior of ``.__contains__`` for the ``AcceptCharset`` classes 

2272 is currently being maintained for backward compatibility, but it 

2273 will change in the future to better conform to the RFC. 

2274 

2275 :param offer: (``str``) charset offer 

2276 :return: (``bool``) Whether ``offer`` is acceptable according to the 

2277 header. 

2278 

2279 For this class, either there is no ``Accept-Charset`` header in the 

2280 request, or the header is invalid, so any charset is acceptable, and 

2281 this always returns ``True``. 

2282 """ 

2283 warnings.warn( 

2284 'The behavior of .__contains__ for the AcceptCharset classes is ' 

2285 'currently being maintained for backward compatibility, but it ' 

2286 'will change in the future to better conform to the RFC.', 

2287 DeprecationWarning, 

2288 ) 

2289 return True 

2290 

2291 def __iter__(self): 

2292 """ 

2293 Return all the items with non-0 qvalues, in order of preference. 

2294 

2295 .. warning:: 

2296 

2297 The behavior of this method is currently maintained for backward 

2298 compatibility, but will change in the future. 

2299 

2300 :return: iterator of all the items (charset or ``*``) in the header 

2301 with non-0 qvalues, in descending order of qvalue. If two 

2302 items have the same qvalue, they are returned in the order of 

2303 their positions in the header, from left to right. 

2304 

2305 When there is no ``Accept-Charset`` header in the request or the header 

2306 is invalid, there are no items, and this always returns an empty 

2307 iterator. 

2308 """ 

2309 warnings.warn( 

2310 'The behavior of AcceptCharsetValidHeader.__iter__ is currently ' 

2311 'maintained for backward compatibility, but will change in the ' 

2312 'future.', 

2313 DeprecationWarning, 

2314 ) 

2315 return iter(()) 

2316 

2317 def acceptable_offers(self, offers): 

2318 """ 

2319 Return the offers that are acceptable according to the header. 

2320 

2321 The offers are returned in descending order of preference, where 

2322 preference is indicated by the qvalue of the charset or ``*`` in the 

2323 header matching the offer. 

2324 

2325 This uses the matching rules described in :rfc:`RFC 7231, section 5.3.3 

2326 <7231#section-5.3.3>`. 

2327 

2328 :param offers: ``iterable`` of ``str`` charsets 

2329 :return: A list of tuples of the form (charset, qvalue), in descending 

2330 order of qvalue. Where two offers have the same qvalue, they 

2331 are returned in the same order as their order in `offers`. 

2332 

2333 | When the header is invalid or there is no ``Accept-Charset`` 

2334 header in the request, all `offers` are considered 

2335 acceptable, so this method returns a list of (charset, 

2336 qvalue) tuples where each offer in `offers` is paired with 

2337 the qvalue of 1.0, in the same order as `offers`. 

2338 """ 

2339 return [(offer, 1.0) for offer in offers] 

2340 

2341 def best_match(self, offers, default_match=None): 

2342 """ 

2343 Return the best match from the sequence of charset `offers`. 

2344 

2345 This is the ``.best_match()`` method for when the header is invalid or 

2346 not found in the request, corresponding to 

2347 :meth:`AcceptCharsetValidHeader.best_match`. 

2348 

2349 .. warning:: 

2350 

2351 This is currently maintained for backward compatibility, and will be 

2352 deprecated in the future (see the documentation for 

2353 :meth:`AcceptCharsetValidHeader.best_match`). 

2354 

2355 When the header is invalid, or there is no `Accept-Charset` header in 

2356 the request, all the charsets in `offers` are considered acceptable, so 

2357 the best match is the charset in `offers` with the highest server 

2358 quality value (if the server quality value is not supplied, it is 1). 

2359 

2360 If more than one charsets in `offers` have the same highest server 

2361 quality value, then the one that shows up first in `offers` is the best 

2362 match. 

2363 

2364 :param offers: (iterable) 

2365 

2366 | Each item in the iterable may be a ``str`` charset, or 

2367 a (charset, server quality value) ``tuple`` or 

2368 ``list``. (The two may be mixed in the iterable.) 

2369 

2370 :param default_match: (optional, any type) the value to be returned if 

2371 `offers` is empty. 

2372 

2373 :return: (``str``, or the type of `default_match`) 

2374 

2375 | The charset that has the highest server quality value. If 

2376 `offers` is empty, the value of `default_match` is returned. 

2377 """ 

2378 warnings.warn( 

2379 'The behavior of .best_match for the AcceptCharset classes is ' 

2380 'currently being maintained for backward compatibility, but the ' 

2381 'method will be deprecated in the future, as its behavior is not ' 

2382 'specified in (and currently does not conform to) RFC 7231.', 

2383 DeprecationWarning, 

2384 ) 

2385 best_quality = -1 

2386 best_offer = default_match 

2387 for offer in offers: 

2388 if isinstance(offer, (list, tuple)): 

2389 offer, quality = offer 

2390 else: 

2391 quality = 1 

2392 if quality > best_quality: 

2393 best_offer = offer 

2394 best_quality = quality 

2395 return best_offer 

2396 

2397 def quality(self, offer): 

2398 """ 

2399 Return quality value of given offer, or ``None`` if there is no match. 

2400 

2401 This is the ``.quality()`` method for when the header is invalid or not 

2402 found in the request, corresponding to 

2403 :meth:`AcceptCharsetValidHeader.quality`. 

2404 

2405 .. warning:: 

2406 

2407 This is currently maintained for backward compatibility, and will be 

2408 deprecated in the future (see the documentation for 

2409 :meth:`AcceptCharsetValidHeader.quality`). 

2410 

2411 :param offer: (``str``) charset offer 

2412 :return: (``float``) ``1.0``. 

2413 

2414 When the ``Accept-Charset`` header is invalid or not in the request, 

2415 all offers are equally acceptable, so 1.0 is always returned. 

2416 """ 

2417 warnings.warn( 

2418 'The behavior of .quality for the Accept-Charset classes is ' 

2419 'currently being maintained for backward compatibility, but the ' 

2420 'method will be deprecated in the future, as its behavior does not' 

2421 ' conform to RFC 7231.', 

2422 DeprecationWarning, 

2423 ) 

2424 return 1.0 

2425 

2426 

2427class AcceptCharsetNoHeader(_AcceptCharsetInvalidOrNoHeader): 

2428 """ 

2429 Represent when there is no ``Accept-Charset`` header in the request. 

2430 

2431 This object should not be modified. To add to the header, we can use the 

2432 addition operators (``+`` and ``+=``), which return a new object (see the 

2433 docstring for :meth:`AcceptCharsetNoHeader.__add__`). 

2434 """ 

2435 

2436 @property 

2437 def header_value(self): 

2438 """ 

2439 (``str`` or ``None``) The header value. 

2440 

2441 As there is no header in the request, this is ``None``. 

2442 """ 

2443 return self._header_value 

2444 

2445 @property 

2446 def parsed(self): 

2447 """ 

2448 (``list`` or ``None``) Parsed form of the header. 

2449 

2450 As there is no header in the request, this is ``None``. 

2451 """ 

2452 return self._parsed 

2453 

2454 def __init__(self): 

2455 """ 

2456 Create an :class:`AcceptCharsetNoHeader` instance. 

2457 """ 

2458 self._header_value = None 

2459 self._parsed = None 

2460 self._parsed_nonzero = None 

2461 

2462 def copy(self): 

2463 """ 

2464 Create a copy of the header object. 

2465 

2466 """ 

2467 return self.__class__() 

2468 

2469 def __add__(self, other): 

2470 """ 

2471 Add to header, creating a new header object. 

2472 

2473 `other` can be: 

2474 

2475 * ``None`` 

2476 * a ``str`` header value 

2477 * a ``dict``, where keys are charsets and values are qvalues 

2478 * a ``tuple`` or ``list``, where each item is a charset ``str`` or a 

2479 ``tuple`` or ``list`` (charset, qvalue) pair (``str``'s and pairs 

2480 can be mixed within the ``tuple`` or ``list``) 

2481 * an :class:`AcceptCharsetValidHeader`, :class:`AcceptCharsetNoHeader`, 

2482 or :class:`AcceptCharsetInvalidHeader` instance 

2483 * object of any other type that returns a value for ``__str__`` 

2484 

2485 If `other` is a valid header value or an 

2486 :class:`AcceptCharsetValidHeader` instance, a new 

2487 :class:`AcceptCharsetValidHeader` instance with the valid header value 

2488 is returned. 

2489 

2490 If `other` is ``None``, an :class:`AcceptCharsetNoHeader` instance, an 

2491 invalid header value, or an :class:`AcceptCharsetInvalidHeader` 

2492 instance, a new :class:`AcceptCharsetNoHeader` instance is returned. 

2493 """ 

2494 if isinstance(other, AcceptCharsetValidHeader): 

2495 return AcceptCharsetValidHeader(header_value=other.header_value) 

2496 

2497 if isinstance( 

2498 other, (AcceptCharsetNoHeader, AcceptCharsetInvalidHeader) 

2499 ): 

2500 return self.__class__() 

2501 

2502 return self._add_instance_and_non_accept_charset_type( 

2503 instance=self, other=other, 

2504 ) 

2505 

2506 def __radd__(self, other): 

2507 """ 

2508 Add to header, creating a new header object. 

2509 

2510 See the docstring for :meth:`AcceptCharsetNoHeader.__add__`. 

2511 """ 

2512 return self.__add__(other=other) 

2513 

2514 def __repr__(self): 

2515 return '<{}>'.format(self.__class__.__name__) 

2516 

2517 def __str__(self): 

2518 """Return the ``str`` ``'<no header in request>'``.""" 

2519 return '<no header in request>' 

2520 

2521 def _add_instance_and_non_accept_charset_type(self, instance, other): 

2522 if not other: 

2523 return self.__class__() 

2524 

2525 other_header_value = self._python_value_to_header_str(value=other) 

2526 

2527 try: 

2528 return AcceptCharsetValidHeader(header_value=other_header_value) 

2529 except ValueError: # invalid header value 

2530 return self.__class__() 

2531 

2532 

2533class AcceptCharsetInvalidHeader(_AcceptCharsetInvalidOrNoHeader): 

2534 """ 

2535 Represent an invalid ``Accept-Charset`` header. 

2536 

2537 An invalid header is one that does not conform to 

2538 :rfc:`7231#section-5.3.3`. As specified in the RFC, an empty header is an 

2539 invalid ``Accept-Charset`` header. 

2540 

2541 :rfc:`7231` does not provide any guidance on what should happen if the 

2542 ``Accept-Charset`` header has an invalid value. This implementation 

2543 disregards the header, and treats it as if there is no ``Accept-Charset`` 

2544 header in the request. 

2545 

2546 This object should not be modified. To add to the header, we can use the 

2547 addition operators (``+`` and ``+=``), which return a new object (see the 

2548 docstring for :meth:`AcceptCharsetInvalidHeader.__add__`). 

2549 """ 

2550 

2551 @property 

2552 def header_value(self): 

2553 """(``str`` or ``None``) The header value.""" 

2554 return self._header_value 

2555 

2556 @property 

2557 def parsed(self): 

2558 """ 

2559 (``list`` or ``None``) Parsed form of the header. 

2560 

2561 As the header is invalid and cannot be parsed, this is ``None``. 

2562 """ 

2563 return self._parsed 

2564 

2565 def __init__(self, header_value): 

2566 """ 

2567 Create an :class:`AcceptCharsetInvalidHeader` instance. 

2568 """ 

2569 self._header_value = header_value 

2570 self._parsed = None 

2571 self._parsed_nonzero = None 

2572 

2573 def copy(self): 

2574 """ 

2575 Create a copy of the header object. 

2576 

2577 """ 

2578 return self.__class__(self._header_value) 

2579 

2580 def __add__(self, other): 

2581 """ 

2582 Add to header, creating a new header object. 

2583 

2584 `other` can be: 

2585 

2586 * ``None`` 

2587 * a ``str`` header value 

2588 * a ``dict``, where keys are charsets and values are qvalues 

2589 * a ``tuple`` or ``list``, where each item is a charset ``str`` or a 

2590 ``tuple`` or ``list`` (charset, qvalue) pair (``str``'s and pairs 

2591 can be mixed within the ``tuple`` or ``list``) 

2592 * an :class:`AcceptCharsetValidHeader`, :class:`AcceptCharsetNoHeader`, 

2593 or :class:`AcceptCharsetInvalidHeader` instance 

2594 * object of any other type that returns a value for ``__str__`` 

2595 

2596 If `other` is a valid header value or an 

2597 :class:`AcceptCharsetValidHeader` instance, a new 

2598 :class:`AcceptCharsetValidHeader` instance with the valid header value 

2599 is returned. 

2600 

2601 If `other` is ``None``, an :class:`AcceptCharsetNoHeader` instance, an 

2602 invalid header value, or an :class:`AcceptCharsetInvalidHeader` 

2603 instance, a new :class:`AcceptCharsetNoHeader` instance is returned. 

2604 """ 

2605 if isinstance(other, AcceptCharsetValidHeader): 

2606 return AcceptCharsetValidHeader(header_value=other.header_value) 

2607 

2608 if isinstance( 

2609 other, (AcceptCharsetNoHeader, AcceptCharsetInvalidHeader) 

2610 ): 

2611 return AcceptCharsetNoHeader() 

2612 

2613 return self._add_instance_and_non_accept_charset_type( 

2614 instance=self, other=other, 

2615 ) 

2616 

2617 def __radd__(self, other): 

2618 """ 

2619 Add to header, creating a new header object. 

2620 

2621 See the docstring for :meth:`AcceptCharsetValidHeader.__add__`. 

2622 """ 

2623 return self._add_instance_and_non_accept_charset_type( 

2624 instance=self, other=other, instance_on_the_right=True, 

2625 ) 

2626 

2627 def __repr__(self): 

2628 return '<{}>'.format(self.__class__.__name__) 

2629 # We do not display the header_value, as it is untrusted input. The 

2630 # header_value could always be easily obtained from the .header_value 

2631 # property. 

2632 

2633 def __str__(self): 

2634 """Return the ``str`` ``'<invalid header value>'``.""" 

2635 return '<invalid header value>' 

2636 

2637 def _add_instance_and_non_accept_charset_type( 

2638 self, instance, other, instance_on_the_right=False, 

2639 ): 

2640 if not other: 

2641 return AcceptCharsetNoHeader() 

2642 

2643 other_header_value = self._python_value_to_header_str(value=other) 

2644 

2645 try: 

2646 return AcceptCharsetValidHeader(header_value=other_header_value) 

2647 except ValueError: # invalid header value 

2648 return AcceptCharsetNoHeader() 

2649 

2650 

2651def create_accept_charset_header(header_value): 

2652 """ 

2653 Create an object representing the ``Accept-Charset`` header in a request. 

2654 

2655 :param header_value: (``str``) header value 

2656 :return: If `header_value` is ``None``, an :class:`AcceptCharsetNoHeader` 

2657 instance. 

2658 

2659 | If `header_value` is a valid ``Accept-Charset`` header, an 

2660 :class:`AcceptCharsetValidHeader` instance. 

2661 

2662 | If `header_value` is an invalid ``Accept-Charset`` header, an 

2663 :class:`AcceptCharsetInvalidHeader` instance. 

2664 """ 

2665 if header_value is None: 

2666 return AcceptCharsetNoHeader() 

2667 if isinstance(header_value, AcceptCharset): 

2668 return header_value.copy() 

2669 try: 

2670 return AcceptCharsetValidHeader(header_value=header_value) 

2671 except ValueError: 

2672 return AcceptCharsetInvalidHeader(header_value=header_value) 

2673 

2674 

2675def accept_charset_property(): 

2676 doc = """ 

2677 Property representing the ``Accept-Charset`` header. 

2678 

2679 (:rfc:`RFC 7231, section 5.3.3 <7231#section-5.3.3>`) 

2680 

2681 The header value in the request environ is parsed and a new object 

2682 representing the header is created every time we *get* the value of the 

2683 property. (*set* and *del* change the header value in the request 

2684 environ, and do not involve parsing.) 

2685 """ 

2686 

2687 ENVIRON_KEY = 'HTTP_ACCEPT_CHARSET' 

2688 

2689 def fget(request): 

2690 """Get an object representing the header in the request.""" 

2691 return create_accept_charset_header( 

2692 header_value=request.environ.get(ENVIRON_KEY) 

2693 ) 

2694 

2695 def fset(request, value): 

2696 """ 

2697 Set the corresponding key in the request environ. 

2698 

2699 `value` can be: 

2700 

2701 * ``None`` 

2702 * a ``str`` header value 

2703 * a ``dict``, where keys are charsets and values are qvalues 

2704 * a ``tuple`` or ``list``, where each item is a charset ``str`` or a 

2705 ``tuple`` or ``list`` (charset, qvalue) pair (``str``'s and pairs 

2706 can be mixed within the ``tuple`` or ``list``) 

2707 * an :class:`AcceptCharsetValidHeader`, :class:`AcceptCharsetNoHeader`, 

2708 or :class:`AcceptCharsetInvalidHeader` instance 

2709 * object of any other type that returns a value for ``__str__`` 

2710 """ 

2711 if value is None or isinstance(value, AcceptCharsetNoHeader): 

2712 fdel(request=request) 

2713 else: 

2714 if isinstance( 

2715 value, (AcceptCharsetValidHeader, AcceptCharsetInvalidHeader) 

2716 ): 

2717 header_value = value.header_value 

2718 else: 

2719 header_value = AcceptCharset._python_value_to_header_str( 

2720 value=value, 

2721 ) 

2722 request.environ[ENVIRON_KEY] = header_value 

2723 

2724 def fdel(request): 

2725 """Delete the corresponding key from the request environ.""" 

2726 try: 

2727 del request.environ[ENVIRON_KEY] 

2728 except KeyError: 

2729 pass 

2730 

2731 return property(fget, fset, fdel, textwrap.dedent(doc)) 

2732 

2733 

2734class AcceptEncoding(object): 

2735 """ 

2736 Represent an ``Accept-Encoding`` header. 

2737 

2738 Base class for :class:`AcceptEncodingValidHeader`, 

2739 :class:`AcceptEncodingNoHeader`, and :class:`AcceptEncodingInvalidHeader`. 

2740 """ 

2741 

2742 # RFC 7231 Section 3.1.2.1 "Content Codings": 

2743 # content-coding = token 

2744 # Section 5.3.4 "Accept-Encoding": 

2745 # Accept-Encoding = #( codings [ weight ] ) 

2746 # codings = content-coding / "identity" / "*" 

2747 codings_re = token_re 

2748 # "identity" (case-insensitive) and "*" are both already included in token 

2749 # rule 

2750 codings_n_weight_re = _item_n_weight_re(item_re=codings_re) 

2751 codings_n_weight_compiled_re = re.compile(codings_n_weight_re) 

2752 accept_encoding_compiled_re = _list_0_or_more__compiled_re( 

2753 element_re=codings_n_weight_re, 

2754 ) 

2755 

2756 @classmethod 

2757 def _python_value_to_header_str(cls, value): 

2758 if isinstance(value, str): 

2759 header_str = value 

2760 else: 

2761 if hasattr(value, 'items'): 

2762 value = sorted( 

2763 value.items(), 

2764 key=lambda item: item[1], 

2765 reverse=True, 

2766 ) 

2767 if isinstance(value, (tuple, list)): 

2768 result = [] 

2769 for item in value: 

2770 if isinstance(item, (tuple, list)): 

2771 item = _item_qvalue_pair_to_header_element(pair=item) 

2772 result.append(item) 

2773 header_str = ', '.join(result) 

2774 else: 

2775 header_str = str(value) 

2776 return header_str 

2777 

2778 @classmethod 

2779 def parse(cls, value): 

2780 """ 

2781 Parse an ``Accept-Encoding`` header. 

2782 

2783 :param value: (``str``) header value 

2784 :return: If `value` is a valid ``Accept-Encoding`` header, returns an 

2785 iterator of (codings, quality value) tuples, as parsed from 

2786 the header from left to right. 

2787 :raises ValueError: if `value` is an invalid header 

2788 """ 

2789 # Check if header is valid 

2790 # Using Python stdlib's `re` module, there is currently no way to check 

2791 # the match *and* get all the groups using the same regex, so we have 

2792 # to use one regex to check the match, and another to get the groups. 

2793 if cls.accept_encoding_compiled_re.match(value) is None: 

2794 raise ValueError('Invalid value for an Accept-Encoding header.') 

2795 def generator(value): 

2796 for match in (cls.codings_n_weight_compiled_re.finditer(value)): 

2797 codings = match.group(1) 

2798 qvalue = match.group(2) 

2799 qvalue = float(qvalue) if qvalue else 1.0 

2800 yield (codings, qvalue) 

2801 return generator(value=value) 

2802 

2803 

2804class AcceptEncodingValidHeader(AcceptEncoding): 

2805 """ 

2806 Represent a valid ``Accept-Encoding`` header. 

2807 

2808 A valid header is one that conforms to :rfc:`RFC 7231, section 5.3.4 

2809 <7231#section-5.3.4>`. 

2810 

2811 This object should not be modified. To add to the header, we can use the 

2812 addition operators (``+`` and ``+=``), which return a new object (see the 

2813 docstring for :meth:`AcceptEncodingValidHeader.__add__`). 

2814 """ 

2815 

2816 @property 

2817 def header_value(self): 

2818 """(``str`` or ``None``) The header value.""" 

2819 return self._header_value 

2820 

2821 @property 

2822 def parsed(self): 

2823 """ 

2824 (``list`` or ``None``) Parsed form of the header. 

2825 

2826 A list of (*codings*, *qvalue*) tuples, where 

2827 

2828 *codings* (``str``) is a content-coding, the string "``identity``", or 

2829 "``*``"; and 

2830 

2831 *qvalue* (``float``) is the quality value of the codings. 

2832 """ 

2833 return self._parsed 

2834 

2835 def __init__(self, header_value): 

2836 """ 

2837 Create an :class:`AcceptEncodingValidHeader` instance. 

2838 

2839 :param header_value: (``str``) header value. 

2840 :raises ValueError: if `header_value` is an invalid value for an 

2841 ``Accept-Encoding`` header. 

2842 """ 

2843 self._header_value = header_value 

2844 self._parsed = list(self.parse(header_value)) 

2845 self._parsed_nonzero = [item for item in self.parsed if item[1]] 

2846 # item[1] is the qvalue 

2847 

2848 def copy(self): 

2849 """ 

2850 Create a copy of the header object. 

2851 

2852 """ 

2853 return self.__class__(self._header_value) 

2854 

2855 def __add__(self, other): 

2856 """ 

2857 Add to header, creating a new header object. 

2858 

2859 `other` can be: 

2860 

2861 * ``None`` 

2862 * a ``str`` header value 

2863 * a ``dict``, with content-coding, ``identity`` or ``*`` ``str``'s as 

2864 keys, and qvalue ``float``'s as values 

2865 * a ``tuple`` or ``list``, where each item is either a header element 

2866 ``str``, or a (content-coding/``identity``/``*``, qvalue) ``tuple`` 

2867 or ``list`` 

2868 * an :class:`AcceptEncodingValidHeader`, 

2869 :class:`AcceptEncodingNoHeader`, or 

2870 :class:`AcceptEncodingInvalidHeader` instance 

2871 * object of any other type that returns a value for ``__str__`` 

2872 

2873 If `other` is a valid header value or another 

2874 :class:`AcceptEncodingValidHeader` instance, and the header value it 

2875 represents is not ``''``, then the two header values are joined with 

2876 ``', '``, and a new :class:`AcceptEncodingValidHeader` instance with 

2877 the new header value is returned. 

2878 

2879 If `other` is a valid header value or another 

2880 :class:`AcceptEncodingValidHeader` instance representing a header value 

2881 of ``''``; or if it is ``None`` or an :class:`AcceptEncodingNoHeader` 

2882 instance; or if it is an invalid header value, or an 

2883 :class:`AcceptEncodingInvalidHeader` instance, then a new 

2884 :class:`AcceptEncodingValidHeader` instance with the same header value 

2885 as ``self`` is returned. 

2886 """ 

2887 if isinstance(other, AcceptEncodingValidHeader): 

2888 if other.header_value == '': 

2889 return self.__class__(header_value=self.header_value) 

2890 else: 

2891 return create_accept_encoding_header( 

2892 header_value=self.header_value + ', ' + other.header_value, 

2893 ) 

2894 

2895 if isinstance( 

2896 other, (AcceptEncodingNoHeader, AcceptEncodingInvalidHeader) 

2897 ): 

2898 return self.__class__(header_value=self.header_value) 

2899 

2900 return self._add_instance_and_non_accept_encoding_type( 

2901 instance=self, other=other, 

2902 ) 

2903 

2904 def __bool__(self): 

2905 """ 

2906 Return whether ``self`` represents a valid ``Accept-Encoding`` header. 

2907 

2908 Return ``True`` if ``self`` represents a valid header, and ``False`` if 

2909 it represents an invalid header, or the header not being in the 

2910 request. 

2911 

2912 For this class, it always returns ``True``. 

2913 """ 

2914 return True 

2915 __nonzero__ = __bool__ # Python 2 

2916 

2917 def __contains__(self, offer): 

2918 """ 

2919 Return ``bool`` indicating whether `offer` is acceptable. 

2920 

2921 .. warning:: 

2922 

2923 The behavior of :meth:`AcceptEncodingValidHeader.__contains__` is 

2924 currently being maintained for backward compatibility, but it will 

2925 change in the future to better conform to the RFC. 

2926 

2927 :param offer: (``str``) a content-coding or ``identity`` offer 

2928 :return: (``bool``) Whether ``offer`` is acceptable according to the 

2929 header. 

2930 

2931 The behavior of this method does not fully conform to :rfc:`7231`. 

2932 It does not correctly interpret ``*``:: 

2933 

2934 >>> 'gzip' in AcceptEncodingValidHeader('gzip;q=0, *') 

2935 True 

2936 

2937 and does not handle the ``identity`` token correctly:: 

2938 

2939 >>> 'identity' in AcceptEncodingValidHeader('gzip') 

2940 False 

2941 """ 

2942 warnings.warn( 

2943 'The behavior of AcceptEncodingValidHeader.__contains__ is ' 

2944 'currently being maintained for backward compatibility, but it ' 

2945 'will change in the future to better conform to the RFC.', 

2946 DeprecationWarning, 

2947 ) 

2948 for mask, quality in self._parsed_nonzero: 

2949 if self._old_match(mask, offer): 

2950 return True 

2951 

2952 def __iter__(self): 

2953 """ 

2954 Return all the ranges with non-0 qvalues, in order of preference. 

2955 

2956 .. warning:: 

2957 

2958 The behavior of this method is currently maintained for backward 

2959 compatibility, but will change in the future. 

2960 

2961 :return: iterator of all the (content-coding/``identity``/``*``) items 

2962 in the header with non-0 qvalues, in descending order of 

2963 qvalue. If two items have the same qvalue, they are returned 

2964 in the order of their positions in the header, from left to 

2965 right. 

2966 

2967 Please note that this is a simple filter for the items in the header 

2968 with non-0 qvalues, and is not necessarily the same as what the client 

2969 prefers, e.g. ``'gzip;q=0, *'`` means 'everything but gzip', but 

2970 ``list(instance)`` would return only ``['*']``. 

2971 """ 

2972 warnings.warn( 

2973 'The behavior of AcceptEncodingLanguageValidHeader.__iter__ is ' 

2974 'currently maintained for backward compatibility, but will change' 

2975 ' in the future.', 

2976 DeprecationWarning, 

2977 ) 

2978 

2979 for m,q in sorted( 

2980 self._parsed_nonzero, 

2981 key=lambda i: i[1], 

2982 reverse=True 

2983 ): 

2984 yield m 

2985 

2986 def __radd__(self, other): 

2987 """ 

2988 Add to header, creating a new header object. 

2989 

2990 See the docstring for :meth:`AcceptEncodingValidHeader.__add__`. 

2991 """ 

2992 return self._add_instance_and_non_accept_encoding_type( 

2993 instance=self, other=other, instance_on_the_right=True, 

2994 ) 

2995 

2996 def __repr__(self): 

2997 return '<{} ({!r})>'.format(self.__class__.__name__, str(self)) 

2998 

2999 def __str__(self): 

3000 r""" 

3001 Return a tidied up version of the header value. 

3002 

3003 e.g. If the ``header_value`` is ``",\t, a ;\t q=0.20 , b ,',"``, 

3004 ``str(instance)`` returns ``"a;q=0.2, b, '"``. 

3005 """ 

3006 return ', '.join( 

3007 _item_qvalue_pair_to_header_element(pair=tuple_) 

3008 for tuple_ in self.parsed 

3009 ) 

3010 

3011 def _add_instance_and_non_accept_encoding_type( 

3012 self, instance, other, instance_on_the_right=False, 

3013 ): 

3014 if not other: 

3015 return self.__class__(header_value=instance.header_value) 

3016 

3017 other_header_value = self._python_value_to_header_str(value=other) 

3018 

3019 if other_header_value == '': 

3020 # if ``other`` is an object whose type we don't recognise, and 

3021 # str(other) returns '' 

3022 return self.__class__(header_value=instance.header_value) 

3023 

3024 try: 

3025 self.parse(value=other_header_value) 

3026 except ValueError: # invalid header value 

3027 return self.__class__(header_value=instance.header_value) 

3028 

3029 new_header_value = ( 

3030 (other_header_value + ', ' + instance.header_value) 

3031 if instance_on_the_right 

3032 else (instance.header_value + ', ' + other_header_value) 

3033 ) 

3034 return self.__class__(header_value=new_header_value) 

3035 

3036 def _old_match(self, mask, offer): 

3037 """ 

3038 Return whether content-coding offer matches codings header item. 

3039 

3040 .. warning:: 

3041 

3042 This is maintained for backward compatibility, and will be 

3043 deprecated in the future. 

3044 

3045 This method was WebOb's old criterion for deciding whether a 

3046 content-coding offer matches a header item (content-coding, 

3047 ``identity`` or ``*``), used in 

3048 

3049 - :meth:`AcceptCharsetValidHeader.__contains__` 

3050 - :meth:`AcceptCharsetValidHeader.best_match` 

3051 - :meth:`AcceptCharsetValidHeader.quality` 

3052 

3053 It does not conform to :rfc:`RFC 7231, section 5.3.4 

3054 <7231#section-5.3.4>` in that it does not interpret ``*`` values in the 

3055 header correctly: ``*`` should only match content-codings not mentioned 

3056 elsewhere in the header. 

3057 """ 

3058 return mask == '*' or offer.lower() == mask.lower() 

3059 

3060 def acceptable_offers(self, offers): 

3061 """ 

3062 Return the offers that are acceptable according to the header. 

3063 

3064 The offers are returned in descending order of preference, where 

3065 preference is indicated by the qvalue of the item (content-coding, 

3066 "identity" or "*") in the header that matches the offer. 

3067 

3068 This uses the matching rules described in :rfc:`RFC 7231, section 5.3.4 

3069 <7231#section-5.3.4>`. 

3070 

3071 :param offers: ``iterable`` of ``str``s, where each ``str`` is a 

3072 content-coding or the string ``identity`` (the token 

3073 used to represent "no encoding") 

3074 :return: A list of tuples of the form (content-coding or "identity", 

3075 qvalue), in descending order of qvalue. Where two offers have 

3076 the same qvalue, they are returned in the same order as their 

3077 order in `offers`. 

3078 

3079 Use the string ``'identity'`` (without the quotes) in `offers` to 

3080 indicate an offer with no content-coding. From the RFC: 'If the 

3081 representation has no content-coding, then it is acceptable by default 

3082 unless specifically excluded by the Accept-Encoding field stating 

3083 either "identity;q=0" or "\\*;q=0" without a more specific entry for 

3084 "identity".' The RFC does not specify the qvalue that should be 

3085 assigned to the representation/offer with no content-coding; this 

3086 implementation assigns it a qvalue of 1.0. 

3087 """ 

3088 lowercased_parsed = [ 

3089 (codings.lower(), qvalue) for (codings, qvalue) in self.parsed 

3090 ] 

3091 lowercased_offers = [offer.lower() for offer in offers] 

3092 

3093 not_acceptable_codingss = set() 

3094 acceptable_codingss = dict() 

3095 asterisk_qvalue = None 

3096 

3097 for codings, qvalue in lowercased_parsed: 

3098 if codings == '*': 

3099 if asterisk_qvalue is None: 

3100 asterisk_qvalue = qvalue 

3101 elif ( 

3102 codings not in acceptable_codingss and codings not in 

3103 not_acceptable_codingss 

3104 # if we have not already encountered this codings in the header 

3105 ): 

3106 if qvalue == 0.0: 

3107 not_acceptable_codingss.add(codings) 

3108 else: 

3109 acceptable_codingss[codings] = qvalue 

3110 acceptable_codingss = list(acceptable_codingss.items()) 

3111 # Sort acceptable_codingss by qvalue, descending order 

3112 acceptable_codingss.sort(key=lambda tuple_: tuple_[1], reverse=True) 

3113 

3114 filtered_offers = [] 

3115 for index, offer in enumerate(lowercased_offers): 

3116 # If offer matches a non-* codings with q=0, it is filtered out 

3117 if any(( 

3118 (offer == codings) for codings in not_acceptable_codingss 

3119 )): 

3120 continue 

3121 

3122 matched_codings_qvalue = None 

3123 for codings, qvalue in acceptable_codingss: 

3124 if offer == codings: 

3125 matched_codings_qvalue = qvalue 

3126 break 

3127 else: 

3128 if asterisk_qvalue: 

3129 matched_codings_qvalue = asterisk_qvalue 

3130 elif asterisk_qvalue != 0.0 and offer == 'identity': 

3131 matched_codings_qvalue = 1.0 

3132 if matched_codings_qvalue is not None: # if there was a match 

3133 filtered_offers.append(( 

3134 offers[index], matched_codings_qvalue, index 

3135 )) 

3136 

3137 # sort by position in `offers` argument, ascending 

3138 filtered_offers.sort(key=lambda tuple_: tuple_[2]) 

3139 # When qvalues are tied, position in `offers` is the tiebreaker. 

3140 

3141 # sort by qvalue, descending 

3142 filtered_offers.sort(key=lambda tuple_: tuple_[1], reverse=True) 

3143 

3144 return [(item[0], item[1]) for item in filtered_offers] 

3145 # (offer, qvalue), dropping the position 

3146 

3147 def best_match(self, offers, default_match=None): 

3148 """ 

3149 Return the best match from the sequence of `offers`. 

3150 

3151 .. warning:: 

3152 

3153 This is currently maintained for backward compatibility, and will be 

3154 deprecated in the future. 

3155 

3156 :meth:`AcceptEncodingValidHeader.best_match` uses its own algorithm 

3157 (one not specified in :rfc:`RFC 7231 <7231>`) to determine what is a 

3158 best match. The algorithm has many issues, and does not conform to 

3159 the RFC. 

3160 

3161 Each offer in `offers` is checked against each non-``q=0`` item 

3162 (content-coding/``identity``/``*``) in the header. If the two are a 

3163 match according to WebOb's old criterion for a match, the quality value 

3164 of the match is the qvalue of the item from the header multiplied by 

3165 the server quality value of the offer (if the server quality value is 

3166 not supplied, it is 1). 

3167 

3168 The offer in the match with the highest quality value is the best 

3169 match. If there is more than one match with the highest qvalue, the one 

3170 that shows up first in `offers` is the best match. 

3171 

3172 :param offers: (iterable) 

3173 

3174 | Each item in the iterable may be a ``str`` *codings*, 

3175 or a (*codings*, server quality value) ``tuple`` or 

3176 ``list``, where *codings* is either a content-coding, 

3177 or the string ``identity`` (which represents *no 

3178 encoding*). ``str`` and ``tuple``/``list`` elements 

3179 may be mixed within the iterable. 

3180 

3181 :param default_match: (optional, any type) the value to be returned if 

3182 there is no match 

3183 

3184 :return: (``str``, or the type of `default_match`) 

3185 

3186 | The offer that is the best match. If there is no match, the 

3187 value of `default_match` is returned. 

3188 

3189 This method does not conform to :rfc:`RFC 7231, section 5.3.4 

3190 <7231#section-5.3.4>`, in that it does not correctly interpret ``*``:: 

3191 

3192 >>> AcceptEncodingValidHeader('gzip;q=0, *').best_match(['gzip']) 

3193 'gzip' 

3194 

3195 and does not handle the ``identity`` token correctly:: 

3196 

3197 >>> instance = AcceptEncodingValidHeader('gzip') 

3198 >>> instance.best_match(['identity']) is None 

3199 True 

3200 """ 

3201 warnings.warn( 

3202 'The behavior of AcceptEncodingValidHeader.best_match is ' 

3203 'currently being maintained for backward compatibility, but it ' 

3204 'will be deprecated in the future, as it does not conform to the' 

3205 ' RFC.', 

3206 DeprecationWarning, 

3207 ) 

3208 best_quality = -1 

3209 best_offer = default_match 

3210 matched_by = '*/*' 

3211 for offer in offers: 

3212 if isinstance(offer, (tuple, list)): 

3213 offer, server_quality = offer 

3214 else: 

3215 server_quality = 1 

3216 for item in self._parsed_nonzero: 

3217 mask = item[0] 

3218 quality = item[1] 

3219 possible_quality = server_quality * quality 

3220 if possible_quality < best_quality: 

3221 continue 

3222 elif possible_quality == best_quality: 

3223 # 'text/plain' overrides 'message/*' overrides '*/*' 

3224 # (if all match w/ the same q=) 

3225 # [We can see that this was written for the Accept header, 

3226 # not the Accept-Encoding header.] 

3227 if matched_by.count('*') <= mask.count('*'): 

3228 continue 

3229 if self._old_match(mask, offer): 

3230 best_quality = possible_quality 

3231 best_offer = offer 

3232 matched_by = mask 

3233 return best_offer 

3234 

3235 def quality(self, offer): 

3236 """ 

3237 Return quality value of given offer, or ``None`` if there is no match. 

3238 

3239 .. warning:: 

3240 

3241 This is currently maintained for backward compatibility, and will be 

3242 deprecated in the future. 

3243 

3244 :param offer: (``str``) A content-coding, or ``identity``. 

3245 :return: (``float`` or ``None``) 

3246 

3247 | The quality value from the header item 

3248 (content-coding/``identity``/``*``) that matches the 

3249 `offer`, or ``None`` if there is no match. 

3250 

3251 The behavior of this method does not conform to :rfc:`RFC 7231, section 

3252 5.3.4<7231#section-5.3.4>`, in that it does not correctly interpret 

3253 ``*``:: 

3254 

3255 >>> AcceptEncodingValidHeader('gzip;q=0, *').quality('gzip') 

3256 1.0 

3257 

3258 and does not handle the ``identity`` token correctly:: 

3259 

3260 >>> AcceptEncodingValidHeader('gzip').quality('identity') is None 

3261 True 

3262 """ 

3263 warnings.warn( 

3264 'The behavior of AcceptEncodingValidHeader.quality is currently ' 

3265 'being maintained for backward compatibility, but it will be ' 

3266 'deprecated in the future, as it does not conform to the RFC.', 

3267 DeprecationWarning, 

3268 ) 

3269 bestq = 0 

3270 for mask, q in self.parsed: 

3271 if self._old_match(mask, offer): 

3272 bestq = max(bestq, q) 

3273 return bestq or None 

3274 

3275 

3276class _AcceptEncodingInvalidOrNoHeader(AcceptEncoding): 

3277 """ 

3278 Represent when an ``Accept-Encoding`` header is invalid or not in request. 

3279 

3280 This is the base class for the behaviour that 

3281 :class:`.AcceptEncodingInvalidHeader` and :class:`.AcceptEncodingNoHeader` 

3282 have in common. 

3283 

3284 :rfc:`7231` does not provide any guidance on what should happen if the 

3285 ``AcceptEncoding`` header has an invalid value. This implementation 

3286 disregards the header when the header is invalid, so 

3287 :class:`.AcceptEncodingInvalidHeader` and :class:`.AcceptEncodingNoHeader` 

3288 have much behaviour in common. 

3289 """ 

3290 

3291 def __bool__(self): 

3292 """ 

3293 Return whether ``self`` represents a valid ``Accept-Encoding`` header. 

3294 

3295 Return ``True`` if ``self`` represents a valid header, and ``False`` if 

3296 it represents an invalid header, or the header not being in the 

3297 request. 

3298 

3299 For this class, it always returns ``False``. 

3300 """ 

3301 return False 

3302 __nonzero__ = __bool__ # Python 2 

3303 

3304 def __contains__(self, offer): 

3305 """ 

3306 Return ``bool`` indicating whether `offer` is acceptable. 

3307 

3308 .. warning:: 

3309 

3310 The behavior of ``.__contains__`` for the ``Accept-Encoding`` 

3311 classes is currently being maintained for backward compatibility, 

3312 but it will change in the future to better conform to the RFC. 

3313 

3314 :param offer: (``str``) a content-coding or ``identity`` offer 

3315 :return: (``bool``) Whether ``offer`` is acceptable according to the 

3316 header. 

3317 

3318 For this class, either there is no ``Accept-Encoding`` header in the 

3319 request, or the header is invalid, so any content-coding is acceptable, 

3320 and this always returns ``True``. 

3321 """ 

3322 warnings.warn( 

3323 'The behavior of .__contains__ for the Accept-Encoding classes is ' 

3324 'currently being maintained for backward compatibility, but it ' 

3325 'will change in the future to better conform to the RFC.', 

3326 DeprecationWarning, 

3327 ) 

3328 return True 

3329 

3330 def __iter__(self): 

3331 """ 

3332 Return all the header items with non-0 qvalues, in order of preference. 

3333 

3334 .. warning:: 

3335 

3336 The behavior of this method is currently maintained for backward 

3337 compatibility, but will change in the future. 

3338 

3339 :return: iterator of all the (content-coding/``identity``/``*``) items 

3340 in the header with non-0 qvalues, in descending order of 

3341 qvalue. If two items have the same qvalue, they are returned 

3342 in the order of their positions in the header, from left to 

3343 right. 

3344 

3345 When there is no ``Accept-Encoding`` header in the request or the 

3346 header is invalid, there are no items in the header, so this always 

3347 returns an empty iterator. 

3348 """ 

3349 warnings.warn( 

3350 'The behavior of AcceptEncodingValidHeader.__iter__ is currently ' 

3351 'maintained for backward compatibility, but will change in the ' 

3352 'future.', 

3353 DeprecationWarning, 

3354 ) 

3355 return iter(()) 

3356 

3357 def acceptable_offers(self, offers): 

3358 """ 

3359 Return the offers that are acceptable according to the header. 

3360 

3361 :param offers: ``iterable`` of ``str``s, where each ``str`` is a 

3362 content-coding or the string ``identity`` (the token 

3363 used to represent "no encoding") 

3364 :return: When the header is invalid, or there is no ``Accept-Encoding`` 

3365 header in the request, all `offers` are considered acceptable, 

3366 so this method returns a list of (content-coding or 

3367 "identity", qvalue) tuples where each offer in `offers` is 

3368 paired with the qvalue of 1.0, in the same order as in 

3369 `offers`. 

3370 """ 

3371 return [(offer, 1.0) for offer in offers] 

3372 

3373 def best_match(self, offers, default_match=None): 

3374 """ 

3375 Return the best match from the sequence of `offers`. 

3376 

3377 This is the ``.best_match()`` method for when the header is invalid or 

3378 not found in the request, corresponding to 

3379 :meth:`AcceptEncodingValidHeader.best_match`. 

3380 

3381 .. warning:: 

3382 

3383 This is currently maintained for backward compatibility, and will be 

3384 deprecated in the future (see the documentation for 

3385 :meth:`AcceptEncodingValidHeader.best_match`). 

3386 

3387 When the header is invalid, or there is no `Accept-Encoding` header in 

3388 the request, all `offers` are considered acceptable, so the best match 

3389 is the offer in `offers` with the highest server quality value (if the 

3390 server quality value is not supplied for a media type, it is 1). 

3391 

3392 If more than one offer in `offers` have the same highest server quality 

3393 value, then the one that shows up first in `offers` is the best match. 

3394 

3395 :param offers: (iterable) 

3396 

3397 | Each item in the iterable may be a ``str`` *codings*, 

3398 or a (*codings*, server quality value) ``tuple`` or 

3399 ``list``, where *codings* is either a content-coding, 

3400 or the string ``identity`` (which represents *no 

3401 encoding*). ``str`` and ``tuple``/``list`` elements 

3402 may be mixed within the iterable. 

3403 

3404 :param default_match: (optional, any type) the value to be returned if 

3405 `offers` is empty. 

3406 

3407 :return: (``str``, or the type of `default_match`) 

3408 

3409 | The offer that has the highest server quality value. If 

3410 `offers` is empty, the value of `default_match` is returned. 

3411 """ 

3412 warnings.warn( 

3413 'The behavior of .best_match for the Accept-Encoding classes is ' 

3414 'currently being maintained for backward compatibility, but the ' 

3415 'method will be deprecated in the future, as its behavior is not ' 

3416 'specified in (and currently does not conform to) RFC 7231.', 

3417 DeprecationWarning, 

3418 ) 

3419 best_quality = -1 

3420 best_offer = default_match 

3421 for offer in offers: 

3422 if isinstance(offer, (list, tuple)): 

3423 offer, quality = offer 

3424 else: 

3425 quality = 1 

3426 if quality > best_quality: 

3427 best_offer = offer 

3428 best_quality = quality 

3429 return best_offer 

3430 

3431 def quality(self, offer): 

3432 """ 

3433 Return quality value of given offer, or ``None`` if there is no match. 

3434 

3435 This is the ``.quality()`` method for when the header is invalid or not 

3436 found in the request, corresponding to 

3437 :meth:`AcceptEncodingValidHeader.quality`. 

3438 

3439 .. warning:: 

3440 

3441 This is currently maintained for backward compatibility, and will be 

3442 deprecated in the future (see the documentation for 

3443 :meth:`AcceptEncodingValidHeader.quality`). 

3444 

3445 :param offer: (``str``) A content-coding, or ``identity``. 

3446 :return: (``float``) ``1.0``. 

3447 

3448 When the ``Accept-Encoding`` header is invalid or not in the request, 

3449 all offers are equally acceptable, so 1.0 is always returned. 

3450 """ 

3451 warnings.warn( 

3452 'The behavior of .quality for the Accept-Encoding classes is ' 

3453 'currently being maintained for backward compatibility, but the ' 

3454 'method will be deprecated in the future, as its behavior does ' 

3455 'not conform to RFC 7231.', 

3456 DeprecationWarning, 

3457 ) 

3458 return 1.0 

3459 

3460 

3461class AcceptEncodingNoHeader(_AcceptEncodingInvalidOrNoHeader): 

3462 """ 

3463 Represent when there is no ``Accept-Encoding`` header in the request. 

3464 

3465 This object should not be modified. To add to the header, we can use the 

3466 addition operators (``+`` and ``+=``), which return a new object (see the 

3467 docstring for :meth:`AcceptEncodingNoHeader.__add__`). 

3468 """ 

3469 

3470 @property 

3471 def header_value(self): 

3472 """ 

3473 (``str`` or ``None``) The header value. 

3474 

3475 As there is no header in the request, this is ``None``. 

3476 """ 

3477 return self._header_value 

3478 

3479 @property 

3480 def parsed(self): 

3481 """ 

3482 (``list`` or ``None``) Parsed form of the header. 

3483 

3484 As there is no header in the request, this is ``None``. 

3485 """ 

3486 return self._parsed 

3487 

3488 def __init__(self): 

3489 """ 

3490 Create an :class:`AcceptEncodingNoHeader` instance. 

3491 """ 

3492 self._header_value = None 

3493 self._parsed = None 

3494 self._parsed_nonzero = None 

3495 

3496 def copy(self): 

3497 """ 

3498 Create a copy of the header object. 

3499 

3500 """ 

3501 return self.__class__() 

3502 

3503 def __add__(self, other): 

3504 """ 

3505 Add to header, creating a new header object. 

3506 

3507 `other` can be: 

3508 

3509 * ``None`` 

3510 * a ``str`` header value 

3511 * a ``dict``, with content-coding, ``identity`` or ``*`` ``str``'s as 

3512 keys, and qvalue ``float``'s as values 

3513 * a ``tuple`` or ``list``, where each item is either a header element 

3514 ``str``, or a (content-coding/``identity``/``*``, qvalue) ``tuple`` 

3515 or ``list`` 

3516 * an :class:`AcceptEncodingValidHeader`, 

3517 :class:`AcceptEncodingNoHeader`, or 

3518 :class:`AcceptEncodingInvalidHeader` instance 

3519 * object of any other type that returns a value for ``__str__`` 

3520 

3521 If `other` is a valid header value or an 

3522 :class:`AcceptEncodingValidHeader` instance, a new 

3523 :class:`AcceptEncodingValidHeader` instance with the valid header value 

3524 is returned. 

3525 

3526 If `other` is ``None``, an :class:`AcceptEncodingNoHeader` instance, an 

3527 invalid header value, or an :class:`AcceptEncodingInvalidHeader` 

3528 instance, a new :class:`AcceptEncodingNoHeader` instance is returned. 

3529 """ 

3530 if isinstance(other, AcceptEncodingValidHeader): 

3531 return AcceptEncodingValidHeader(header_value=other.header_value) 

3532 

3533 if isinstance( 

3534 other, (AcceptEncodingNoHeader, AcceptEncodingInvalidHeader) 

3535 ): 

3536 return self.__class__() 

3537 

3538 return self._add_instance_and_non_accept_encoding_type( 

3539 instance=self, other=other, 

3540 ) 

3541 

3542 def __radd__(self, other): 

3543 """ 

3544 Add to header, creating a new header object. 

3545 

3546 See the docstring for :meth:`AcceptEncodingNoHeader.__add__`. 

3547 """ 

3548 return self.__add__(other=other) 

3549 

3550 def __repr__(self): 

3551 return '<{}>'.format(self.__class__.__name__) 

3552 

3553 def __str__(self): 

3554 """Return the ``str`` ``'<no header in request>'``.""" 

3555 return '<no header in request>' 

3556 

3557 def _add_instance_and_non_accept_encoding_type(self, instance, other): 

3558 if other is None: 

3559 return self.__class__() 

3560 

3561 other_header_value = self._python_value_to_header_str(value=other) 

3562 

3563 try: 

3564 return AcceptEncodingValidHeader(header_value=other_header_value) 

3565 except ValueError: # invalid header value 

3566 return self.__class__() 

3567 

3568 

3569class AcceptEncodingInvalidHeader(_AcceptEncodingInvalidOrNoHeader): 

3570 """ 

3571 Represent an invalid ``Accept-Encoding`` header. 

3572 

3573 An invalid header is one that does not conform to 

3574 :rfc:`7231#section-5.3.4`. 

3575 

3576 :rfc:`7231` does not provide any guidance on what should happen if the 

3577 ``Accept-Encoding`` header has an invalid value. This implementation 

3578 disregards the header, and treats it as if there is no ``Accept-Encoding`` 

3579 header in the request. 

3580 

3581 This object should not be modified. To add to the header, we can use the 

3582 addition operators (``+`` and ``+=``), which return a new object (see the 

3583 docstring for :meth:`AcceptEncodingInvalidHeader.__add__`). 

3584 """ 

3585 

3586 @property 

3587 def header_value(self): 

3588 """(``str`` or ``None``) The header value.""" 

3589 return self._header_value 

3590 

3591 @property 

3592 def parsed(self): 

3593 """ 

3594 (``list`` or ``None``) Parsed form of the header. 

3595 

3596 As the header is invalid and cannot be parsed, this is ``None``. 

3597 """ 

3598 return self._parsed 

3599 

3600 def __init__(self, header_value): 

3601 """ 

3602 Create an :class:`AcceptEncodingInvalidHeader` instance. 

3603 """ 

3604 self._header_value = header_value 

3605 self._parsed = None 

3606 self._parsed_nonzero = None 

3607 

3608 def copy(self): 

3609 """ 

3610 Create a copy of the header object. 

3611 

3612 """ 

3613 return self.__class__(self._header_value) 

3614 

3615 def __add__(self, other): 

3616 """ 

3617 Add to header, creating a new header object. 

3618 

3619 `other` can be: 

3620 

3621 * ``None`` 

3622 * a ``str`` header value 

3623 * a ``dict``, with content-coding, ``identity`` or ``*`` ``str``'s as 

3624 keys, and qvalue ``float``'s as values 

3625 * a ``tuple`` or ``list``, where each item is either a header element 

3626 ``str``, or a (content-coding/``identity``/``*``, qvalue) ``tuple`` 

3627 or ``list`` 

3628 * an :class:`AcceptEncodingValidHeader`, 

3629 :class:`AcceptEncodingNoHeader`, or 

3630 :class:`AcceptEncodingInvalidHeader` instance 

3631 * object of any other type that returns a value for ``__str__`` 

3632 

3633 If `other` is a valid header value or an 

3634 :class:`AcceptEncodingValidHeader` instance, then a new 

3635 :class:`AcceptEncodingValidHeader` instance with the valid header value 

3636 is returned. 

3637 

3638 If `other` is ``None``, an :class:`AcceptEncodingNoHeader` instance, an 

3639 invalid header value, or an :class:`AcceptEncodingInvalidHeader` 

3640 instance, a new :class:`AcceptEncodingNoHeader` instance is returned. 

3641 """ 

3642 if isinstance(other, AcceptEncodingValidHeader): 

3643 return AcceptEncodingValidHeader(header_value=other.header_value) 

3644 

3645 if isinstance( 

3646 other, (AcceptEncodingNoHeader, AcceptEncodingInvalidHeader) 

3647 ): 

3648 return AcceptEncodingNoHeader() 

3649 

3650 return self._add_instance_and_non_accept_encoding_type( 

3651 instance=self, other=other, 

3652 ) 

3653 

3654 def __radd__(self, other): 

3655 """ 

3656 Add to header, creating a new header object. 

3657 

3658 See the docstring for :meth:`AcceptEncodingValidHeader.__add__`. 

3659 """ 

3660 return self._add_instance_and_non_accept_encoding_type( 

3661 instance=self, other=other, instance_on_the_right=True, 

3662 ) 

3663 

3664 def __repr__(self): 

3665 return '<{}>'.format(self.__class__.__name__) 

3666 # We do not display the header_value, as it is untrusted input. The 

3667 # header_value could always be easily obtained from the .header_value 

3668 # property. 

3669 

3670 def __str__(self): 

3671 """Return the ``str`` ``'<invalid header value>'``.""" 

3672 return '<invalid header value>' 

3673 

3674 def _add_instance_and_non_accept_encoding_type( 

3675 self, instance, other, instance_on_the_right=False, 

3676 ): 

3677 if other is None: 

3678 return AcceptEncodingNoHeader() 

3679 

3680 other_header_value = self._python_value_to_header_str(value=other) 

3681 

3682 try: 

3683 return AcceptEncodingValidHeader(header_value=other_header_value) 

3684 except ValueError: # invalid header value 

3685 return AcceptEncodingNoHeader() 

3686 

3687 

3688def create_accept_encoding_header(header_value): 

3689 """ 

3690 Create an object representing the ``Accept-Encoding`` header in a request. 

3691 

3692 :param header_value: (``str``) header value 

3693 :return: If `header_value` is ``None``, an :class:`AcceptEncodingNoHeader` 

3694 instance. 

3695 

3696 | If `header_value` is a valid ``Accept-Encoding`` header, an 

3697 :class:`AcceptEncodingValidHeader` instance. 

3698 

3699 | If `header_value` is an invalid ``Accept-Encoding`` header, an 

3700 :class:`AcceptEncodingInvalidHeader` instance. 

3701 """ 

3702 if header_value is None: 

3703 return AcceptEncodingNoHeader() 

3704 if isinstance(header_value, AcceptEncoding): 

3705 return header_value.copy() 

3706 try: 

3707 return AcceptEncodingValidHeader(header_value=header_value) 

3708 except ValueError: 

3709 return AcceptEncodingInvalidHeader(header_value=header_value) 

3710 

3711 

3712def accept_encoding_property(): 

3713 doc = """ 

3714 Property representing the ``Accept-Encoding`` header. 

3715 

3716 (:rfc:`RFC 7231, section 5.3.4 <7231#section-5.3.4>`) 

3717 

3718 The header value in the request environ is parsed and a new object 

3719 representing the header is created every time we *get* the value of the 

3720 property. (*set* and *del* change the header value in the request 

3721 environ, and do not involve parsing.) 

3722 """ 

3723 

3724 ENVIRON_KEY = 'HTTP_ACCEPT_ENCODING' 

3725 

3726 def fget(request): 

3727 """Get an object representing the header in the request.""" 

3728 return create_accept_encoding_header( 

3729 header_value=request.environ.get(ENVIRON_KEY) 

3730 ) 

3731 

3732 def fset(request, value): 

3733 """ 

3734 Set the corresponding key in the request environ. 

3735 

3736 `value` can be: 

3737 

3738 * ``None`` 

3739 * a ``str`` header value 

3740 * a ``dict``, with content-coding, ``identity`` or ``*`` ``str``'s as 

3741 keys, and qvalue ``float``'s as values 

3742 * a ``tuple`` or ``list``, where each item is either a header element 

3743 ``str``, or a (content-coding/``identity``/``*``, qvalue) ``tuple`` 

3744 or ``list`` 

3745 * an :class:`AcceptEncodingValidHeader`, 

3746 :class:`AcceptEncodingNoHeader`, or 

3747 :class:`AcceptEncodingInvalidHeader` instance 

3748 * object of any other type that returns a value for ``__str__`` 

3749 """ 

3750 if value is None or isinstance(value, AcceptEncodingNoHeader): 

3751 fdel(request=request) 

3752 else: 

3753 if isinstance( 

3754 value, (AcceptEncodingValidHeader, AcceptEncodingInvalidHeader) 

3755 ): 

3756 header_value = value.header_value 

3757 else: 

3758 header_value = AcceptEncoding._python_value_to_header_str( 

3759 value=value, 

3760 ) 

3761 request.environ[ENVIRON_KEY] = header_value 

3762 

3763 def fdel(request): 

3764 """Delete the corresponding key from the request environ.""" 

3765 try: 

3766 del request.environ[ENVIRON_KEY] 

3767 except KeyError: 

3768 pass 

3769 

3770 return property(fget, fset, fdel, textwrap.dedent(doc)) 

3771 

3772 

3773class AcceptLanguage(object): 

3774 """ 

3775 Represent an ``Accept-Language`` header. 

3776 

3777 Base class for :class:`AcceptLanguageValidHeader`, 

3778 :class:`AcceptLanguageNoHeader`, and :class:`AcceptLanguageInvalidHeader`. 

3779 """ 

3780 

3781 # RFC 7231 Section 5.3.5 "Accept-Language": 

3782 # Accept-Language = 1#( language-range [ weight ] ) 

3783 # language-range = 

3784 # <language-range, see [RFC4647], Section 2.1> 

3785 # RFC 4647 Section 2.1 "Basic Language Range": 

3786 # language-range = (1*8ALPHA *("-" 1*8alphanum)) / "*" 

3787 # alphanum = ALPHA / DIGIT 

3788 lang_range_re = ( 

3789 r'\*|' 

3790 '(?:' 

3791 '[A-Za-z]{1,8}' 

3792 '(?:-[A-Za-z0-9]{1,8})*' 

3793 ')' 

3794 ) 

3795 lang_range_n_weight_re = _item_n_weight_re(item_re=lang_range_re) 

3796 lang_range_n_weight_compiled_re = re.compile(lang_range_n_weight_re) 

3797 accept_language_compiled_re = _list_1_or_more__compiled_re( 

3798 element_re=lang_range_n_weight_re, 

3799 ) 

3800 

3801 @classmethod 

3802 def _python_value_to_header_str(cls, value): 

3803 if isinstance(value, str): 

3804 header_str = value 

3805 else: 

3806 if hasattr(value, 'items'): 

3807 value = sorted( 

3808 value.items(), 

3809 key=lambda item: item[1], 

3810 reverse=True, 

3811 ) 

3812 if isinstance(value, (tuple, list)): 

3813 result = [] 

3814 for element in value: 

3815 if isinstance(element, (tuple, list)): 

3816 element = _item_qvalue_pair_to_header_element( 

3817 pair=element 

3818 ) 

3819 result.append(element) 

3820 header_str = ', '.join(result) 

3821 else: 

3822 header_str = str(value) 

3823 return header_str 

3824 

3825 @classmethod 

3826 def parse(cls, value): 

3827 """ 

3828 Parse an ``Accept-Language`` header. 

3829 

3830 :param value: (``str``) header value 

3831 :return: If `value` is a valid ``Accept-Language`` header, returns an 

3832 iterator of (language range, quality value) tuples, as parsed 

3833 from the header from left to right. 

3834 :raises ValueError: if `value` is an invalid header 

3835 """ 

3836 # Check if header is valid 

3837 # Using Python stdlib's `re` module, there is currently no way to check 

3838 # the match *and* get all the groups using the same regex, so we have 

3839 # to use one regex to check the match, and another to get the groups. 

3840 if cls.accept_language_compiled_re.match(value) is None: 

3841 raise ValueError('Invalid value for an Accept-Language header.') 

3842 def generator(value): 

3843 for match in ( 

3844 cls.lang_range_n_weight_compiled_re.finditer(value) 

3845 ): 

3846 lang_range = match.group(1) 

3847 qvalue = match.group(2) 

3848 qvalue = float(qvalue) if qvalue else 1.0 

3849 yield (lang_range, qvalue) 

3850 return generator(value=value) 

3851 

3852 

3853class AcceptLanguageValidHeader(AcceptLanguage): 

3854 """ 

3855 Represent a valid ``Accept-Language`` header. 

3856 

3857 A valid header is one that conforms to :rfc:`RFC 7231, section 5.3.5 

3858 <7231#section-5.3.5>`. 

3859 

3860 We take the reference from the ``language-range`` syntax rule in :rfc:`RFC 

3861 7231, section 5.3.5 <7231#section-5.3.5>` to :rfc:`RFC 4647, section 2.1 

3862 <4647#section-2.1>` to mean that only basic language ranges (and not 

3863 extended language ranges) are expected in the ``Accept-Language`` header. 

3864 

3865 This object should not be modified. To add to the header, we can use the 

3866 addition operators (``+`` and ``+=``), which return a new object (see the 

3867 docstring for :meth:`AcceptLanguageValidHeader.__add__`). 

3868 """ 

3869 

3870 def __init__(self, header_value): 

3871 """ 

3872 Create an :class:`AcceptLanguageValidHeader` instance. 

3873 

3874 :param header_value: (``str``) header value. 

3875 :raises ValueError: if `header_value` is an invalid value for an 

3876 ``Accept-Language`` header. 

3877 """ 

3878 self._header_value = header_value 

3879 self._parsed = list(self.parse(header_value)) 

3880 self._parsed_nonzero = [item for item in self.parsed if item[1]] 

3881 # item[1] is the qvalue 

3882 

3883 def copy(self): 

3884 """ 

3885 Create a copy of the header object. 

3886 

3887 """ 

3888 return self.__class__(self._header_value) 

3889 

3890 @property 

3891 def header_value(self): 

3892 """(``str`` or ``None``) The header value.""" 

3893 return self._header_value 

3894 

3895 @property 

3896 def parsed(self): 

3897 """ 

3898 (``list`` or ``None``) Parsed form of the header. 

3899 

3900 A list of (language range, quality value) tuples. 

3901 """ 

3902 return self._parsed 

3903 

3904 def __add__(self, other): 

3905 """ 

3906 Add to header, creating a new header object. 

3907 

3908 `other` can be: 

3909 

3910 * ``None`` 

3911 * a ``str`` 

3912 * a ``dict``, with language ranges as keys and qvalues as values 

3913 * a ``tuple`` or ``list``, of language range ``str``'s or of ``tuple`` 

3914 or ``list`` (language range, qvalue) pairs (``str``'s and pairs can 

3915 be mixed within the ``tuple`` or ``list``) 

3916 * an :class:`AcceptLanguageValidHeader`, 

3917 :class:`AcceptLanguageNoHeader`, or 

3918 :class:`AcceptLanguageInvalidHeader` instance 

3919 * object of any other type that returns a value for ``__str__`` 

3920 

3921 If `other` is a valid header value or another 

3922 :class:`AcceptLanguageValidHeader` instance, the two header values are 

3923 joined with ``', '``, and a new :class:`AcceptLanguageValidHeader` 

3924 instance with the new header value is returned. 

3925 

3926 If `other` is ``None``, an :class:`AcceptLanguageNoHeader` instance, an 

3927 invalid header value, or an :class:`AcceptLanguageInvalidHeader` 

3928 instance, a new :class:`AcceptLanguageValidHeader` instance with the 

3929 same header value as ``self`` is returned. 

3930 """ 

3931 if isinstance(other, AcceptLanguageValidHeader): 

3932 return create_accept_language_header( 

3933 header_value=self.header_value + ', ' + other.header_value, 

3934 ) 

3935 

3936 if isinstance( 

3937 other, (AcceptLanguageNoHeader, AcceptLanguageInvalidHeader) 

3938 ): 

3939 return self.__class__(header_value=self.header_value) 

3940 

3941 return self._add_instance_and_non_accept_language_type( 

3942 instance=self, other=other, 

3943 ) 

3944 

3945 def __nonzero__(self): 

3946 """ 

3947 Return whether ``self`` represents a valid ``Accept-Language`` header. 

3948 

3949 Return ``True`` if ``self`` represents a valid header, and ``False`` if 

3950 it represents an invalid header, or the header not being in the 

3951 request. 

3952 

3953 For this class, it always returns ``True``. 

3954 """ 

3955 return True 

3956 __bool__ = __nonzero__ # Python 3 

3957 

3958 def __contains__(self, offer): 

3959 """ 

3960 Return ``bool`` indicating whether `offer` is acceptable. 

3961 

3962 .. warning:: 

3963 

3964 The behavior of :meth:`AcceptLanguageValidHeader.__contains__` is 

3965 currently being maintained for backward compatibility, but it will 

3966 change in the future to better conform to the RFC. 

3967 

3968 What is 'acceptable' depends on the needs of your application. 

3969 :rfc:`RFC 7231, section 5.3.5 <7231#section-5.3.5>` suggests three 

3970 matching schemes from :rfc:`RFC 4647 <4647>`, two of which WebOb 

3971 supports with :meth:`AcceptLanguageValidHeader.basic_filtering` and 

3972 :meth:`AcceptLanguageValidHeader.lookup` (we interpret the RFC to 

3973 mean that Extended Filtering cannot apply for the 

3974 ``Accept-Language`` header, as the header only accepts basic 

3975 language ranges.) If these are not suitable for the needs of your 

3976 application, you may need to write your own matching using 

3977 :attr:`AcceptLanguageValidHeader.parsed`. 

3978 

3979 :param offer: (``str``) language tag offer 

3980 :return: (``bool``) Whether ``offer`` is acceptable according to the 

3981 header. 

3982 

3983 This uses the old criterion of a match in 

3984 :meth:`AcceptLanguageValidHeader._old_match`, which does not conform to 

3985 :rfc:`RFC 7231, section 5.3.5 <7231#section-5.3.5>` or any of the 

3986 matching schemes suggested there. It also does not properly take into 

3987 account ranges with ``q=0`` in the header:: 

3988 

3989 >>> 'en-gb' in AcceptLanguageValidHeader('en, en-gb;q=0') 

3990 True 

3991 >>> 'en' in AcceptLanguageValidHeader('en;q=0, *') 

3992 True 

3993 

3994 (See the docstring for :meth:`AcceptLanguageValidHeader._old_match` for 

3995 other problems with the old criterion for a match.) 

3996 """ 

3997 warnings.warn( 

3998 'The behavior of AcceptLanguageValidHeader.__contains__ is ' 

3999 'currently being maintained for backward compatibility, but it ' 

4000 'will change in the future to better conform to the RFC.', 

4001 DeprecationWarning, 

4002 ) 

4003 for mask, quality in self._parsed_nonzero: 

4004 if self._old_match(mask, offer): 

4005 return True 

4006 return False 

4007 

4008 def __iter__(self): 

4009 """ 

4010 Return all the ranges with non-0 qvalues, in order of preference. 

4011 

4012 .. warning:: 

4013 

4014 The behavior of this method is currently maintained for backward 

4015 compatibility, but will change in the future. 

4016 

4017 :return: iterator of all the language ranges in the header with non-0 

4018 qvalues, in descending order of qvalue. If two ranges have the 

4019 same qvalue, they are returned in the order of their positions 

4020 in the header, from left to right. 

4021 

4022 Please note that this is a simple filter for the ranges in the header 

4023 with non-0 qvalues, and is not necessarily the same as what the client 

4024 prefers, e.g. ``'en-gb;q=0, *'`` means 'everything but British 

4025 English', but ``list(instance)`` would return only ``['*']``. 

4026 """ 

4027 warnings.warn( 

4028 'The behavior of AcceptLanguageValidHeader.__iter__ is currently ' 

4029 'maintained for backward compatibility, but will change in the ' 

4030 'future.', 

4031 DeprecationWarning, 

4032 ) 

4033 

4034 for m, q in sorted( 

4035 self._parsed_nonzero, 

4036 key=lambda i: i[1], 

4037 reverse=True 

4038 ): 

4039 yield m 

4040 

4041 def __radd__(self, other): 

4042 """ 

4043 Add to header, creating a new header object. 

4044 

4045 See the docstring for :meth:`AcceptLanguageValidHeader.__add__`. 

4046 """ 

4047 return self._add_instance_and_non_accept_language_type( 

4048 instance=self, other=other, instance_on_the_right=True, 

4049 ) 

4050 

4051 def __repr__(self): 

4052 return '<{} ({!r})>'.format(self.__class__.__name__, str(self)) 

4053 

4054 def __str__(self): 

4055 r""" 

4056 Return a tidied up version of the header value. 

4057 

4058 e.g. If the ``header_value`` is ``', \t,de;q=0.000 \t, es;q=1.000, zh, 

4059 jp;q=0.210 ,'``, ``str(instance)`` returns ``'de;q=0, es, zh, 

4060 jp;q=0.21'``. 

4061 """ 

4062 return ', '.join( 

4063 _item_qvalue_pair_to_header_element(pair=tuple_) 

4064 for tuple_ in self.parsed 

4065 ) 

4066 

4067 def _add_instance_and_non_accept_language_type( 

4068 self, instance, other, instance_on_the_right=False, 

4069 ): 

4070 if not other: 

4071 return self.__class__(header_value=instance.header_value) 

4072 

4073 other_header_value = self._python_value_to_header_str(value=other) 

4074 

4075 try: 

4076 self.parse(value=other_header_value) 

4077 except ValueError: # invalid header value 

4078 return self.__class__(header_value=instance.header_value) 

4079 

4080 new_header_value = ( 

4081 (other_header_value + ', ' + instance.header_value) 

4082 if instance_on_the_right 

4083 else (instance.header_value + ', ' + other_header_value) 

4084 ) 

4085 return self.__class__(header_value=new_header_value) 

4086 

4087 def _old_match(self, mask, item): 

4088 """ 

4089 Return whether a language tag matches a language range. 

4090 

4091 .. warning:: 

4092 

4093 This is maintained for backward compatibility, and will be 

4094 deprecated in the future. 

4095 

4096 This method was WebOb's old criterion for deciding whether a language 

4097 tag matches a language range, used in 

4098 

4099 - :meth:`AcceptLanguageValidHeader.__contains__` 

4100 - :meth:`AcceptLanguageValidHeader.best_match` 

4101 - :meth:`AcceptLanguageValidHeader.quality` 

4102 

4103 It does not conform to :rfc:`RFC 7231, section 5.3.5 

4104 <7231#section-5.3.5>`, or any of the matching schemes suggested there. 

4105 

4106 :param mask: (``str``) 

4107 

4108 | language range 

4109 

4110 :param item: (``str``) 

4111 

4112 | language tag. Subtags in language tags are separated by 

4113 ``-`` (hyphen). If there are underscores (``_``) in this 

4114 argument, they will be converted to hyphens before 

4115 checking the match. 

4116 

4117 :return: (``bool``) whether the tag in `item` matches the range in 

4118 `mask`. 

4119 

4120 `mask` and `item` are a match if: 

4121 

4122 - ``mask == *``. 

4123 - ``mask == item``. 

4124 - If the first subtag of `item` equals `mask`, or if the first subtag 

4125 of `mask` equals `item`. 

4126 This means that:: 

4127 

4128 >>> instance._old_match(mask='en-gb', item='en') 

4129 True 

4130 >>> instance._old_match(mask='en', item='en-gb') 

4131 True 

4132 

4133 Which is different from any of the matching schemes suggested in 

4134 :rfc:`RFC 7231, section 5.3.5 <7231#section-5.3.5>`, in that none of 

4135 those schemes match both more *and* less specific tags. 

4136 

4137 However, this method appears to be only designed for language tags 

4138 and ranges with at most two subtags. So with an `item`/language tag 

4139 with more than two subtags like ``zh-Hans-CN``:: 

4140 

4141 >>> instance._old_match(mask='zh', item='zh-Hans-CN') 

4142 True 

4143 >>> instance._old_match(mask='zh-Hans', item='zh-Hans-CN') 

4144 False 

4145 

4146 From commit history, this does not appear to have been from a 

4147 decision to match only the first subtag, but rather because only 

4148 language ranges and tags with at most two subtags were expected. 

4149 """ 

4150 item = item.replace('_', '-').lower() 

4151 mask = mask.lower() 

4152 return (mask == '*' 

4153 or item == mask 

4154 or item.split('-')[0] == mask 

4155 or item == mask.split('-')[0] 

4156 ) 

4157 

4158 def basic_filtering(self, language_tags): 

4159 """ 

4160 Return the tags that match the header, using Basic Filtering. 

4161 

4162 This is an implementation of the Basic Filtering matching scheme, 

4163 suggested as a matching scheme for the ``Accept-Language`` header in 

4164 :rfc:`RFC 7231, section 5.3.5 <7231#section-5.3.5>`, and defined in 

4165 :rfc:`RFC 4647, section 3.3.1 <4647#section-3.3.1>`. It filters the 

4166 tags in the `language_tags` argument and returns the ones that match 

4167 the header according to the matching scheme. 

4168 

4169 :param language_tags: (``iterable``) language tags 

4170 :return: A list of tuples of the form (language tag, qvalue), in 

4171 descending order of qvalue. If two or more tags have the same 

4172 qvalue, they are returned in the same order as that in the 

4173 header of the ranges they matched. If the matched range is the 

4174 same for two or more tags (i.e. their matched ranges have the 

4175 same qvalue and the same position in the header), then they 

4176 are returned in the same order as that in the `language_tags` 

4177 argument. If `language_tags` is unordered, e.g. if it is a set 

4178 or a dict, then that order may not be reliable. 

4179 

4180 For each tag in `language_tags`: 

4181 

4182 1. If the tag matches a non-``*`` language range in the header with 

4183 ``q=0`` (meaning "not acceptable", see :rfc:`RFC 7231, section 5.3.1 

4184 <7231#section-5.3.1>`), the tag is filtered out. 

4185 2. The non-``*`` language ranges in the header that do not have ``q=0`` 

4186 are considered in descending order of qvalue; where two or more 

4187 language ranges have the same qvalue, they are considered in the 

4188 order in which they appear in the header. 

4189 3. A language range 'matches a particular language tag if, in a 

4190 case-insensitive comparison, it exactly equals the tag, or if it 

4191 exactly equals a prefix of the tag such that the first character 

4192 following the prefix is "-".' (:rfc:`RFC 4647, section 3.3.1 

4193 <4647#section-3.3.1>`) 

4194 4. If the tag does not match any of the non-``*`` language ranges, and 

4195 there is a ``*`` language range in the header, then if the ``*`` 

4196 language range has ``q=0``, the language tag is filtered out, 

4197 otherwise the tag is considered a match. 

4198 

4199 (If a range (``*`` or non-``*``) appears in the header more than once 

4200 -- this would not make sense, but is nonetheless a valid header 

4201 according to the RFC -- the first in the header is used for matching, 

4202 and the others are ignored.) 

4203 """ 

4204 # The Basic Filtering matching scheme as applied to the Accept-Language 

4205 # header is very under-specified by RFCs 7231 and 4647. This 

4206 # implementation combines the description of the matching scheme in RFC 

4207 # 4647 and the rules of the Accept-Language header in RFC 7231 to 

4208 # arrive at an algorithm for Basic Filtering as applied to the 

4209 # Accept-Language header. 

4210 

4211 lowercased_parsed = [ 

4212 (range_.lower(), qvalue) for (range_, qvalue) in self.parsed 

4213 ] 

4214 lowercased_tags = [tag.lower() for tag in language_tags] 

4215 

4216 not_acceptable_ranges = set() 

4217 acceptable_ranges = dict() 

4218 asterisk_qvalue = None 

4219 

4220 for position_in_header, (range_, qvalue) in enumerate( 

4221 lowercased_parsed 

4222 ): 

4223 if range_ == '*': 

4224 if asterisk_qvalue is None: 

4225 asterisk_qvalue = qvalue 

4226 asterisk_position = position_in_header 

4227 elif ( 

4228 range_ not in acceptable_ranges and range_ not in 

4229 not_acceptable_ranges 

4230 # if we have not already encountered this range in the header 

4231 ): 

4232 if qvalue == 0.0: 

4233 not_acceptable_ranges.add(range_) 

4234 else: 

4235 acceptable_ranges[range_] = (qvalue, position_in_header) 

4236 acceptable_ranges = [ 

4237 (range_, qvalue, position_in_header) 

4238 for range_, (qvalue, position_in_header) 

4239 in acceptable_ranges.items() 

4240 ] 

4241 # Sort acceptable_ranges by position_in_header, ascending order 

4242 acceptable_ranges.sort(key=lambda tuple_: tuple_[2]) 

4243 # Sort acceptable_ranges by qvalue, descending order 

4244 acceptable_ranges.sort(key=lambda tuple_: tuple_[1], reverse=True) 

4245 # Sort guaranteed to be stable with Python >= 2.2, so position in 

4246 # header is tiebreaker when two ranges have the same qvalue 

4247 

4248 def match(tag, range_): 

4249 # RFC 4647, section 2.1: 'A language range matches a particular 

4250 # language tag if, in a case-insensitive comparison, it exactly 

4251 # equals the tag, or if it exactly equals a prefix of the tag such 

4252 # that the first character following the prefix is "-".' 

4253 return (tag == range_) or tag.startswith(range_ + '-') 

4254 # We can assume here that the language tags are valid tags, so we 

4255 # do not have to worry about them being malformed and ending with 

4256 # '-'. 

4257 

4258 filtered_tags = [] 

4259 for index, tag in enumerate(lowercased_tags): 

4260 # If tag matches a non-* range with q=0, it is filtered out 

4261 if any(( 

4262 match(tag=tag, range_=range_) 

4263 for range_ in not_acceptable_ranges 

4264 )): 

4265 continue 

4266 

4267 matched_range_qvalue = None 

4268 for range_, qvalue, position_in_header in acceptable_ranges: 

4269 # acceptable_ranges is in descending order of qvalue, and tied 

4270 # ranges are in ascending order of position_in_header, so the 

4271 # first range_ that matches the tag is the best match 

4272 if match(tag=tag, range_=range_): 

4273 matched_range_qvalue = qvalue 

4274 matched_range_position = position_in_header 

4275 break 

4276 else: 

4277 if asterisk_qvalue: 

4278 # From RFC 4647, section 3.3.1: '...HTTP/1.1 [RFC2616] 

4279 # specifies that the range "*" matches only languages not 

4280 # matched by any other range within an "Accept-Language" 

4281 # header.' (Though RFC 2616 is obsolete, and there is no 

4282 # mention of the meaning of "*" in RFC 7231, as the 

4283 # ``language-range`` syntax rule in RFC 7231 section 5.3.1 

4284 # directs us to RFC 4647, we can only assume that the 

4285 # meaning of "*" in the Accept-Language header remains the 

4286 # same). 

4287 matched_range_qvalue = asterisk_qvalue 

4288 matched_range_position = asterisk_position 

4289 if matched_range_qvalue is not None: # if there was a match 

4290 filtered_tags.append(( 

4291 language_tags[index], matched_range_qvalue, 

4292 matched_range_position 

4293 )) 

4294 

4295 # sort by matched_range_position, ascending 

4296 filtered_tags.sort(key=lambda tuple_: tuple_[2]) 

4297 # When qvalues are tied, matched range position in the header is the 

4298 # tiebreaker. 

4299 

4300 # sort by qvalue, descending 

4301 filtered_tags.sort(key=lambda tuple_: tuple_[1], reverse=True) 

4302 

4303 return [(item[0], item[1]) for item in filtered_tags] 

4304 # (tag, qvalue), dropping the matched_range_position 

4305 

4306 # We return a list of tuples with qvalues, instead of just a set or 

4307 # a list of language tags, because 

4308 # RFC 4647 section 3.3: "If the language priority list contains more 

4309 # than one range, the content returned is typically ordered in 

4310 # descending level of preference, but it MAY be unordered, according to 

4311 # the needs of the application or protocol." 

4312 # We return the filtered tags in order of preference, each paired with 

4313 # the qvalue of the range that was their best match, as the ordering 

4314 # and the qvalues may well be needed in some applications, and a simple 

4315 # set or list of language tags can always be easily obtained from the 

4316 # returned list if the qvalues are not required. One use for qvalues, 

4317 # for example, would be to indicate that two tags are equally preferred 

4318 # (same qvalue), which we would not be able to do easily with a set or 

4319 # a list without e.g. making a member of the set or list a sequence. 

4320 

4321 def best_match(self, offers, default_match=None): 

4322 """ 

4323 Return the best match from the sequence of language tag `offers`. 

4324 

4325 .. warning:: 

4326 

4327 This is currently maintained for backward compatibility, and will be 

4328 deprecated in the future. 

4329 

4330 :meth:`AcceptLanguageValidHeader.best_match` uses its own algorithm 

4331 (one not specified in :rfc:`RFC 7231 <7231>`) to determine what is a 

4332 best match. The algorithm has many issues, and does not conform to 

4333 :rfc:`RFC 7231 <7231>`. 

4334 

4335 :meth:`AcceptLanguageValidHeader.lookup` is a possible alternative 

4336 for finding a best match -- it conforms to, and is suggested as a 

4337 matching scheme for the ``Accept-Language`` header in, :rfc:`RFC 

4338 7231, section 5.3.5 <7231#section-5.3.5>` -- but please be aware 

4339 that there are differences in how it determines what is a best 

4340 match. If that is not suitable for the needs of your application, 

4341 you may need to write your own matching using 

4342 :attr:`AcceptLanguageValidHeader.parsed`. 

4343 

4344 Each language tag in `offers` is checked against each non-0 range in 

4345 the header. If the two are a match according to WebOb's old criterion 

4346 for a match, the quality value of the match is the qvalue of the 

4347 language range from the header multiplied by the server quality value 

4348 of the offer (if the server quality value is not supplied, it is 1). 

4349 

4350 The offer in the match with the highest quality value is the best 

4351 match. If there is more than one match with the highest qvalue, the 

4352 match where the language range has a lower number of '*'s is the best 

4353 match. If the two have the same number of '*'s, the one that shows up 

4354 first in `offers` is the best match. 

4355 

4356 :param offers: (iterable) 

4357 

4358 | Each item in the iterable may be a ``str`` language 

4359 tag, or a (language tag, server quality value) 

4360 ``tuple`` or ``list``. (The two may be mixed in the 

4361 iterable.) 

4362 

4363 :param default_match: (optional, any type) the value to be returned if 

4364 there is no match 

4365 

4366 :return: (``str``, or the type of `default_match`) 

4367 

4368 | The language tag that is the best match. If there is no 

4369 match, the value of `default_match` is returned. 

4370 

4371 

4372 **Issues**: 

4373 

4374 - Incorrect tiebreaking when quality values of two matches are the same 

4375 (https://github.com/Pylons/webob/issues/256):: 

4376 

4377 >>> header = AcceptLanguageValidHeader( 

4378 ... header_value='en-gb;q=1, en;q=0.8' 

4379 ... ) 

4380 >>> header.best_match(offers=['en', 'en-GB']) 

4381 'en' 

4382 >>> header.best_match(offers=['en-GB', 'en']) 

4383 'en-GB' 

4384 

4385 >>> header = AcceptLanguageValidHeader(header_value='en-gb, en') 

4386 >>> header.best_match(offers=['en', 'en-gb']) 

4387 'en' 

4388 >>> header.best_match(offers=['en-gb', 'en']) 

4389 'en-gb' 

4390 

4391 - Incorrect handling of ``q=0``:: 

4392 

4393 >>> header = AcceptLanguageValidHeader(header_value='en;q=0, *') 

4394 >>> header.best_match(offers=['en']) 

4395 'en' 

4396 

4397 >>> header = AcceptLanguageValidHeader(header_value='fr, en;q=0') 

4398 >>> header.best_match(offers=['en-gb'], default_match='en') 

4399 'en' 

4400 

4401 - Matching only takes into account the first subtag when matching a 

4402 range with more specific or less specific tags:: 

4403 

4404 >>> header = AcceptLanguageValidHeader(header_value='zh') 

4405 >>> header.best_match(offers=['zh-Hans-CN']) 

4406 'zh-Hans-CN' 

4407 >>> header = AcceptLanguageValidHeader(header_value='zh-Hans') 

4408 >>> header.best_match(offers=['zh-Hans-CN']) 

4409 >>> header.best_match(offers=['zh-Hans-CN']) is None 

4410 True 

4411 

4412 >>> header = AcceptLanguageValidHeader(header_value='zh-Hans-CN') 

4413 >>> header.best_match(offers=['zh']) 

4414 'zh' 

4415 >>> header.best_match(offers=['zh-Hans']) 

4416 >>> header.best_match(offers=['zh-Hans']) is None 

4417 True 

4418 

4419 """ 

4420 warnings.warn( 

4421 'The behavior of AcceptLanguageValidHeader.best_match is ' 

4422 'currently being maintained for backward compatibility, but it ' 

4423 'will be deprecated in the future as it does not conform to the ' 

4424 'RFC.', 

4425 DeprecationWarning, 

4426 ) 

4427 best_quality = -1 

4428 best_offer = default_match 

4429 matched_by = '*/*' 

4430 # [We can see that this was written for the ``Accept`` header and not 

4431 # the ``Accept-Language`` header, as there are no '/'s in a valid 

4432 # ``Accept-Language`` header.] 

4433 for offer in offers: 

4434 if isinstance(offer, (tuple, list)): 

4435 offer, server_quality = offer 

4436 else: 

4437 server_quality = 1 

4438 for mask, quality in self._parsed_nonzero: 

4439 possible_quality = server_quality * quality 

4440 if possible_quality < best_quality: 

4441 continue 

4442 elif possible_quality == best_quality: 

4443 # 'text/plain' overrides 'message/*' overrides '*/*' 

4444 # (if all match w/ the same q=) 

4445 if matched_by.count('*') <= mask.count('*'): 

4446 continue 

4447 # [This tiebreaking was written for the `Accept` header. A 

4448 # basic language range in a valid ``Accept-Language`` 

4449 # header can only be either '*' or a range with no '*' in 

4450 # it. This happens to work here, but is not sufficient as a 

4451 # tiebreaker. 

4452 # 

4453 # A best match here, given this algorithm uses 

4454 # self._old_match() which matches both more *and* less 

4455 # specific tags, should be the match where the absolute 

4456 # value of the difference between the subtag counts of 

4457 # `mask` and `offer` is the lowest.] 

4458 if self._old_match(mask, offer): 

4459 best_quality = possible_quality 

4460 best_offer = offer 

4461 matched_by = mask 

4462 return best_offer 

4463 

4464 def lookup( 

4465 self, language_tags, default_range=None, default_tag=None, 

4466 default=None, 

4467 ): 

4468 """ 

4469 Return the language tag that best matches the header, using Lookup. 

4470 

4471 This is an implementation of the Lookup matching scheme, 

4472 suggested as a matching scheme for the ``Accept-Language`` header in 

4473 :rfc:`RFC 7231, section 5.3.5 <7231#section-5.3.5>`, and described in 

4474 :rfc:`RFC 4647, section 3.4 <4647#section-3.4>`. 

4475 

4476 Each language range in the header is considered in turn, by descending 

4477 order of qvalue; where qvalues are tied, ranges are considered from 

4478 left to right. 

4479 

4480 Each language range in the header represents the most specific tag that 

4481 is an acceptable match: Lookup progressively truncates subtags from the 

4482 end of the range until a matching language tag is found. An example is 

4483 given in :rfc:`RFC 4647, section 3.4 <4647#section-3.4>`, under 

4484 "Example of a Lookup Fallback Pattern": 

4485 

4486 :: 

4487 

4488 Range to match: zh-Hant-CN-x-private1-private2 

4489 1. zh-Hant-CN-x-private1-private2 

4490 2. zh-Hant-CN-x-private1 

4491 3. zh-Hant-CN 

4492 4. zh-Hant 

4493 5. zh 

4494 6. (default) 

4495 

4496 :param language_tags: (``iterable``) language tags 

4497 

4498 :param default_range: (optional, ``None`` or ``str``) 

4499 

4500 | If Lookup finds no match using the ranges in 

4501 the header, and this argument is not None, 

4502 Lookup will next attempt to match the range in 

4503 this argument, using the same subtag 

4504 truncation. 

4505 

4506 | `default_range` cannot be '*', as '*' is 

4507 skipped in Lookup. See :ref:`note 

4508 <acceptparse-lookup-asterisk-note>`. 

4509 

4510 | This parameter corresponds to the functionality 

4511 described in :rfc:`RFC 4647, section 3.4.1 

4512 <4647#section-3.4.1>`, in the paragraph 

4513 starting with "One common way to provide for a 

4514 default is to allow a specific language range 

4515 to be set as the default..." 

4516 

4517 :param default_tag: (optional, ``None`` or ``str``) 

4518 

4519 | At least one of `default_tag` or `default` must 

4520 be supplied as an argument to the method, to 

4521 define the defaulting behaviour. 

4522 

4523 | If Lookup finds no match using the ranges in the 

4524 header and `default_range`, this argument is not 

4525 ``None``, and it does not match any range in the 

4526 header with ``q=0`` (exactly, with no subtag 

4527 truncation), then this value is returned. 

4528 

4529 | This parameter corresponds to "return a 

4530 particular language tag designated for the 

4531 operation", one of the examples of "defaulting 

4532 behavior" described in :rfc:`RFC 4647, section 

4533 3.4.1 <4647#section-3.4.1>`. 

4534 

4535 :param default: (optional, ``None`` or any type, including a callable) 

4536 

4537 | At least one of `default_tag` or `default` must be 

4538 supplied as an argument to the method, to define the 

4539 defaulting behaviour. 

4540 

4541 | If Lookup finds no match using the ranges in the 

4542 header and `default_range`, and `default_tag` is 

4543 ``None`` or not acceptable because it matches a 

4544 ``q=0`` range in the header, then Lookup will next 

4545 examine the `default` argument. 

4546 

4547 | If `default` is a callable, it will be called, and 

4548 the callable's return value will be returned. 

4549 

4550 | If `default` is not a callable, the value itself will 

4551 be returned. 

4552 

4553 | The difference between supplying a ``str`` to 

4554 `default_tag` and `default` is that `default_tag` is 

4555 checked against ``q=0`` ranges in the header to see 

4556 if it matches one of the ranges specified as not 

4557 acceptable, whereas a ``str`` for the `default` 

4558 argument is simply returned. 

4559 

4560 | This parameter corresponds to the "defaulting 

4561 behavior" described in :rfc:`RFC 4647, section 3.4.1 

4562 <4647#section-3.4.1>` 

4563 

4564 :return: (``str``, ``None``, or any type) 

4565 

4566 | The best match according to the Lookup matching scheme, or a 

4567 return value from one of the default arguments. 

4568 

4569 **Notes**: 

4570 

4571 .. _acceptparse-lookup-asterisk-note: 

4572 

4573 - Lookup's behaviour with '*' language ranges in the header may be 

4574 surprising. From :rfc:`RFC 4647, section 3.4 <4647#section-3.4>`: 

4575 

4576 In the lookup scheme, this range does not convey enough 

4577 information by itself to determine which language tag is most 

4578 appropriate, since it matches everything. If the language range 

4579 "*" is followed by other language ranges, it is skipped. If the 

4580 language range "*" is the only one in the language priority list 

4581 or if no other language range follows, the default value is 

4582 computed and returned. 

4583 

4584 So 

4585 

4586 :: 

4587 

4588 >>> header = AcceptLanguageValidHeader('de, zh, *') 

4589 >>> header.lookup(language_tags=['ja', 'en'], default='default') 

4590 'default' 

4591 

4592 - Any tags in `language_tags` and `default_tag` and any tag matched 

4593 during the subtag truncation search for `default_range`, that are an 

4594 exact match for a non-``*`` range with ``q=0`` in the header, are 

4595 considered not acceptable and ruled out. 

4596 

4597 - If there is a ``*;q=0`` in the header, then `default_range` and 

4598 `default_tag` have no effect, as ``*;q=0`` means that all languages 

4599 not already matched by other ranges within the header are 

4600 unacceptable. 

4601 """ 

4602 if default_tag is None and default is None: 

4603 raise TypeError( 

4604 '`default_tag` and `default` arguments cannot both be None.' 

4605 ) 

4606 

4607 # We need separate `default_tag` and `default` arguments because if we 

4608 # only had the `default` argument, there would be no way to tell 

4609 # whether a str is a language tag (in which case we have to check 

4610 # whether it has been specified as not acceptable with a q=0 range in 

4611 # the header) or not (in which case we can just return the value). 

4612 

4613 if default_range == '*': 

4614 raise ValueError('default_range cannot be *.') 

4615 

4616 parsed = list(self.parsed) 

4617 

4618 tags = language_tags 

4619 not_acceptable_ranges = [] 

4620 acceptable_ranges = [] 

4621 

4622 asterisk_non0_found = False 

4623 # Whether there is a '*' range in the header with q={not 0} 

4624 

4625 asterisk_q0_found = False 

4626 # Whether there is a '*' range in the header with q=0 

4627 # While '*' is skipped in Lookup because it "does not convey enough 

4628 # information by itself to determine which language tag is most 

4629 # appropriate" (RFC 4647, section 3.4), '*;q=0' is clear in meaning: 

4630 # languages not matched by any other range within the header are not 

4631 # acceptable. 

4632 

4633 for range_, qvalue in parsed: 

4634 if qvalue == 0.0: 

4635 if range_ == '*': # *;q=0 

4636 asterisk_q0_found = True 

4637 else: # {non-* range};q=0 

4638 not_acceptable_ranges.append(range_.lower()) 

4639 elif not asterisk_q0_found and range_ == '*': # *;q={not 0} 

4640 asterisk_non0_found = True 

4641 # if asterisk_q0_found, then it does not matter whether 

4642 # asterisk_non0_found 

4643 else: # {non-* range};q={not 0} 

4644 acceptable_ranges.append((range_, qvalue)) 

4645 # Sort acceptable_ranges by qvalue, descending order 

4646 acceptable_ranges.sort(key=lambda tuple_: tuple_[1], reverse=True) 

4647 # Sort guaranteed to be stable with Python >= 2.2, so position in 

4648 # header is tiebreaker when two ranges have the same qvalue 

4649 

4650 acceptable_ranges = [tuple_[0] for tuple_ in acceptable_ranges] 

4651 lowered_tags = [tag.lower() for tag in tags] 

4652 

4653 def best_match(range_): 

4654 subtags = range_.split('-') 

4655 while True: 

4656 for index, tag in enumerate(lowered_tags): 

4657 if tag in not_acceptable_ranges: 

4658 continue 

4659 # We think a non-'*' range with q=0 represents only 

4660 # itself as a tag, and there should be no falling back 

4661 # with subtag truncation. For example, with 

4662 # 'en-gb;q=0', it should not mean 'en;q=0': the client 

4663 # is unlikely to expect that specifying 'en-gb' as not 

4664 # acceptable would mean that 'en' is also not 

4665 # acceptable. There is no guidance on this at all in 

4666 # the RFCs, so it is left to us to decide how it should 

4667 # work. 

4668 

4669 if tag == range_: 

4670 return tags[index] # return the pre-lowered tag 

4671 

4672 try: 

4673 subtag_before_this = subtags[-2] 

4674 except IndexError: # len(subtags) == 1 

4675 break 

4676 # len(subtags) >= 2 

4677 if len(subtag_before_this) == 1 and ( 

4678 subtag_before_this.isdigit() or 

4679 subtag_before_this.isalpha() 

4680 ): # if subtag_before_this is a single-letter or -digit subtag 

4681 subtags.pop(-1) # pop twice instead of once 

4682 subtags.pop(-1) 

4683 range_ = '-'.join(subtags) 

4684 

4685 for range_ in acceptable_ranges: 

4686 match = best_match(range_=range_.lower()) 

4687 if match is not None: 

4688 return match 

4689 

4690 if not asterisk_q0_found: 

4691 if default_range is not None: 

4692 lowered_default_range = default_range.lower() 

4693 match = best_match(range_=lowered_default_range) 

4694 if match is not None: 

4695 return match 

4696 

4697 if default_tag is not None: 

4698 lowered_default_tag = default_tag.lower() 

4699 if lowered_default_tag not in not_acceptable_ranges: 

4700 return default_tag 

4701 

4702 try: 

4703 return default() 

4704 except TypeError: # default is not a callable 

4705 return default 

4706 

4707 def quality(self, offer): 

4708 """ 

4709 Return quality value of given offer, or ``None`` if there is no match. 

4710 

4711 .. warning:: 

4712 

4713 This is currently maintained for backward compatibility, and will be 

4714 deprecated in the future. 

4715 

4716 :meth:`AcceptLanguageValidHeader.quality` uses its own algorithm 

4717 (one not specified in :rfc:`RFC 7231 <7231>`) to determine what is a 

4718 best match. The algorithm has many issues, and does not conform to 

4719 :rfc:`RFC 7231 <7231>`. 

4720 

4721 What should be considered a match depends on the needs of your 

4722 application (for example, should a language range in the header 

4723 match a more specific language tag offer, or a less specific tag 

4724 offer?) :rfc:`RFC 7231, section 5.3.5 <7231#section-5.3.5>` suggests 

4725 three matching schemes from :rfc:`RFC 4647 <4647>`, two of which 

4726 WebOb supports with 

4727 :meth:`AcceptLanguageValidHeader.basic_filtering` and 

4728 :meth:`AcceptLanguageValidHeader.lookup` (we interpret the RFC to 

4729 mean that Extended Filtering cannot apply for the 

4730 ``Accept-Language`` header, as the header only accepts basic 

4731 language ranges.) :meth:`AcceptLanguageValidHeader.basic_filtering` 

4732 returns quality values with the matched language tags. 

4733 :meth:`AcceptLanguageValidHeader.lookup` returns a language tag 

4734 without the quality value, but the quality value is less likely to 

4735 be useful when we are looking for a best match. 

4736 

4737 If these are not suitable or sufficient for the needs of your 

4738 application, you may need to write your own matching using 

4739 :attr:`AcceptLanguageValidHeader.parsed`. 

4740 

4741 :param offer: (``str``) language tag offer 

4742 :return: (``float`` or ``None``) 

4743 

4744 | The highest quality value from the language range(s) that 

4745 match the `offer`, or ``None`` if there is no match. 

4746 

4747 

4748 **Issues**: 

4749 

4750 - Incorrect handling of ``q=0`` and ``*``:: 

4751 

4752 >>> header = AcceptLanguageValidHeader(header_value='en;q=0, *') 

4753 >>> header.quality(offer='en') 

4754 1.0 

4755 

4756 - Matching only takes into account the first subtag when matching a 

4757 range with more specific or less specific tags:: 

4758 

4759 >>> header = AcceptLanguageValidHeader(header_value='zh') 

4760 >>> header.quality(offer='zh-Hans-CN') 

4761 1.0 

4762 >>> header = AcceptLanguageValidHeader(header_value='zh-Hans') 

4763 >>> header.quality(offer='zh-Hans-CN') 

4764 >>> header.quality(offer='zh-Hans-CN') is None 

4765 True 

4766 

4767 >>> header = AcceptLanguageValidHeader(header_value='zh-Hans-CN') 

4768 >>> header.quality(offer='zh') 

4769 1.0 

4770 >>> header.quality(offer='zh-Hans') 

4771 >>> header.quality(offer='zh-Hans') is None 

4772 True 

4773 

4774 """ 

4775 warnings.warn( 

4776 'The behavior of AcceptLanguageValidHeader.quality is' 

4777 'currently being maintained for backward compatibility, but it ' 

4778 'will be deprecated in the future as it does not conform to the ' 

4779 'RFC.', 

4780 DeprecationWarning, 

4781 ) 

4782 bestq = 0 

4783 for mask, q in self.parsed: 

4784 if self._old_match(mask, offer): 

4785 bestq = max(bestq, q) 

4786 return bestq or None 

4787 

4788 

4789class _AcceptLanguageInvalidOrNoHeader(AcceptLanguage): 

4790 """ 

4791 Represent when an ``Accept-Language`` header is invalid or not in request. 

4792 

4793 This is the base class for the behaviour that 

4794 :class:`.AcceptLanguageInvalidHeader` and :class:`.AcceptLanguageNoHeader` 

4795 have in common. 

4796 

4797 :rfc:`7231` does not provide any guidance on what should happen if the 

4798 ``Accept-Language`` header has an invalid value. This implementation 

4799 disregards the header when the header is invalid, so 

4800 :class:`.AcceptLanguageInvalidHeader` and :class:`.AcceptLanguageNoHeader` 

4801 have much behaviour in common. 

4802 """ 

4803 

4804 def __nonzero__(self): 

4805 """ 

4806 Return whether ``self`` represents a valid ``Accept-Language`` header. 

4807 

4808 Return ``True`` if ``self`` represents a valid header, and ``False`` if 

4809 it represents an invalid header, or the header not being in the 

4810 request. 

4811 

4812 For this class, it always returns ``False``. 

4813 """ 

4814 return False 

4815 __bool__ = __nonzero__ # Python 3 

4816 

4817 def __contains__(self, offer): 

4818 """ 

4819 Return ``bool`` indicating whether `offer` is acceptable. 

4820 

4821 .. warning:: 

4822 

4823 The behavior of ``.__contains__`` for the ``AcceptLanguage`` classes 

4824 is currently being maintained for backward compatibility, but it 

4825 will change in the future to better conform to the RFC. 

4826 

4827 :param offer: (``str``) language tag offer 

4828 :return: (``bool``) Whether ``offer`` is acceptable according to the 

4829 header. 

4830 

4831 For this class, either there is no ``Accept-Language`` header in the 

4832 request, or the header is invalid, so any language tag is acceptable, 

4833 and this always returns ``True``. 

4834 """ 

4835 warnings.warn( 

4836 'The behavior of .__contains__ for the AcceptLanguage classes is ' 

4837 'currently being maintained for backward compatibility, but it ' 

4838 'will change in the future to better conform to the RFC.', 

4839 DeprecationWarning, 

4840 ) 

4841 return True 

4842 

4843 def __iter__(self): 

4844 """ 

4845 Return all the ranges with non-0 qvalues, in order of preference. 

4846 

4847 .. warning:: 

4848 

4849 The behavior of this method is currently maintained for backward 

4850 compatibility, but will change in the future. 

4851 

4852 :return: iterator of all the language ranges in the header with non-0 

4853 qvalues, in descending order of qvalue. If two ranges have the 

4854 same qvalue, they are returned in the order of their positions 

4855 in the header, from left to right. 

4856 

4857 For this class, either there is no ``Accept-Language`` header in the 

4858 request, or the header is invalid, so there are no language ranges, and 

4859 this always returns an empty iterator. 

4860 """ 

4861 warnings.warn( 

4862 'The behavior of AcceptLanguageValidHeader.__iter__ is currently ' 

4863 'maintained for backward compatibility, but will change in the ' 

4864 'future.', 

4865 DeprecationWarning, 

4866 ) 

4867 return iter(()) 

4868 

4869 def basic_filtering(self, language_tags): 

4870 """ 

4871 Return the tags that match the header, using Basic Filtering. 

4872 

4873 :param language_tags: (``iterable``) language tags 

4874 :return: A list of tuples of the form (language tag, qvalue), in 

4875 descending order of preference. 

4876 

4877 When the header is invalid and when the header is not in the request, 

4878 there are no matches, so this method always returns an empty list. 

4879 """ 

4880 return [] 

4881 

4882 def best_match(self, offers, default_match=None): 

4883 """ 

4884 Return the best match from the sequence of language tag `offers`. 

4885 

4886 This is the ``.best_match()`` method for when the header is invalid or 

4887 not found in the request, corresponding to 

4888 :meth:`AcceptLanguageValidHeader.best_match`. 

4889 

4890 .. warning:: 

4891 

4892 This is currently maintained for backward compatibility, and will be 

4893 deprecated in the future (see the documentation for 

4894 :meth:`AcceptLanguageValidHeader.best_match`). 

4895 

4896 When the header is invalid, or there is no `Accept-Language` header in 

4897 the request, any of the language tags in `offers` are considered 

4898 acceptable, so the best match is the tag in `offers` with the highest 

4899 server quality value (if the server quality value is not supplied, it 

4900 is 1). 

4901 

4902 If more than one language tags in `offers` have the same highest server 

4903 quality value, then the one that shows up first in `offers` is the best 

4904 match. 

4905 

4906 :param offers: (iterable) 

4907 

4908 | Each item in the iterable may be a ``str`` language 

4909 tag, or a (language tag, server quality value) 

4910 ``tuple`` or ``list``. (The two may be mixed in the 

4911 iterable.) 

4912 

4913 :param default_match: (optional, any type) the value to be returned if 

4914 `offers` is empty. 

4915 

4916 :return: (``str``, or the type of `default_match`) 

4917 

4918 | The language tag that has the highest server quality value. 

4919 If `offers` is empty, the value of `default_match` is 

4920 returned. 

4921 """ 

4922 warnings.warn( 

4923 'The behavior of .best_match for the AcceptLanguage classes is ' 

4924 'currently being maintained for backward compatibility, but the ' 

4925 'method will be deprecated in the future, as its behavior is not ' 

4926 'specified in (and currently does not conform to) RFC 7231.', 

4927 DeprecationWarning, 

4928 ) 

4929 best_quality = -1 

4930 best_offer = default_match 

4931 for offer in offers: 

4932 if isinstance(offer, (list, tuple)): 

4933 offer, quality = offer 

4934 else: 

4935 quality = 1 

4936 if quality > best_quality: 

4937 best_offer = offer 

4938 best_quality = quality 

4939 return best_offer 

4940 

4941 def lookup( 

4942 self, language_tags=None, default_range=None, default_tag=None, 

4943 default=None, 

4944 ): 

4945 """ 

4946 Return the language tag that best matches the header, using Lookup. 

4947 

4948 When the header is invalid, or there is no ``Accept-Language`` header 

4949 in the request, all language tags are considered acceptable, so it is 

4950 as if the header is '*'. As specified for the Lookup matching scheme in 

4951 :rfc:`RFC 4647, section 3.4 <4647#section-3.4>`, when the header is 

4952 '*', the default value is to be computed and returned. So this method 

4953 will ignore the `language_tags` and `default_range` arguments, and 

4954 proceed to `default_tag`, then `default`. 

4955 

4956 :param language_tags: (optional, any type) 

4957 

4958 | This argument is ignored, and is only used as a 

4959 placeholder so that the method signature 

4960 corresponds to that of 

4961 :meth:`AcceptLanguageValidHeader.lookup`. 

4962 

4963 :param default_range: (optional, any type) 

4964 

4965 | This argument is ignored, and is only used as a 

4966 placeholder so that the method signature 

4967 corresponds to that of 

4968 :meth:`AcceptLanguageValidHeader.lookup`. 

4969 

4970 :param default_tag: (optional, ``None`` or ``str``) 

4971 

4972 | At least one of `default_tag` or `default` must 

4973 be supplied as an argument to the method, to 

4974 define the defaulting behaviour. 

4975 

4976 | If this argument is not ``None``, then it is 

4977 returned. 

4978 

4979 | This parameter corresponds to "return a 

4980 particular language tag designated for the 

4981 operation", one of the examples of "defaulting 

4982 behavior" described in :rfc:`RFC 4647, section 

4983 3.4.1 <4647#section-3.4.1>`. 

4984 

4985 :param default: (optional, ``None`` or any type, including a callable) 

4986 

4987 | At least one of `default_tag` or `default` must be 

4988 supplied as an argument to the method, to define the 

4989 defaulting behaviour. 

4990 

4991 | If `default_tag` is ``None``, then Lookup will next 

4992 examine the `default` argument. 

4993 

4994 | If `default` is a callable, it will be called, and 

4995 the callable's return value will be returned. 

4996 

4997 | If `default` is not a callable, the value itself will 

4998 be returned. 

4999 

5000 | This parameter corresponds to the "defaulting 

5001 behavior" described in :rfc:`RFC 4647, section 3.4.1 

5002 <4647#section-3.4.1>` 

5003 

5004 :return: (``str``, or any type) 

5005 

5006 | the return value from `default_tag` or `default`. 

5007 """ 

5008 if default_tag is None and default is None: 

5009 raise TypeError( 

5010 '`default_tag` and `default` arguments cannot both be None.' 

5011 ) 

5012 

5013 if default_tag is not None: 

5014 return default_tag 

5015 

5016 try: 

5017 return default() 

5018 except TypeError: # default is not a callable 

5019 return default 

5020 

5021 def quality(self, offer): 

5022 """ 

5023 Return quality value of given offer, or ``None`` if there is no match. 

5024 

5025 This is the ``.quality()`` method for when the header is invalid or not 

5026 found in the request, corresponding to 

5027 :meth:`AcceptLanguageValidHeader.quality`. 

5028 

5029 .. warning:: 

5030 

5031 This is currently maintained for backward compatibility, and will be 

5032 deprecated in the future (see the documentation for 

5033 :meth:`AcceptLanguageValidHeader.quality`). 

5034 

5035 :param offer: (``str``) language tag offer 

5036 :return: (``float``) ``1.0``. 

5037 

5038 When the ``Accept-Language`` header is invalid or not in the request, 

5039 all offers are equally acceptable, so 1.0 is always returned. 

5040 """ 

5041 warnings.warn( 

5042 'The behavior of .quality for the AcceptLanguage classes is ' 

5043 'currently being maintained for backward compatibility, but the ' 

5044 'method will be deprecated in the future, as its behavior is not ' 

5045 'specified in (and currently does not conform to) RFC 7231.', 

5046 DeprecationWarning, 

5047 ) 

5048 return 1.0 

5049 

5050 

5051class AcceptLanguageNoHeader(_AcceptLanguageInvalidOrNoHeader): 

5052 """ 

5053 Represent when there is no ``Accept-Language`` header in the request. 

5054 

5055 This object should not be modified. To add to the header, we can use the 

5056 addition operators (``+`` and ``+=``), which return a new object (see the 

5057 docstring for :meth:`AcceptLanguageNoHeader.__add__`). 

5058 """ 

5059 

5060 def __init__(self): 

5061 """ 

5062 Create an :class:`AcceptLanguageNoHeader` instance. 

5063 """ 

5064 self._header_value = None 

5065 self._parsed = None 

5066 self._parsed_nonzero = None 

5067 

5068 def copy(self): 

5069 """ 

5070 Create a copy of the header object. 

5071 

5072 """ 

5073 return self.__class__() 

5074 

5075 @property 

5076 def header_value(self): 

5077 """ 

5078 (``str`` or ``None``) The header value. 

5079 

5080 As there is no header in the request, this is ``None``. 

5081 """ 

5082 return self._header_value 

5083 

5084 @property 

5085 def parsed(self): 

5086 """ 

5087 (``list`` or ``None``) Parsed form of the header. 

5088 

5089 As there is no header in the request, this is ``None``. 

5090 """ 

5091 return self._parsed 

5092 

5093 def __add__(self, other): 

5094 """ 

5095 Add to header, creating a new header object. 

5096 

5097 `other` can be: 

5098 

5099 * ``None`` 

5100 * a ``str`` 

5101 * a ``dict``, with language ranges as keys and qvalues as values 

5102 * a ``tuple`` or ``list``, of language range ``str``'s or of ``tuple`` 

5103 or ``list`` (language range, qvalue) pairs (``str``'s and pairs can be 

5104 mixed within the ``tuple`` or ``list``) 

5105 * an :class:`AcceptLanguageValidHeader`, 

5106 :class:`AcceptLanguageNoHeader`, or 

5107 :class:`AcceptLanguageInvalidHeader` instance 

5108 * object of any other type that returns a value for ``__str__`` 

5109 

5110 If `other` is a valid header value or an 

5111 :class:`AcceptLanguageValidHeader` instance, a new 

5112 :class:`AcceptLanguageValidHeader` instance with the valid header value 

5113 is returned. 

5114 

5115 If `other` is ``None``, an :class:`AcceptLanguageNoHeader` instance, an 

5116 invalid header value, or an :class:`AcceptLanguageInvalidHeader` 

5117 instance, a new :class:`AcceptLanguageNoHeader` instance is returned. 

5118 """ 

5119 if isinstance(other, AcceptLanguageValidHeader): 

5120 return AcceptLanguageValidHeader(header_value=other.header_value) 

5121 

5122 if isinstance( 

5123 other, (AcceptLanguageNoHeader, AcceptLanguageInvalidHeader) 

5124 ): 

5125 return self.__class__() 

5126 

5127 return self._add_instance_and_non_accept_language_type( 

5128 instance=self, other=other, 

5129 ) 

5130 

5131 def __radd__(self, other): 

5132 """ 

5133 Add to header, creating a new header object. 

5134 

5135 See the docstring for :meth:`AcceptLanguageNoHeader.__add__`. 

5136 """ 

5137 return self.__add__(other=other) 

5138 

5139 def __repr__(self): 

5140 return '<{}>'.format(self.__class__.__name__) 

5141 

5142 def __str__(self): 

5143 """Return the ``str`` ``'<no header in request>'``.""" 

5144 return '<no header in request>' 

5145 

5146 def _add_instance_and_non_accept_language_type(self, instance, other): 

5147 if not other: 

5148 return self.__class__() 

5149 

5150 other_header_value = self._python_value_to_header_str(value=other) 

5151 

5152 try: 

5153 return AcceptLanguageValidHeader(header_value=other_header_value) 

5154 except ValueError: # invalid header value 

5155 return self.__class__() 

5156 

5157 

5158class AcceptLanguageInvalidHeader(_AcceptLanguageInvalidOrNoHeader): 

5159 """ 

5160 Represent an invalid ``Accept-Language`` header. 

5161 

5162 An invalid header is one that does not conform to 

5163 :rfc:`7231#section-5.3.5`. As specified in the RFC, an empty header is an 

5164 invalid ``Accept-Language`` header. 

5165 

5166 :rfc:`7231` does not provide any guidance on what should happen if the 

5167 ``Accept-Language`` header has an invalid value. This implementation 

5168 disregards the header, and treats it as if there is no ``Accept-Language`` 

5169 header in the request. 

5170 

5171 This object should not be modified. To add to the header, we can use the 

5172 addition operators (``+`` and ``+=``), which return a new object (see the 

5173 docstring for :meth:`AcceptLanguageInvalidHeader.__add__`). 

5174 """ 

5175 

5176 def __init__(self, header_value): 

5177 """ 

5178 Create an :class:`AcceptLanguageInvalidHeader` instance. 

5179 """ 

5180 self._header_value = header_value 

5181 self._parsed = None 

5182 self._parsed_nonzero = None 

5183 

5184 def copy(self): 

5185 """ 

5186 Create a copy of the header object. 

5187 

5188 """ 

5189 return self.__class__(self._header_value) 

5190 

5191 @property 

5192 def header_value(self): 

5193 """(``str`` or ``None``) The header value.""" 

5194 return self._header_value 

5195 

5196 @property 

5197 def parsed(self): 

5198 """ 

5199 (``list`` or ``None``) Parsed form of the header. 

5200 

5201 As the header is invalid and cannot be parsed, this is ``None``. 

5202 """ 

5203 return self._parsed 

5204 

5205 def __add__(self, other): 

5206 """ 

5207 Add to header, creating a new header object. 

5208 

5209 `other` can be: 

5210 

5211 * ``None`` 

5212 * a ``str`` 

5213 * a ``dict``, with language ranges as keys and qvalues as values 

5214 * a ``tuple`` or ``list``, of language range ``str``'s or of ``tuple`` 

5215 or ``list`` (language range, qvalue) pairs (``str``'s and pairs can 

5216 be mixed within the ``tuple`` or ``list``) 

5217 * an :class:`AcceptLanguageValidHeader`, 

5218 :class:`AcceptLanguageNoHeader`, or 

5219 :class:`AcceptLanguageInvalidHeader` instance 

5220 * object of any other type that returns a value for ``__str__`` 

5221 

5222 If `other` is a valid header value or an 

5223 :class:`AcceptLanguageValidHeader` instance, a new 

5224 :class:`AcceptLanguageValidHeader` instance with the valid header value 

5225 is returned. 

5226 

5227 If `other` is ``None``, an :class:`AcceptLanguageNoHeader` instance, an 

5228 invalid header value, or an :class:`AcceptLanguageInvalidHeader` 

5229 instance, a new :class:`AcceptLanguageNoHeader` instance is returned. 

5230 """ 

5231 if isinstance(other, AcceptLanguageValidHeader): 

5232 return AcceptLanguageValidHeader(header_value=other.header_value) 

5233 

5234 if isinstance( 

5235 other, (AcceptLanguageNoHeader, AcceptLanguageInvalidHeader) 

5236 ): 

5237 return AcceptLanguageNoHeader() 

5238 

5239 return self._add_instance_and_non_accept_language_type( 

5240 instance=self, other=other, 

5241 ) 

5242 

5243 def __radd__(self, other): 

5244 """ 

5245 Add to header, creating a new header object. 

5246 

5247 See the docstring for :meth:`AcceptLanguageValidHeader.__add__`. 

5248 """ 

5249 return self._add_instance_and_non_accept_language_type( 

5250 instance=self, other=other, instance_on_the_right=True, 

5251 ) 

5252 

5253 def __repr__(self): 

5254 return '<{}>'.format(self.__class__.__name__) 

5255 # We do not display the header_value, as it is untrusted input. The 

5256 # header_value could always be easily obtained from the .header_value 

5257 # property. 

5258 

5259 def __str__(self): 

5260 """Return the ``str`` ``'<invalid header value>'``.""" 

5261 return '<invalid header value>' 

5262 

5263 def _add_instance_and_non_accept_language_type( 

5264 self, instance, other, instance_on_the_right=False, 

5265 ): 

5266 if not other: 

5267 return AcceptLanguageNoHeader() 

5268 

5269 other_header_value = self._python_value_to_header_str(value=other) 

5270 

5271 try: 

5272 return AcceptLanguageValidHeader(header_value=other_header_value) 

5273 except ValueError: # invalid header value 

5274 return AcceptLanguageNoHeader() 

5275 

5276 

5277def create_accept_language_header(header_value): 

5278 """ 

5279 Create an object representing the ``Accept-Language`` header in a request. 

5280 

5281 :param header_value: (``str``) header value 

5282 :return: If `header_value` is ``None``, an :class:`AcceptLanguageNoHeader` 

5283 instance. 

5284 

5285 | If `header_value` is a valid ``Accept-Language`` header, an 

5286 :class:`AcceptLanguageValidHeader` instance. 

5287 

5288 | If `header_value` is an invalid ``Accept-Language`` header, an 

5289 :class:`AcceptLanguageInvalidHeader` instance. 

5290 """ 

5291 if header_value is None: 

5292 return AcceptLanguageNoHeader() 

5293 if isinstance(header_value, AcceptLanguage): 

5294 return header_value.copy() 

5295 try: 

5296 return AcceptLanguageValidHeader(header_value=header_value) 

5297 except ValueError: 

5298 return AcceptLanguageInvalidHeader(header_value=header_value) 

5299 

5300 

5301def accept_language_property(): 

5302 doc = """ 

5303 Property representing the ``Accept-Language`` header. 

5304 

5305 (:rfc:`RFC 7231, section 5.3.5 <7231#section-5.3.5>`) 

5306 

5307 The header value in the request environ is parsed and a new object 

5308 representing the header is created every time we *get* the value of the 

5309 property. (*set* and *del* change the header value in the request 

5310 environ, and do not involve parsing.) 

5311 """ 

5312 

5313 ENVIRON_KEY = 'HTTP_ACCEPT_LANGUAGE' 

5314 

5315 def fget(request): 

5316 """Get an object representing the header in the request.""" 

5317 return create_accept_language_header( 

5318 header_value=request.environ.get(ENVIRON_KEY) 

5319 ) 

5320 

5321 def fset(request, value): 

5322 """ 

5323 Set the corresponding key in the request environ. 

5324 

5325 `value` can be: 

5326 

5327 * ``None`` 

5328 * a ``str`` 

5329 * a ``dict``, with language ranges as keys and qvalues as values 

5330 * a ``tuple`` or ``list``, of language range ``str``'s or of ``tuple`` 

5331 or ``list`` (language range, qvalue) pairs (``str``'s and pairs can 

5332 be mixed within the ``tuple`` or ``list``) 

5333 * an :class:`AcceptLanguageValidHeader`, 

5334 :class:`AcceptLanguageNoHeader`, or 

5335 :class:`AcceptLanguageInvalidHeader` instance 

5336 * object of any other type that returns a value for ``__str__`` 

5337 """ 

5338 if value is None or isinstance(value, AcceptLanguageNoHeader): 

5339 fdel(request=request) 

5340 else: 

5341 if isinstance( 

5342 value, (AcceptLanguageValidHeader, AcceptLanguageInvalidHeader) 

5343 ): 

5344 header_value = value.header_value 

5345 else: 

5346 header_value = AcceptLanguage._python_value_to_header_str( 

5347 value=value, 

5348 ) 

5349 request.environ[ENVIRON_KEY] = header_value 

5350 

5351 def fdel(request): 

5352 """Delete the corresponding key from the request environ.""" 

5353 try: 

5354 del request.environ[ENVIRON_KEY] 

5355 except KeyError: 

5356 pass 

5357 

5358 return property(fget, fset, fdel, textwrap.dedent(doc))