Skip to content

Instantly share code, notes, and snippets.

@samuraisam
Created May 16, 2012 21:43
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save samuraisam/2714195 to your computer and use it in GitHub Desktop.
Save samuraisam/2714195 to your computer and use it in GitHub Desktop.
Parse an accept header in Python w/ support for vendor types
# The author disclaims copyright to this source code. In place of a legal
# notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
# It is based on a snipped found in this project:
# https://github.com/martinblech/mimerender
def parse_accept_header(accept):
"""
Parse the Accept header *accept*, returning a list with 3-tuples of
[(str(media_type), dict(params), float(q_value)),] ordered by q values.
If the accept header includes vendor-specific types like::
application/vnd.yourcompany.yourproduct-v1.1+json
It will actually convert the vendor and version into parameters and
convert the content type into `application/json` so appropriate content
negotiation decisions can be made.
Default `q` for values that are not specified is 1.0
"""
result = []
for media_range in accept.split(","):
parts = media_range.split(";")
media_type = parts.pop(0).strip()
media_params = []
# convert vendor-specific content types into something useful (see
# docstring)
typ, subtyp = media_type.split('/')
# check for a + in the sub-type
if '+' in subtyp:
# if it exists, determine if the subtype is a vendor-specific type
vnd, sep, extra = subtyp.partition('+')
if vnd.startswith('vnd'):
# and then... if it ends in something like "-v1.1" parse the
# version out
if '-v' in vnd:
vnd, sep, rest = vnd.rpartition('-v')
if len(rest):
# add the version as a media param
try:
version = media_params.append(('version',
float(rest)))
except ValueError:
version = 1.0 # could not be parsed
# add the vendor code as a media param
media_params.append(('vendor', vnd))
# and re-write media_type to something like application/json so
# it can be used usefully when looking up emitters
media_type = '{}/{}'.format(typ, extra)
q = 1.0
for part in parts:
(key, value) = part.lstrip().split("=", 1)
key = key.strip()
value = value.strip()
if key == "q":
q = float(value)
else:
media_params.append((key, value))
result.append((media_type, dict(media_params), q))
result.sort(lambda x, y: -cmp(x[2], y[2]))
return result
from unittest import TestCase
from parse_accept import parse_accept_header
class TestParseAcceptHeader(TestCase):
def test_parse_accept_header_browser(self):
accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8,application/json"
should = [('text/html', {}, 1.0),
('application/xhtml+xml', {}, 1.0),
('application/json', {}, 1.0),
('application/xml', {}, 0.9),
('*/*', {}, 0.8)]
self.assertEquals(parse_accept_header(accept), should)
def test_parse_accept_header_smart_client(self):
accept = "application/vnd.ficture.lightt-v1.1+json"
should = [('application/json', {'version': 1.1,
'vendor': 'vnd.ficture.lightt'}, 1.0)]
self.assertEquals(parse_accept_header(accept), should)
def test_parse_accept_header_dumbish_client(self):
accept = "application/vnd.ficture.lightt-v1.0"
should = [('application/vnd.ficture.lightt-v1.0', {}, 1.0)]
self.assertEquals(parse_accept_header(accept), should)
def test_parse_accept_header_also_dumb_client(self):
accept = "application/vnd.ficture.lightt"
should = [('application/vnd.ficture.lightt', {}, 1.0)]
self.assertEquals(parse_accept_header(accept), should)
def test_parse_accept_header_dumb_client(self):
accept = "application/json"
should = [('application/json', {}, 1.0)]
self.assertEquals(parse_accept_header(accept), should)
def test_parse_accept_header_really_dumb_client(self):
accept = ""
should = [('', {}, 1.0)]
self.assertEquals(parse_accept_header(accept), should)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment