comparison roundup/rest.py @ 5594:864cf6cb5790 REST-rebased

Added ability to parse HTTP accept header .. to serve the content type correctly committer: Ralf Schlatterbeck <rsc@runtux.com>
author Chau Nguyen <dangchau1991@yahoo.com>
date Wed, 30 Jan 2019 10:26:35 +0100
parents 344b6a87dac6
children 65caddd54da2
comparison
equal deleted inserted replaced
5593:344b6a87dac6 5594:864cf6cb5790
67 'data': data 67 'data': data
68 } 68 }
69 return result 69 return result
70 return format_object 70 return format_object
71 71
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
72 131
73 class RestfulInstance(object): 132 class RestfulInstance(object):
74 """The RestfulInstance performs REST request from the client""" 133 """The RestfulInstance performs REST request from the client"""
75 134
76 __default_patch_op = "replace" # default operator for PATCH method 135 __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"
77 142
78 def __init__(self, client, db): 143 def __init__(self, client, db):
79 self.client = client 144 self.client = client
80 self.db = db 145 self.db = db
81 146
776 ) 841 )
777 return 204, "" 842 return 204, ""
778 843
779 def dispatch(self, method, uri, input): 844 def dispatch(self, method, uri, input):
780 """format and process the request""" 845 """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
788 # if X-HTTP-Method-Override is set, follow the override method 846 # if X-HTTP-Method-Override is set, follow the override method
789 headers = self.client.request.headers 847 headers = self.client.request.headers
790 method = headers.getheader('X-HTTP-Method-Override') or method 848 method = headers.getheader('X-HTTP-Method-Override') or method
849
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]]
791 856
792 # get the request format for response 857 # get the request format for response
793 # priority : extension from uri (/rest/issue.json), 858 # priority : extension from uri (/rest/issue.json),
794 # header (Accept: application/json, application/xml) 859 # header (Accept: application/json, application/xml)
795 # default (application/json) 860 # default (application/json)
796
797 # format_header need a priority parser
798 ext_type = os.path.splitext(urlparse.urlparse(uri).path)[1][1:] 861 ext_type = os.path.splitext(urlparse.urlparse(uri).path)[1][1:]
799 accept_header = headers.getheader('Accept')[12:] 862 data_type = ext_type or accept_type or self.__default_accept_type
800 data_type = ext_type or accept_header or "json"
801 863
802 # check for pretty print 864 # check for pretty print
803 try: 865 try:
804 pretty_output = input['pretty'].value.lower() == "true" 866 pretty_output = input['pretty'].value.lower() == "true"
805 except KeyError: 867 except KeyError:
817 ) 879 )
818 self.client.setHeader( 880 self.client.setHeader(
819 "Access-Control-Allow-Methods", 881 "Access-Control-Allow-Methods",
820 "HEAD, OPTIONS, GET, PUT, DELETE, PATCH" 882 "HEAD, OPTIONS, GET, PUT, DELETE, PATCH"
821 ) 883 )
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
822 try: 892 try:
823 class_name, item_id = hyperdb.splitDesignator(resource_uri) 893 class_name, item_id = hyperdb.splitDesignator(resource_uri)
824 except hyperdb.DesignatorError: 894 except hyperdb.DesignatorError:
825 class_name = resource_uri 895 class_name = resource_uri
826 item_id = None 896 item_id = None

Roundup Issue Tracker: http://roundup-tracker.org/