comparison roundup/rest.py @ 6311:be8d5a8e090a

Fix uncaught error when parsing rest headers, document Started this work as better docs for rest response format. But I found 406 error response was not being tested. Also there was no error for bad Content-Type. In rest.py fix uncaught exceptions due to invalid Accept or Content-Type headers. If Content-type is valid but not application/json return code 415. Document use of accept header (was only shown in examples) and support for q parameter. Describe using .xml and .json extensions to select return format for testing from browser (where setting accept header is a problem). Document 406 error code return. Document 415 error code return and acceptable content types. Previously only doc was in examples. Set up tests for 406 and 415 error codes.
author John Rouillard <rouilj@ieee.org>
date Fri, 01 Jan 2021 14:14:34 -0500
parents 5b66c480f71f
children c1a672b1ad85
comparison
equal deleted inserted replaced
6310:68d83479747b 6311:be8d5a8e090a
221 parts = media_range.split(";") 221 parts = media_range.split(";")
222 media_type = parts.pop(0).strip() 222 media_type = parts.pop(0).strip()
223 media_params = [] 223 media_params = []
224 # convert vendor-specific content types into something useful (see 224 # convert vendor-specific content types into something useful (see
225 # docstring) 225 # docstring)
226 typ, subtyp = media_type.split('/') 226 try:
227 typ, subtyp = media_type.split('/')
228 except ValueError:
229 raise UsageError("Invalid media type: %s"%media_type)
227 # check for a + in the sub-type 230 # check for a + in the sub-type
228 if '+' in subtyp: 231 if '+' in subtyp:
229 # if it exists, determine if the subtype is a vendor-specific type 232 # if it exists, determine if the subtype is a vendor-specific type
230 vnd, sep, extra = subtyp.partition('+') 233 vnd, sep, extra = subtyp.partition('+')
231 if vnd.startswith('vnd'): 234 if vnd.startswith('vnd'):
244 # and re-write media_type to something like application/json so 247 # and re-write media_type to something like application/json so
245 # it can be used usefully when looking up emitters 248 # it can be used usefully when looking up emitters
246 media_type = '{}/{}'.format(typ, extra) 249 media_type = '{}/{}'.format(typ, extra)
247 q = 1.0 250 q = 1.0
248 for part in parts: 251 for part in parts:
249 (key, value) = part.lstrip().split("=", 1) 252 try:
253 (key, value) = part.lstrip().split("=", 1)
254 except ValueError:
255 raise UsageError("Invalid param: %s"%part.lstrip())
250 key = key.strip() 256 key = key.strip()
251 value = value.strip() 257 value = value.strip()
252 if key == "q": 258 if key == "q":
253 q = float(value) 259 q = float(value)
254 if q > 1.0: 260 if q > 1.0:
1871 method.upper(), uri) 1877 method.upper(), uri)
1872 1878
1873 # parse Accept header and get the content type 1879 # parse Accept header and get the content type
1874 # Acceptable types ordered with preferred one first 1880 # Acceptable types ordered with preferred one first
1875 # in list. 1881 # in list.
1876 accept_header = parse_accept_header(headers.get('Accept')) 1882 try:
1883 accept_header = parse_accept_header(headers.get('Accept'))
1884 except UsageError as e:
1885 output = self.error_obj(406, _("Unable to parse Accept Header. %(error)s. "
1886 "Acceptable types: %(acceptable_types)s") % {
1887 'error': e.args[0],
1888 'acceptable_types': " ".join(sorted(self.__accepted_content_type.keys()))})
1889 accept_header = []
1890
1877 if not accept_header: 1891 if not accept_header:
1878 accept_type = self.__default_accept_type 1892 accept_type = self.__default_accept_type
1879 else: 1893 else:
1880 accept_type = None 1894 accept_type = None
1881 for part in accept_header: 1895 for part in accept_header:
1911 # get the request format for response 1925 # get the request format for response
1912 # priority : extension from uri (/rest/data/issue.json), 1926 # priority : extension from uri (/rest/data/issue.json),
1913 # header (Accept: application/json, application/xml) 1927 # header (Accept: application/json, application/xml)
1914 # default (application/json) 1928 # default (application/json)
1915 ext_type = os.path.splitext(urlparse(uri).path)[1][1:] 1929 ext_type = os.path.splitext(urlparse(uri).path)[1][1:]
1916 data_type = ext_type or accept_type or "invalid" 1930
1931 # headers.get('Accept') is never empty if called here.
1932 # accept_type will be set to json if there is no Accept header
1933 # accept_type wil be empty only if there is an Accept header
1934 # with invalid values.
1935 data_type = ext_type or accept_type or headers.get('Accept') or "invalid"
1917 1936
1918 if (ext_type): 1937 if (ext_type):
1919 # strip extension so uri make sense 1938 # strip extension so uri make sense
1920 # .../issue.json -> .../issue 1939 # .../issue.json -> .../issue
1921 uri = uri[:-(len(ext_type) + 1)] 1940 uri = uri[:-(len(ext_type) + 1)]
1954 if content_type_header.lower() == "application/json": 1973 if content_type_header.lower() == "application/json":
1955 try: 1974 try:
1956 input = SimulateFieldStorageFromJson(b2s(input.value)) 1975 input = SimulateFieldStorageFromJson(b2s(input.value))
1957 except ValueError as msg: 1976 except ValueError as msg:
1958 output = self.error_obj(400, msg) 1977 output = self.error_obj(400, msg)
1978 else:
1979 output = self.error_obj(415,
1980 "Unable to process input of type %s" %
1981 content_type_header)
1959 1982
1960 # check for pretty print 1983 # check for pretty print
1961 try: 1984 try:
1962 pretty_output = not input['@pretty'].value.lower() == "false" 1985 pretty_output = not input['@pretty'].value.lower() == "false"
1963 # Can also return a TypeError ("not indexable") 1986 # Can also return a TypeError ("not indexable")

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