Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

298 lines (254 sloc) 8.998 kb
"""
Parses a variety of ``Accept-*`` headers.
These headers generally take the form of::
value1; q=0.5, value2; q=0
Where the ``q`` parameter is optional. In theory other parameters
exists, but this ignores them.
"""
import re
try:
sorted
except NameError:
from webob.compat import sorted
part_re = re.compile(
r',\s*([^\s;,\n]+)(?:[^,]*?;\s*q=([0-9.]*))?')
def parse_accept(value):
"""
Parses an ``Accept-*`` style header.
A list of ``[(value, quality), ...]`` is returned. ``quality``
will be 1 if it was not given.
"""
result = []
for match in part_re.finditer(','+value):
name = match.group(1)
if name == 'q':
continue
quality = match.group(2) or ''
if not quality:
quality = 1
else:
try:
quality = max(min(float(quality), 1), 0)
except ValueError:
quality = 1
result.append((name, quality))
return result
class Accept(object):
"""
Represents a generic ``Accept-*`` style header.
This object should not be modified. To add items you can use
``accept_obj + 'accept_thing'`` to get a new object
"""
def __init__(self, header_name, header_value):
self.header_name = header_name
self.header_value = header_value
self._parsed = parse_accept(header_value)
def __repr__(self):
return '<%s at 0x%x %s: %s>' % (
self.__class__.__name__,
abs(id(self)),
self.header_name, str(self))
def __str__(self):
result = []
for match, quality in self._parsed:
if quality != 1:
match = '%s;q=%0.1f' % (match, quality)
result.append(match)
return ', '.join(result)
# FIXME: should subtraction be allowed?
def __add__(self, other, reversed=False):
if isinstance(other, Accept):
other = other.header_value
if hasattr(other, 'items'):
other = sorted(other.items(), key=lambda item: -item[1])
if isinstance(other, (list, tuple)):
result = []
for item in other:
if isinstance(item, (list, tuple)):
name, quality = item
result.append('%s; q=%s' % (name, quality))
else:
result.append(item)
other = ', '.join(result)
other = str(other)
my_value = self.header_value
if reversed:
other, my_value = my_value, other
if not other:
new_value = my_value
elif not my_value:
new_value = other
else:
new_value = my_value + ', ' + other
return self.__class__(self.header_name, new_value)
def __radd__(self, other):
return self.__add__(other, True)
def __contains__(self, match):
"""
Returns true if the given object is listed in the accepted
types.
"""
for item, quality in self._parsed:
if self._match(item, match):
return True
def quality(self, match):
"""
Return the quality of the given match. Returns None if there
is no match (not 0).
"""
for item, quality in self._parsed:
if self._match(item, match):
return quality
return None
def first_match(self, matches):
"""
Returns the first match in the sequences of matches that is
allowed. Ignores quality. Returns the first item if nothing
else matches; or if you include None at the end of the match
list then that will be returned.
"""
if not matches:
raise ValueError(
"You must pass in a non-empty list")
for match in matches:
for item, quality in self._parsed:
if self._match(item, match):
return match
if match is None:
return None
return matches[0]
def best_match(self, matches, default_match=None):
"""
Returns the best match in the sequence of matches.
The sequence can be a simple sequence, or you can have
``(match, server_quality)`` items in the sequence. If you
have these tuples then the client quality is multiplied by the
server_quality to get a total.
default_match (default None) is returned if there is no intersection.
"""
best_quality = -1
best_match = default_match
for match_item in matches:
if isinstance(match_item, (tuple, list)):
match, server_quality = match_item
else:
match = match_item
server_quality = 1
for item, quality in self._parsed:
possible_quality = server_quality * quality
if possible_quality < best_quality:
continue
if self._match(item, match):
best_quality = possible_quality
best_match = match
return best_match
def best_matches(self, fallback=None):
"""
Return all the matches in order of quality, with fallback (if
given) at the end.
"""
items = [
i for i, q in sorted(self._parsed, key=lambda iq: -iq[1])]
if fallback:
for index, item in enumerate(items):
if self._match(item, fallback):
items[index+1:] = []
break
else:
items.append(fallback)
return items
def _match(self, item, match):
return item.lower() == match.lower() or item == '*'
class NilAccept(object):
"""
Represents an Accept header with no value.
"""
MasterClass = Accept
def __init__(self, header_name):
self.header_name = header_name
def __repr__(self):
return '<%s for %s: %s>' % (
self.__class__.__name__, self.header_name, self.MasterClass)
def __str__(self):
return ''
def __add__(self, item):
if isinstance(item, self.MasterClass):
return item
else:
return self.MasterClass(self.header_name, '') + item
def __radd__(self, item):
if isinstance(item, self.MasterClass):
return item
else:
return item + self.MasterClass(self.header_name, '')
def __contains__(self, item):
return True
def quality(self, match, default_quality=1):
return 0
def first_match(self, matches):
return matches[0]
def best_match(self, matches, default_match=None):
best_quality = -1
best_match = default_match
for match_item in matches:
if isinstance(match_item, (list, tuple)):
match, quality = match_item
else:
match = match_item
quality = 1
if quality > best_quality:
best_match = match
best_quality = quality
return best_match
def best_matches(self, fallback=None):
if fallback:
return [fallback]
else:
return []
class NoAccept(NilAccept):
def __contains__(self, item):
return False
class MIMEAccept(Accept):
"""
Represents the ``Accept`` header, which is a list of mimetypes.
This class knows about mime wildcards, like ``image/*``
"""
def _match(self, item, match):
item = item.lower()
if item == '*':
item = '*/*'
match = match.lower()
if match == '*':
match = '*/*'
if '/' not in item:
# Bad, but we ignore
return False
if '/' not in match:
raise ValueError(
"MIME matches must include / (bad: %r)" % match)
item_major, item_minor = item.split('/', 1)
match_major, match_minor = match.split('/', 1)
if match_major == '*' and match_minor != '*':
raise ValueError(
"A MIME type of %r doesn't make sense" % match)
if item_major == '*' and item_minor != '*':
# Bad, but we ignore
return False
if ((item_major == '*' and item_minor == '*')
or (match_major == '*' and match_minor == '*')):
return True
if (item_major == match_major
and ((item_minor == '*' or match_minor == '*')
or item_minor == match_minor)):
return True
return False
def accept_html(self):
"""
Returns true if any HTML-like type is accepted
"""
return ('text/html' in self
or 'application/xhtml+xml' in self
or 'application/xml' in self
or 'text/xml' in self)
class MIMENilAccept(NilAccept):
MasterClass = MIMEAccept
Jump to Line
Something went wrong with that request. Please try again.