@@ -69,11 +69,76 @@ def format_object(self, *args, **kwargs):
6969 return result
7070 return format_object
7171
72+ def parse_accept_header (accept ):
73+ """
74+ Parse the Accept header *accept*, returning a list with 3-tuples of
75+ [(str(media_type), dict(params), float(q_value)),] ordered by q values.
76+
77+ If the accept header includes vendor-specific types like::
78+ application/vnd.yourcompany.yourproduct-v1.1+json
79+
80+ It will actually convert the vendor and version into parameters and
81+ convert the content type into `application/json` so appropriate content
82+ negotiation decisions can be made.
83+
84+ Default `q` for values that are not specified is 1.0
85+
86+ # Based on https://gist.github.com/samuraisam/2714195
87+ # Also, based on a snipped found in this project:
88+ # https://github.com/martinblech/mimerender
89+ """
90+ result = []
91+ for media_range in accept .split ("," ):
92+ parts = media_range .split (";" )
93+ media_type = parts .pop (0 ).strip ()
94+ media_params = []
95+ # convert vendor-specific content types into something useful (see
96+ # docstring)
97+ typ , subtyp = media_type .split ('/' )
98+ # check for a + in the sub-type
99+ if '+' in subtyp :
100+ # if it exists, determine if the subtype is a vendor-specific type
101+ vnd , sep , extra = subtyp .partition ('+' )
102+ if vnd .startswith ('vnd' ):
103+ # and then... if it ends in something like "-v1.1" parse the
104+ # version out
105+ if '-v' in vnd :
106+ vnd , sep , rest = vnd .rpartition ('-v' )
107+ if len (rest ):
108+ # add the version as a media param
109+ try :
110+ version = media_params .append (('version' ,
111+ float (rest )))
112+ except ValueError :
113+ version = 1.0 # could not be parsed
114+ # add the vendor code as a media param
115+ media_params .append (('vendor' , vnd ))
116+ # and re-write media_type to something like application/json so
117+ # it can be used usefully when looking up emitters
118+ media_type = '{}/{}' .format (typ , extra )
119+ q = 1.0
120+ for part in parts :
121+ (key , value ) = part .lstrip ().split ("=" , 1 )
122+ key = key .strip ()
123+ value = value .strip ()
124+ if key == "q" :
125+ q = float (value )
126+ else :
127+ media_params .append ((key , value ))
128+ result .append ((media_type , dict (media_params ), q ))
129+ result .sort (lambda x , y : - cmp (x [2 ], y [2 ]))
130+ return result
72131
73132class RestfulInstance (object ):
74133 """The RestfulInstance performs REST request from the client"""
75134
76135 __default_patch_op = "replace" # default operator for PATCH method
136+ __accepted_content_type = {
137+ "application/json" : "json" ,
138+ "*/*" : "json"
139+ # "application/xml": "xml"
140+ }
141+ __default_accept_type = "json"
77142
78143 def __init__ (self , client , db ):
79144 self .client = client
@@ -778,26 +843,23 @@ def option_attribute(self, class_name, item_id, attr_name, input):
778843
779844 def dispatch (self , method , uri , input ):
780845 """format and process the request"""
781- # PATH is split to multiple pieces
782- # 0 - rest
783- # 1 - resource
784- # 2 - attribute
785- uri_split = uri .split ("/" )
786- resource_uri = uri_split [1 ]
787-
788846 # if X-HTTP-Method-Override is set, follow the override method
789847 headers = self .client .request .headers
790848 method = headers .getheader ('X-HTTP-Method-Override' ) or method
791849
850+ # parse Accept header and get the content type
851+ accept_header = parse_accept_header (headers .getheader ('Accept' ))
852+ accept_type = "invalid"
853+ for part in accept_header :
854+ if part [0 ] in self .__accepted_content_type :
855+ accept_type = self .__accepted_content_type [part [0 ]]
856+
792857 # get the request format for response
793858 # priority : extension from uri (/rest/issue.json),
794859 # header (Accept: application/json, application/xml)
795860 # default (application/json)
796-
797- # format_header need a priority parser
798861 ext_type = os .path .splitext (urlparse .urlparse (uri ).path )[1 ][1 :]
799- accept_header = headers .getheader ('Accept' )[12 :]
800- data_type = ext_type or accept_header or "json"
862+ data_type = ext_type or accept_type or self .__default_accept_type
801863
802864 # check for pretty print
803865 try :
@@ -819,6 +881,14 @@ def dispatch(self, method, uri, input):
819881 "Access-Control-Allow-Methods" ,
820882 "HEAD, OPTIONS, GET, PUT, DELETE, PATCH"
821883 )
884+
885+ # PATH is split to multiple pieces
886+ # 0 - rest
887+ # 1 - resource
888+ # 2 - attribute
889+ uri_split = uri .split ("/" )
890+ resource_uri = uri_split [1 ]
891+
822892 try :
823893 class_name , item_id = hyperdb .splitDesignator (resource_uri )
824894 except hyperdb .DesignatorError :
0 commit comments