comparison roundup/rest.py @ 8188:e5362f8e1808

chore(ruff): rename input and id parms/vars - don't shadow builtin Changed input parm to input_payload and id param to node_id for etag context. Changed var/param id to item_id. Change var id to working_id. Trying to clear a lot of ruff warnings as client.py and rest.py will be getting some work done on them and I want to reduce erros so I can see errors in newer code more easily.
author John Rouillard <rouilj@ieee.org>
date Wed, 11 Dec 2024 13:04:34 -0500
parents d02ce1d14acd
children 569aff540a21
comparison
equal deleted inserted replaced
8187:5c506c778893 8188:e5362f8e1808
171 f.openapi_doc = d 171 f.openapi_doc = d
172 return f 172 return f
173 return wrapper 173 return wrapper
174 174
175 175
176 def calculate_etag(node, key, classname="Missing", id="0", 176 def calculate_etag(node, key, classname="Missing", node_id="0",
177 repr_format="json"): 177 repr_format="json"):
178 '''given a hyperdb node generate a hashed representation of it to be 178 '''given a hyperdb node generate a hashed representation of it to be
179 used as an etag. 179 used as an etag.
180 180
181 This code needs a __repr__ function in the Password class. This 181 This code needs a __repr__ function in the Password class. This
192 calculate the etag. 192 calculate the etag.
193 193
194 Note that repr() is chosen for the node rather than str() since 194 Note that repr() is chosen for the node rather than str() since
195 repr is meant to be an unambiguous representation. 195 repr is meant to be an unambiguous representation.
196 196
197 classname and id are used for logging only. 197 classname and node_id are used for logging only.
198 ''' 198 '''
199 199
200 items = node.items(protected=True) # include every item 200 items = node.items(protected=True) # include every item
201 etag = hmac.new(bs2b(key), bs2b(repr_format + 201 etag = hmac.new(bs2b(key), bs2b(repr_format +
202 repr(sorted(items))), md5).hexdigest() 202 repr(sorted(items))), md5).hexdigest()
203 logger.debug("object=%s%s; tag=%s; repr=%s", classname, id, 203 logger.debug("object=%s%s; tag=%s; repr=%s", classname, node_id,
204 etag, repr(node.items(protected=True))) 204 etag, repr(node.items(protected=True)))
205 # Quotes are part of ETag spec, normal headers don't have quotes 205 # Quotes are part of ETag spec, normal headers don't have quotes
206 return '"%s"' % etag 206 return '"%s"' % etag
207 207
208 208
209 def check_etag(node, key, etags, classname="Missing", id="0", 209 def check_etag(node, key, etags, classname="Missing", node_id="0",
210 repr_format="json"): 210 repr_format="json"):
211 '''Take a list of etags and compare to the etag for the given node. 211 '''Take a list of etags and compare to the etag for the given node.
212 212
213 Iterate over all supplied etags, 213 Iterate over all supplied etags,
214 If a tag fails to match, return False. 214 If a tag fails to match, return False.
216 If all etags are None, return False. 216 If all etags are None, return False.
217 217
218 ''' 218 '''
219 have_etag_match = False 219 have_etag_match = False
220 220
221 node_etag = calculate_etag(node, key, classname, id, 221 node_etag = calculate_etag(node, key, classname, node_id,
222 repr_format=repr_format) 222 repr_format=repr_format)
223 223
224 for etag in etags: 224 for etag in etags:
225 # etag includes doublequotes around tag: 225 # etag includes doublequotes around tag:
226 # '"a46a5572190e4fad63958c135f3746fa"' 226 # '"a46a5572190e4fad63958c135f3746fa"'
243 return True 243 return True
244 else: 244 else:
245 return False 245 return False
246 246
247 247
248 def obtain_etags(headers, input): 248 def obtain_etags(headers, input_payload):
249 '''Get ETags value from headers or payload data''' 249 '''Get ETags value from headers or payload data
250 Only supports one etag value not list.
251 '''
250 etags = [] 252 etags = []
251 if '@etag' in input: 253 if '@etag' in input_payload:
252 etags.append(input['@etag'].value) 254 etags.append(input_payload['@etag'].value)
253 etags.append(headers.get("If-Match", None)) 255 etags.append(headers.get("If-Match", None))
254 return etags 256 return etags
255 257
256 258
257 def parse_accept_header(accept): 259 def parse_accept_header(accept):
388 cls.__route_map[pattern] = rule_route 390 cls.__route_map[pattern] = rule_route
389 return func 391 return func
390 return decorator 392 return decorator
391 393
392 @classmethod 394 @classmethod
393 def execute(cls, instance, path, method, input): 395 def execute(cls, instance, path, method, input_payload):
394 # format the input, note that we may not lowercase the path 396 # format the input_payload, note that we may not lowercase the path
395 # here, URL parameters are case-sensitive 397 # here, URL parameters are case-sensitive
396 path = path.strip('/') 398 path = path.strip('/')
397 if path == 'rest': 399 if path == 'rest':
398 # allow handler to be called for /rest/ 400 # allow handler to be called for /rest/
399 path = 'rest/' 401 path = 'rest/'
420 list_vars = func_obj['vars'] 422 list_vars = func_obj['vars']
421 func = func_obj['func'] 423 func = func_obj['func']
422 424
423 # zip the varlist into a dictionary, and pass it to the caller 425 # zip the varlist into a dictionary, and pass it to the caller
424 args = dict(zip(list_vars, match_obj.groups())) 426 args = dict(zip(list_vars, match_obj.groups()))
425 args['input'] = input 427 args['input'] = input_payload
426 return func(instance, **args) 428 return func(instance, **args)
427 raise NotFound('Nothing matches the given URI') 429 raise NotFound('Nothing matches the given URI')
428 430
429 431
430 class RestfulInstance(object): 432 class RestfulInstance(object):
666 else: 668 else:
667 raise UsageError('PATCH Operation %s is not allowed' % op) 669 raise UsageError('PATCH Operation %s is not allowed' % op)
668 670
669 return result 671 return result
670 672
671 def raise_if_no_etag(self, class_name, item_id, input, repr_format="json"): 673 def raise_if_no_etag(self, class_name, item_id, input_payload,
674 repr_format="json"):
672 class_obj = self.db.getclass(class_name) 675 class_obj = self.db.getclass(class_name)
673 if not check_etag(class_obj.getnode(item_id), 676 if not check_etag(class_obj.getnode(item_id),
674 self.db.config.WEB_SECRET_KEY, 677 self.db.config.WEB_SECRET_KEY,
675 obtain_etags(self.client.request.headers, input), 678 obtain_etags(self.client.request.headers,
676 class_name, 679 input_payload), class_name, item_id,
677 item_id, repr_format=repr_format): 680 repr_format=repr_format):
678 raise PreconditionFailed( 681 raise PreconditionFailed(
679 "If-Match is missing or does not match." 682 "If-Match is missing or does not match."
680 " Retrieve asset and retry modification if valid.") 683 " Retrieve asset and retry modification if valid.")
681 684
682 def format_item(self, node, item_id, props=None, verbose=1): 685 def format_item(self, node, item_id, props=None, verbose=1):
697 result = {} 700 result = {}
698 try: 701 try:
699 # pn = propname 702 # pn = propname
700 for pn in sorted(props): 703 for pn in sorted(props):
701 ok = False 704 ok = False
702 id = item_id 705 working_id = item_id
703 nd = node 706 nd = node
704 cn = class_name 707 cn = class_name
705 for p in pn.split('.'): 708 for p in pn.split('.'):
706 if not self.db.security.hasPermission( 709 if not self.db.security.hasPermission(
707 'View', uid, cn, p, id 710 'View', uid, cn, p, working_id
708 ): 711 ):
709 break 712 break
710 cl = self.db.getclass(cn) 713 cl = self.db.getclass(cn)
711 nd = cl.getnode(id) 714 nd = cl.getnode(working_id)
712 id = v = getattr(nd, p) 715 working_id = v = getattr(nd, p)
713 # Handle transitive properties where something on 716 # Handle transitive properties where something on
714 # the road is None (empty Link property) 717 # the road is None (empty Link property)
715 if id is None: 718 if working_id is None:
716 prop = None 719 prop = None
717 ok = True 720 ok = True
718 break 721 break
719 prop = cl.getprops(protected=True)[p] 722 prop = cl.getprops(protected=True)[p]
720 cn = getattr(prop, 'classname', None) 723 cn = getattr(prop, 'classname', None)
726 linkcls = self.db.getclass(prop.classname) 729 linkcls = self.db.getclass(prop.classname)
727 cp = '%s/%s/' % (self.data_path, prop.classname) 730 cp = '%s/%s/' % (self.data_path, prop.classname)
728 if verbose and v: 731 if verbose and v:
729 if isinstance(v, type([])): 732 if isinstance(v, type([])):
730 r = [] 733 r = []
731 for id in v: 734 for working_id in v:
732 d = dict(id=id, link=cp + id) 735 d = dict(id=working_id, link=cp + working_id)
733 if verbose > 1: 736 if verbose > 1:
734 label = linkcls.labelprop() 737 label = linkcls.labelprop()
735 d[label] = linkcls.get(id, label) 738 d[label] = linkcls.get(working_id, label)
736 r.append(d) 739 r.append(d)
737 result[pn] = r 740 result[pn] = r
738 else: 741 else:
739 result[pn] = dict(id=v, link=cp + v) 742 result[pn] = dict(id=v, link=cp + v)
740 if verbose > 1: 743 if verbose > 1:
766 769
767 return result 770 return result
768 771
769 @Routing.route("/data/<:class_name>", 'GET') 772 @Routing.route("/data/<:class_name>", 'GET')
770 @_data_decorator 773 @_data_decorator
771 def get_collection(self, class_name, input): 774 def get_collection(self, class_name, input_payload):
772 """GET resource from class URI. 775 """GET resource from class URI.
773 776
774 This function returns only items have View permission 777 This function returns only items have View permission
775 class_name should be valid already 778 class_name should be valid already
776 779
777 Args: 780 Args:
778 class_name (string): class name of the resource (Ex: issue, msg) 781 class_name (string): class name of the resource (Ex: issue, msg)
779 input (list): the submitted form of the user 782 input_payload (list): the submitted form of the user
780 783
781 Returns: 784 Returns:
782 int: http status code 200 (OK) 785 int: http status code 200 (OK)
783 list: list of reference item in the class 786 list: list of reference item in the class
784 id: id of the object 787 id: id of the object
804 } 807 }
805 verbose = 1 808 verbose = 1
806 display_props = set() 809 display_props = set()
807 sort = [] 810 sort = []
808 group = [] 811 group = []
809 for form_field in input.value: 812 for form_field in input_payload.value:
810 key = form_field.name 813 key = form_field.name
811 value = form_field.value 814 value = form_field.value
812 if key.startswith("@page_"): # serve the paging purpose 815 if key.startswith("@page_"): # serve the paging purpose
813 key = key[6:] 816 key = key[6:]
814 value = int(value) 817 value = int(value)
1019 result['@links'][rel].append({ 1022 result['@links'][rel].append({
1020 'rel': rel, 1023 'rel': rel,
1021 'uri': "%s/%s?@page_index=%s&" % (self.data_path, 1024 'uri': "%s/%s?@page_index=%s&" % (self.data_path,
1022 class_name, index) + 1025 class_name, index) +
1023 '&'.join(["%s=%s" % (field.name, field.value) 1026 '&'.join(["%s=%s" % (field.name, field.value)
1024 for field in input.value 1027 for field in input_payload.value
1025 if field.name != "@page_index"])}) 1028 if field.name != "@page_index"])})
1026 1029
1027 result['@total_size'] = total_len 1030 result['@total_size'] = total_len
1028 self.client.setHeader("X-Count-Total", str(total_len)) 1031 self.client.setHeader("X-Count-Total", str(total_len))
1029 self.client.setHeader("Allow", "OPTIONS, GET, POST") 1032 self.client.setHeader("Allow", "OPTIONS, GET, POST")
1030 return 200, result 1033 return 200, result
1031 1034
1032 @Routing.route("/data/user/roles", 'GET') 1035 @Routing.route("/data/user/roles", 'GET')
1033 @_data_decorator 1036 @_data_decorator
1034 def get_roles(self, input): 1037 def get_roles(self, input_payload):
1035 """Return all defined roles for users with Admin role. 1038 """Return all defined roles for users with Admin role.
1036 The User class property roles is a string but simulate 1039 The User class property roles is a string but simulate
1037 it as a MultiLink to an actual Roles class. 1040 it as a MultiLink to an actual Roles class.
1038 """ 1041 """
1039 if not self.client.db.user.has_role(self.client.db.getuid(), "Admin"): 1042 if not self.client.db.user.has_role(self.client.db.getuid(), "Admin"):
1049 [{"id": rolename,"name": rolename} 1052 [{"id": rolename,"name": rolename}
1050 for rolename in list(self.db.security.role.keys())]} 1053 for rolename in list(self.db.security.role.keys())]}
1051 1054
1052 @Routing.route("/data/<:class_name>/<:item_id>", 'GET') 1055 @Routing.route("/data/<:class_name>/<:item_id>", 'GET')
1053 @_data_decorator 1056 @_data_decorator
1054 def get_element(self, class_name, item_id, input): 1057 def get_element(self, class_name, item_id, input_payload):
1055 """GET resource from object URI. 1058 """GET resource from object URI.
1056 1059
1057 This function returns only properties have View permission 1060 This function returns only properties have View permission
1058 class_name and item_id should be valid already 1061 class_name and item_id should be valid already
1059 1062
1060 Args: 1063 Args:
1061 class_name (string): class name of the resource (Ex: issue, msg) 1064 class_name (string): class name of the resource (Ex: issue, msg)
1062 item_id (string): id of the resource (Ex: 12, 15) 1065 item_id (string): id of the resource (Ex: 12, 15)
1063 or (if the class has a key property) this can also be 1066 or (if the class has a key property) this can also be
1064 the key name, e.g. class_name = status, item_id = 'open' 1067 the key name, e.g. class_name = status, item_id = 'open'
1065 input (list): the submitted form of the user 1068 input_payload (list): the submitted form of the user
1066 1069
1067 Returns: 1070 Returns:
1068 int: http status code 200 (OK) 1071 int: http status code 200 (OK)
1069 dict: a dictionary represents the object 1072 dict: a dictionary represents the object
1070 id: id of the object 1073 id: id of the object
1110 etag = calculate_etag(node, self.db.config.WEB_SECRET_KEY, 1113 etag = calculate_etag(node, self.db.config.WEB_SECRET_KEY,
1111 class_name, itemid, repr_format="json") 1114 class_name, itemid, repr_format="json")
1112 props = None 1115 props = None
1113 protected = False 1116 protected = False
1114 verbose = 1 1117 verbose = 1
1115 for form_field in input.value: 1118 for form_field in input_payload.value:
1116 key = form_field.name 1119 key = form_field.name
1117 value = form_field.value 1120 value = form_field.value
1118 if key == "@fields" or key == "@attrs": 1121 if key == "@fields" or key == "@attrs":
1119 if props is None: 1122 if props is None:
1120 props = set() 1123 props = set()
1151 self.client.setHeader("ETag", etag) 1154 self.client.setHeader("ETag", etag)
1152 return 200, result 1155 return 200, result
1153 1156
1154 @Routing.route("/data/<:class_name>/<:item_id>/<:attr_name>", 'GET') 1157 @Routing.route("/data/<:class_name>/<:item_id>/<:attr_name>", 'GET')
1155 @_data_decorator 1158 @_data_decorator
1156 def get_attribute(self, class_name, item_id, attr_name, input): 1159 def get_attribute(self, class_name, item_id, attr_name, input_payload):
1157 """GET resource from attribute URI. 1160 """GET resource from attribute URI.
1158 1161
1159 This function returns only attribute has View permission 1162 This function returns only attribute has View permission
1160 class_name should be valid already 1163 class_name should be valid already
1161 1164
1162 Args: 1165 Args:
1163 class_name (string): class name of the resource (Ex: issue, msg) 1166 class_name (string): class name of the resource (Ex: issue, msg)
1164 item_id (string): id of the resource (Ex: 12, 15) 1167 item_id (string): id of the resource (Ex: 12, 15)
1165 attr_name (string): attribute of the resource (Ex: title, nosy) 1168 attr_name (string): attribute of the resource (Ex: title, nosy)
1166 input (list): the submitted form of the user 1169 input_payload (list): the submitted form of the user
1167 1170
1168 Returns: 1171 Returns:
1169 int: http status code 200 (OK) 1172 int: http status code 200 (OK)
1170 list: a dictionary represents the attribute 1173 list: a dictionary represents the attribute
1171 id: id of the object 1174 id: id of the object
1203 self.client.setHeader("ETag", etag) 1206 self.client.setHeader("ETag", etag)
1204 return 200, result 1207 return 200, result
1205 1208
1206 @Routing.route("/data/<:class_name>", 'POST') 1209 @Routing.route("/data/<:class_name>", 'POST')
1207 @_data_decorator 1210 @_data_decorator
1208 def post_collection(self, class_name, input): 1211 def post_collection(self, class_name, input_payload):
1209 """POST a new object to a class 1212 """POST a new object to a class
1210 1213
1211 If the item is successfully created, the "Location" header will also 1214 If the item is successfully created, the "Location" header will also
1212 contain the link to the created object 1215 contain the link to the created object
1213 1216
1214 Args: 1217 Args:
1215 class_name (string): class name of the resource (Ex: issue, msg) 1218 class_name (string): class name of the resource (Ex: issue, msg)
1216 input (list): the submitted form of the user 1219 input_payload (list): the submitted form of the user
1217 1220
1218 Returns: 1221 Returns:
1219 int: http status code 201 (Created) 1222 int: http status code 201 (Created)
1220 dict: a reference item to the created object 1223 dict: a reference item to the created object
1221 id: id of the object 1224 id: id of the object
1222 link: path to the object 1225 link: path to the object
1223 """ 1226 """
1224 return self.post_collection_inner(class_name, input) 1227 return self.post_collection_inner(class_name, input_payload)
1225 1228
1226 @Routing.route("/data/<:class_name>/@poe", 'POST') 1229 @Routing.route("/data/<:class_name>/@poe", 'POST')
1227 @_data_decorator 1230 @_data_decorator
1228 def get_post_once_exactly(self, class_name, input): 1231 def get_post_once_exactly(self, class_name, input_payload):
1229 """Get the Post Once Exactly token to create a new instance of class 1232 """Get the Post Once Exactly token to create a new instance of class
1230 See https://tools.ietf.org/html/draft-nottingham-http-poe-00""" 1233 See https://tools.ietf.org/html/draft-nottingham-http-poe-00"""
1231 otks = self.db.Otk 1234 otks = self.db.Otk
1232 poe_key = otks.getUniqueKey() 1235 poe_key = otks.getUniqueKey()
1233 1236
1234 try: 1237 try:
1235 lifetime = int(input['lifetime'].value) 1238 lifetime = int(input_payload['lifetime'].value)
1236 except KeyError: 1239 except KeyError:
1237 lifetime = 30 * 60 # 30 minutes 1240 lifetime = 30 * 60 # 30 minutes
1238 except ValueError: 1241 except ValueError:
1239 raise UsageError("Value 'lifetime' must be an integer specify lifetime in seconds. Got %s." % input['lifetime'].value) 1242 raise UsageError("Value 'lifetime' must be an integer specify lifetime in seconds. Got %s." % input_payload['lifetime'].value)
1240 1243
1241 if lifetime > 3600 or lifetime < 1: 1244 if lifetime > 3600 or lifetime < 1:
1242 raise UsageError("Value 'lifetime' must be between 1 second and 1 hour (3600 seconds). Got %s." % input['lifetime'].value) 1245 raise UsageError("Value 'lifetime' must be between 1 second and 1 hour (3600 seconds). Got %s." % input_payload['lifetime'].value)
1243 1246
1244 try: 1247 try:
1245 # if generic tag exists, we don't care about the value 1248 # if generic tag exists, we don't care about the value
1246 is_generic = input['generic'] 1249 is_generic = input_payload['generic']
1247 # we generate a generic POE token 1250 # we generate a generic POE token
1248 is_generic = True 1251 is_generic = True
1249 except KeyError: 1252 except KeyError:
1250 is_generic = False 1253 is_generic = False
1251 1254
1267 (self.data_path, class_name, poe_key), 1270 (self.data_path, class_name, poe_key),
1268 'expires': ts + (60 * 60 * 24 * 7)} 1271 'expires': ts + (60 * 60 * 24 * 7)}
1269 1272
1270 @Routing.route("/data/<:class_name>/@poe/<:post_token>", 'POST') 1273 @Routing.route("/data/<:class_name>/@poe/<:post_token>", 'POST')
1271 @_data_decorator 1274 @_data_decorator
1272 def post_once_exactly_collection(self, class_name, post_token, input): 1275 def post_once_exactly_collection(self, class_name, post_token, input_payload):
1273 """Post exactly one to the resource named by class_name""" 1276 """Post exactly one to the resource named by class_name"""
1274 otks = self.db.Otk 1277 otks = self.db.Otk
1275 1278
1276 # remove expired keys so we don't use an expired key 1279 # remove expired keys so we don't use an expired key
1277 otks.clean() 1280 otks.clean()
1306 1309
1307 if cn != class_name and cn is not None: 1310 if cn != class_name and cn is not None:
1308 raise UsageError("POE token '%s' not valid for %s, was generated for class %s" % (post_token, class_name, cn)) 1311 raise UsageError("POE token '%s' not valid for %s, was generated for class %s" % (post_token, class_name, cn))
1309 1312
1310 # handle this as though they POSTed to /rest/data/class 1313 # handle this as though they POSTed to /rest/data/class
1311 return self.post_collection_inner(class_name, input) 1314 return self.post_collection_inner(class_name, input_payload)
1312 1315
1313 def post_collection_inner(self, class_name, input): 1316 def post_collection_inner(self, class_name, input_payload):
1314 if class_name not in self.db.classes: 1317 if class_name not in self.db.classes:
1315 raise NotFound('Class %s not found' % class_name) 1318 raise NotFound('Class %s not found' % class_name)
1316 if not self.db.security.hasPermission( 1319 if not self.db.security.hasPermission(
1317 'Create', self.db.getuid(), class_name 1320 'Create', self.db.getuid(), class_name
1318 ): 1321 ):
1319 raise Unauthorised('Permission to create %s denied' % class_name) 1322 raise Unauthorised('Permission to create %s denied' % class_name)
1320 1323
1321 class_obj = self.db.getclass(class_name) 1324 class_obj = self.db.getclass(class_name)
1322 1325
1323 # convert types 1326 # convert types
1324 props = self.props_from_args(class_obj, input.value) 1327 props = self.props_from_args(class_obj, input_payload.value)
1325 1328
1326 # check for the key property 1329 # check for the key property
1327 key = class_obj.getkey() 1330 key = class_obj.getkey()
1328 if key and key not in props: 1331 if key and key not in props:
1329 raise UsageError("Must provide the '%s' property." % key) 1332 raise UsageError("Must provide the '%s' property." % key)
1365 } 1368 }
1366 return 201, result 1369 return 201, result
1367 1370
1368 @Routing.route("/data/<:class_name>/<:item_id>", 'PUT') 1371 @Routing.route("/data/<:class_name>/<:item_id>", 'PUT')
1369 @_data_decorator 1372 @_data_decorator
1370 def put_element(self, class_name, item_id, input): 1373 def put_element(self, class_name, item_id, input_payload):
1371 """PUT a new content to an object 1374 """PUT a new content to an object
1372 1375
1373 Replace the content of the existing object 1376 Replace the content of the existing object
1374 1377
1375 Args: 1378 Args:
1376 class_name (string): class name of the resource (Ex: issue, msg) 1379 class_name (string): class name of the resource (Ex: issue, msg)
1377 item_id (string): id of the resource (Ex: 12, 15) 1380 item_id (string): id of the resource (Ex: 12, 15)
1378 input (list): the submitted form of the user 1381 input_payload (list): the submitted form of the user
1379 1382
1380 Returns: 1383 Returns:
1381 int: http status code 200 (OK) 1384 int: http status code 200 (OK)
1382 dict: a dictionary represents the modified object 1385 dict: a dictionary represents the modified object
1383 id: id of the object 1386 id: id of the object
1388 """ 1391 """
1389 if class_name not in self.db.classes: 1392 if class_name not in self.db.classes:
1390 raise NotFound('Class %s not found' % class_name) 1393 raise NotFound('Class %s not found' % class_name)
1391 class_obj = self.db.getclass(class_name) 1394 class_obj = self.db.getclass(class_name)
1392 1395
1393 props = self.props_from_args(class_obj, input.value, item_id) 1396 props = self.props_from_args(class_obj, input_payload.value, item_id)
1394 for p in props: 1397 for p in props:
1395 if not self.db.security.hasPermission( 1398 if not self.db.security.hasPermission(
1396 'Edit', self.db.getuid(), class_name, p, item_id 1399 'Edit', self.db.getuid(), class_name, p, item_id
1397 ): 1400 ):
1398 raise Unauthorised( 1401 raise Unauthorised(
1399 'Permission to edit %s of %s%s denied' % 1402 'Permission to edit %s of %s%s denied' %
1400 (p, class_name, item_id) 1403 (p, class_name, item_id)
1401 ) 1404 )
1402 try: 1405 try:
1403 self.raise_if_no_etag(class_name, item_id, input) 1406 self.raise_if_no_etag(class_name, item_id, input_payload)
1404 result = class_obj.set(item_id, **props) 1407 result = class_obj.set(item_id, **props)
1405 self.db.commit() 1408 self.db.commit()
1406 except (TypeError, IndexError, ValueError) as message: 1409 except (TypeError, IndexError, ValueError) as message:
1407 raise ValueError(message) 1410 raise ValueError(message)
1408 except KeyError as message: 1411 except KeyError as message:
1418 } 1421 }
1419 return 200, result 1422 return 200, result
1420 1423
1421 @Routing.route("/data/<:class_name>/<:item_id>/<:attr_name>", 'PUT') 1424 @Routing.route("/data/<:class_name>/<:item_id>/<:attr_name>", 'PUT')
1422 @_data_decorator 1425 @_data_decorator
1423 def put_attribute(self, class_name, item_id, attr_name, input): 1426 def put_attribute(self, class_name, item_id, attr_name, input_payload):
1424 """PUT an attribute to an object 1427 """PUT an attribute to an object
1425 1428
1426 Args: 1429 Args:
1427 class_name (string): class name of the resource (Ex: issue, msg) 1430 class_name (string): class name of the resource (Ex: issue, msg)
1428 item_id (string): id of the resource (Ex: 12, 15) 1431 item_id (string): id of the resource (Ex: 12, 15)
1429 attr_name (string): attribute of the resource (Ex: title, nosy) 1432 attr_name (string): attribute of the resource (Ex: title, nosy)
1430 input (list): the submitted form of the user 1433 input_payload (list): the submitted form of the user
1431 1434
1432 Returns: 1435 Returns:
1433 int: http status code 200 (OK) 1436 int: http status code 200 (OK)
1434 dict:a dictionary represents the modified object 1437 dict:a dictionary represents the modified object
1435 id: id of the object 1438 id: id of the object
1448 (class_name, item_id, attr_name) 1451 (class_name, item_id, attr_name)
1449 ) 1452 )
1450 class_obj = self.db.getclass(class_name) 1453 class_obj = self.db.getclass(class_name)
1451 props = { 1454 props = {
1452 attr_name: self.prop_from_arg( 1455 attr_name: self.prop_from_arg(
1453 class_obj, attr_name, input['data'].value, item_id 1456 class_obj, attr_name, input_payload['data'].value, item_id
1454 ) 1457 )
1455 } 1458 }
1456 1459
1457 try: 1460 try:
1458 self.raise_if_no_etag(class_name, item_id, input) 1461 self.raise_if_no_etag(class_name, item_id, input_payload)
1459 result = class_obj.set(item_id, **props) 1462 result = class_obj.set(item_id, **props)
1460 self.db.commit() 1463 self.db.commit()
1461 except (TypeError, IndexError, ValueError) as message: 1464 except (TypeError, IndexError, ValueError) as message:
1462 raise ValueError(message) 1465 raise ValueError(message)
1463 except KeyError as message: 1466 except KeyError as message:
1474 1477
1475 return 200, result 1478 return 200, result
1476 1479
1477 @Routing.route("/data/<:class_name>", 'DELETE') 1480 @Routing.route("/data/<:class_name>", 'DELETE')
1478 @_data_decorator 1481 @_data_decorator
1479 def delete_collection(self, class_name, input): 1482 def delete_collection(self, class_name, input_payload):
1480 """DELETE (retire) all objects in a class 1483 """DELETE (retire) all objects in a class
1481 There is currently no use-case, so this is disabled and 1484 There is currently no use-case, so this is disabled and
1482 always returns Unauthorised. 1485 always returns Unauthorised.
1483 1486
1484 Args: 1487 Args:
1485 class_name (string): class name of the resource (Ex: issue, msg) 1488 class_name (string): class name of the resource (Ex: issue, msg)
1486 input (list): the submitted form of the user 1489 input_payload (list): the submitted form of the user
1487 1490
1488 Returns: 1491 Returns:
1489 int: http status code 200 (OK) 1492 int: http status code 200 (OK)
1490 dict: 1493 dict:
1491 status (string): 'ok' 1494 status (string): 'ok'
1524 return 200, result 1527 return 200, result
1525 ''' 1528 '''
1526 1529
1527 @Routing.route("/data/<:class_name>/<:item_id>", 'DELETE') 1530 @Routing.route("/data/<:class_name>/<:item_id>", 'DELETE')
1528 @_data_decorator 1531 @_data_decorator
1529 def delete_element(self, class_name, item_id, input): 1532 def delete_element(self, class_name, item_id, input_payload):
1530 """DELETE (retire) an object in a class 1533 """DELETE (retire) an object in a class
1531 1534
1532 Args: 1535 Args:
1533 class_name (string): class name of the resource (Ex: issue, msg) 1536 class_name (string): class name of the resource (Ex: issue, msg)
1534 item_id (string): id of the resource (Ex: 12, 15) 1537 item_id (string): id of the resource (Ex: 12, 15)
1535 input (list): the submitted form of the user 1538 input_payload (list): the submitted form of the user
1536 1539
1537 Returns: 1540 Returns:
1538 int: http status code 200 (OK) 1541 int: http status code 200 (OK)
1539 dict: 1542 dict:
1540 status (string): 'ok' 1543 status (string): 'ok'
1547 ): 1550 ):
1548 raise Unauthorised( 1551 raise Unauthorised(
1549 'Permission to retire %s %s denied' % (class_name, item_id) 1552 'Permission to retire %s %s denied' % (class_name, item_id)
1550 ) 1553 )
1551 1554
1552 self.raise_if_no_etag(class_name, item_id, input) 1555 self.raise_if_no_etag(class_name, item_id, input_payload)
1553 class_obj.retire(item_id) 1556 class_obj.retire(item_id)
1554 self.db.commit() 1557 self.db.commit()
1555 result = { 1558 result = {
1556 'status': 'ok' 1559 'status': 'ok'
1557 } 1560 }
1558 1561
1559 return 200, result 1562 return 200, result
1560 1563
1561 @Routing.route("/data/<:class_name>/<:item_id>/<:attr_name>", 'DELETE') 1564 @Routing.route("/data/<:class_name>/<:item_id>/<:attr_name>", 'DELETE')
1562 @_data_decorator 1565 @_data_decorator
1563 def delete_attribute(self, class_name, item_id, attr_name, input): 1566 def delete_attribute(self, class_name, item_id, attr_name, input_payload):
1564 """DELETE an attribute in a object by setting it to None or empty 1567 """DELETE an attribute in a object by setting it to None or empty
1565 1568
1566 Args: 1569 Args:
1567 class_name (string): class name of the resource (Ex: issue, msg) 1570 class_name (string): class name of the resource (Ex: issue, msg)
1568 item_id (string): id of the resource (Ex: 12, 15) 1571 item_id (string): id of the resource (Ex: 12, 15)
1569 attr_name (string): attribute of the resource (Ex: title, nosy) 1572 attr_name (string): attribute of the resource (Ex: title, nosy)
1570 input (list): the submitted form of the user 1573 input_payload (list): the submitted form of the user
1571 1574
1572 Returns: 1575 Returns:
1573 int: http status code 200 (OK) 1576 int: http status code 200 (OK)
1574 dict: 1577 dict:
1575 status (string): 'ok' 1578 status (string): 'ok'
1601 props[attr_name] = [] 1604 props[attr_name] = []
1602 else: 1605 else:
1603 props[attr_name] = None 1606 props[attr_name] = None
1604 1607
1605 try: 1608 try:
1606 self.raise_if_no_etag(class_name, item_id, input) 1609 self.raise_if_no_etag(class_name, item_id, input_payload)
1607 class_obj.set(item_id, **props) 1610 class_obj.set(item_id, **props)
1608 self.db.commit() 1611 self.db.commit()
1609 except (TypeError, IndexError, ValueError) as message: 1612 except (TypeError, IndexError, ValueError) as message:
1610 raise ValueError(message) 1613 raise ValueError(message)
1611 except KeyError as message: 1614 except KeyError as message:
1619 1622
1620 return 200, result 1623 return 200, result
1621 1624
1622 @Routing.route("/data/<:class_name>/<:item_id>", 'PATCH') 1625 @Routing.route("/data/<:class_name>/<:item_id>", 'PATCH')
1623 @_data_decorator 1626 @_data_decorator
1624 def patch_element(self, class_name, item_id, input): 1627 def patch_element(self, class_name, item_id, input_payload):
1625 """PATCH an object 1628 """PATCH an object
1626 1629
1627 Patch an element using 3 operators 1630 Patch an element using 3 operators
1628 ADD : Append new value to the object's attribute 1631 ADD : Append new value to the object's attribute
1629 REPLACE: Replace object's attribute 1632 REPLACE: Replace object's attribute
1630 REMOVE: Clear object's attribute 1633 REMOVE: Clear object's attribute
1631 1634
1632 Args: 1635 Args:
1633 class_name (string): class name of the resource (Ex: issue, msg) 1636 class_name (string): class name of the resource (Ex: issue, msg)
1634 item_id (string): id of the resource (Ex: 12, 15) 1637 item_id (string): id of the resource (Ex: 12, 15)
1635 input (list): the submitted form of the user 1638 input_payload (list): the submitted form of the user
1636 1639
1637 Returns: 1640 Returns:
1638 int: http status code 200 (OK) 1641 int: http status code 200 (OK)
1639 dict: a dictionary represents the modified object 1642 dict: a dictionary represents the modified object
1640 id: id of the object 1643 id: id of the object
1644 the object 1647 the object
1645 """ 1648 """
1646 if class_name not in self.db.classes: 1649 if class_name not in self.db.classes:
1647 raise NotFound('Class %s not found' % class_name) 1650 raise NotFound('Class %s not found' % class_name)
1648 try: 1651 try:
1649 op = input['@op'].value.lower() 1652 op = input_payload['@op'].value.lower()
1650 except KeyError: 1653 except KeyError:
1651 op = self.__default_patch_op 1654 op = self.__default_patch_op
1652 class_obj = self.db.getclass(class_name) 1655 class_obj = self.db.getclass(class_name)
1653 1656
1654 self.raise_if_no_etag(class_name, item_id, input) 1657 self.raise_if_no_etag(class_name, item_id, input_payload)
1655 1658
1656 # if patch operation is action, call the action handler 1659 # if patch operation is action, call the action handler
1657 action_args = [class_name + item_id] 1660 action_args = [class_name + item_id]
1658 if op == 'action': 1661 if op == 'action':
1659 # extract action_name and action_args from form fields 1662 # extract action_name and action_args from form fields
1660 name = None 1663 name = None
1661 for form_field in input.value: 1664 for form_field in input_payload.value:
1662 key = form_field.name 1665 key = form_field.name
1663 value = form_field.value 1666 value = form_field.value
1664 if key == "@action_name": 1667 if key == "@action_name":
1665 name = value 1668 name = value
1666 elif key.startswith('@action_args'): 1669 elif key.startswith('@action_args'):
1682 'link': '%s/%s/%s' % (self.data_path, class_name, item_id), 1685 'link': '%s/%s/%s' % (self.data_path, class_name, item_id),
1683 'result': result 1686 'result': result
1684 } 1687 }
1685 else: 1688 else:
1686 # else patch operation is processing data 1689 # else patch operation is processing data
1687 props = self.props_from_args(class_obj, input.value, item_id, 1690 props = self.props_from_args(class_obj, input_payload.value, item_id,
1688 skip_protected=False) 1691 skip_protected=False)
1689 1692
1690 required_props = class_obj.get_required_props() 1693 required_props = class_obj.get_required_props()
1691 for prop in props: 1694 for prop in props:
1692 if not self.db.security.hasPermission( 1695 if not self.db.security.hasPermission(
1720 } 1723 }
1721 return 200, result 1724 return 200, result
1722 1725
1723 @Routing.route("/data/<:class_name>/<:item_id>/<:attr_name>", 'PATCH') 1726 @Routing.route("/data/<:class_name>/<:item_id>/<:attr_name>", 'PATCH')
1724 @_data_decorator 1727 @_data_decorator
1725 def patch_attribute(self, class_name, item_id, attr_name, input): 1728 def patch_attribute(self, class_name, item_id, attr_name, input_payload):
1726 """PATCH an attribute of an object 1729 """PATCH an attribute of an object
1727 1730
1728 Patch an element using 3 operators 1731 Patch an element using 3 operators
1729 ADD : Append new value to the attribute 1732 ADD : Append new value to the attribute
1730 REPLACE: Replace attribute 1733 REPLACE: Replace attribute
1732 1735
1733 Args: 1736 Args:
1734 class_name (string): class name of the resource (Ex: issue, msg) 1737 class_name (string): class name of the resource (Ex: issue, msg)
1735 item_id (string): id of the resource (Ex: 12, 15) 1738 item_id (string): id of the resource (Ex: 12, 15)
1736 attr_name (string): attribute of the resource (Ex: title, nosy) 1739 attr_name (string): attribute of the resource (Ex: title, nosy)
1737 input (list): the submitted form of the user 1740 input_payload (list): the submitted form of the user
1738 1741
1739 Returns: 1742 Returns:
1740 int: http status code 200 (OK) 1743 int: http status code 200 (OK)
1741 dict: a dictionary represents the modified object 1744 dict: a dictionary represents the modified object
1742 id: id of the object 1745 id: id of the object
1746 the object 1749 the object
1747 """ 1750 """
1748 if class_name not in self.db.classes: 1751 if class_name not in self.db.classes:
1749 raise NotFound('Class %s not found' % class_name) 1752 raise NotFound('Class %s not found' % class_name)
1750 try: 1753 try:
1751 op = input['@op'].value.lower() 1754 op = input_payload['@op'].value.lower()
1752 except KeyError: 1755 except KeyError:
1753 op = self.__default_patch_op 1756 op = self.__default_patch_op
1754 1757
1755 if not self.db.security.hasPermission( 1758 if not self.db.security.hasPermission(
1756 'Edit', self.db.getuid(), class_name, attr_name, item_id 1759 'Edit', self.db.getuid(), class_name, attr_name, item_id
1765 if attr_name not in class_obj.getprops(protected=False): 1768 if attr_name not in class_obj.getprops(protected=False):
1766 if attr_name in class_obj.getprops(protected=True): 1769 if attr_name in class_obj.getprops(protected=True):
1767 raise AttributeError("Attribute '%s' can not be updated " 1770 raise AttributeError("Attribute '%s' can not be updated "
1768 "for class %s." % (attr_name, class_name)) 1771 "for class %s." % (attr_name, class_name))
1769 1772
1770 self.raise_if_no_etag(class_name, item_id, input) 1773 self.raise_if_no_etag(class_name, item_id, input_payload)
1771 1774
1772 props = { 1775 props = {
1773 prop: self.prop_from_arg( 1776 prop: self.prop_from_arg(
1774 class_obj, prop, input['data'].value, item_id 1777 class_obj, prop, input_payload['data'].value, item_id
1775 ) 1778 )
1776 } 1779 }
1777 1780
1778 props[prop] = self.patch_data( 1781 props[prop] = self.patch_data(
1779 op, class_obj.get(item_id, prop), props[prop] 1782 op, class_obj.get(item_id, prop), props[prop]
1797 } 1800 }
1798 return 200, result 1801 return 200, result
1799 1802
1800 @Routing.route("/data/<:class_name>", 'OPTIONS') 1803 @Routing.route("/data/<:class_name>", 'OPTIONS')
1801 @_data_decorator 1804 @_data_decorator
1802 def options_collection(self, class_name, input): 1805 def options_collection(self, class_name, input_payload):
1803 """OPTION return the HTTP Header for the class uri 1806 """OPTION return the HTTP Header for the class uri
1804 1807
1805 Returns: 1808 Returns:
1806 int: http status code 204 (No content) 1809 int: http status code 204 (No content)
1807 body (string): an empty string 1810 body (string): an empty string
1819 ) 1822 )
1820 return 204, "" 1823 return 204, ""
1821 1824
1822 @Routing.route("/data/<:class_name>/<:item_id>", 'OPTIONS') 1825 @Routing.route("/data/<:class_name>/<:item_id>", 'OPTIONS')
1823 @_data_decorator 1826 @_data_decorator
1824 def options_element(self, class_name, item_id, input): 1827 def options_element(self, class_name, item_id, input_payload):
1825 """OPTION return the HTTP Header for the object uri 1828 """OPTION return the HTTP Header for the object uri
1826 1829
1827 Returns: 1830 Returns:
1828 int: http status code 204 (No content) 1831 int: http status code 204 (No content)
1829 body (string): an empty string 1832 body (string): an empty string
1844 ) 1847 )
1845 return 204, "" 1848 return 204, ""
1846 1849
1847 @Routing.route("/data/<:class_name>/<:item_id>/<:attr_name>", 'OPTIONS') 1850 @Routing.route("/data/<:class_name>/<:item_id>/<:attr_name>", 'OPTIONS')
1848 @_data_decorator 1851 @_data_decorator
1849 def option_attribute(self, class_name, item_id, attr_name, input): 1852 def option_attribute(self, class_name, item_id, attr_name, input_payload):
1850 """OPTION return the HTTP Header for the attribute uri 1853 """OPTION return the HTTP Header for the attribute uri
1851 1854
1852 Returns: 1855 Returns:
1853 int: http status code 204 (No content) 1856 int: http status code 204 (No content)
1854 body (string): an empty string 1857 body (string): an empty string
1960 } 1963 }
1961 } 1964 }
1962 ) 1965 )
1963 @Routing.route("/") 1966 @Routing.route("/")
1964 @_data_decorator 1967 @_data_decorator
1965 def describe(self, input): 1968 def describe(self, input_payload):
1966 """Describe the rest endpoint. Return direct children in 1969 """Describe the rest endpoint. Return direct children in
1967 links list. 1970 links list.
1968 """ 1971 """
1969 1972
1970 # paths looks like ['^rest/$', '^rest/summary$', 1973 # paths looks like ['^rest/$', '^rest/summary$',
1997 2000
1998 return 200, result 2001 return 200, result
1999 2002
2000 @Routing.route("/", 'OPTIONS') 2003 @Routing.route("/", 'OPTIONS')
2001 @_data_decorator 2004 @_data_decorator
2002 def options_describe(self, input): 2005 def options_describe(self, input_payload):
2003 """OPTION return the HTTP Header for the root 2006 """OPTION return the HTTP Header for the root
2004 2007
2005 Returns: 2008 Returns:
2006 int: http status code 204 (No content) 2009 int: http status code 204 (No content)
2007 body (string): an empty string 2010 body (string): an empty string
2016 ) 2019 )
2017 return 204, "" 2020 return 204, ""
2018 2021
2019 @Routing.route("/data") 2022 @Routing.route("/data")
2020 @_data_decorator 2023 @_data_decorator
2021 def data(self, input): 2024 def data(self, input_payload):
2022 """Describe the subelements of data 2025 """Describe the subelements of data
2023 2026
2024 One entry for each class the user may view 2027 One entry for each class the user may view
2025 """ 2028 """
2026 result = {} 2029 result = {}
2030 result[cls] = dict(link=self.base_path + '/data/' + cls) 2033 result[cls] = dict(link=self.base_path + '/data/' + cls)
2031 return 200, result 2034 return 200, result
2032 2035
2033 @Routing.route("/data", 'OPTIONS') 2036 @Routing.route("/data", 'OPTIONS')
2034 @_data_decorator 2037 @_data_decorator
2035 def options_data(self, input): 2038 def options_data(self, input_payload):
2036 """OPTION return the HTTP Header for the /data element 2039 """OPTION return the HTTP Header for the /data element
2037 2040
2038 Returns: 2041 Returns:
2039 int: http status code 204 (No content) 2042 int: http status code 204 (No content)
2040 body (string): an empty string 2043 body (string): an empty string
2049 ) 2052 )
2050 return 204, "" 2053 return 204, ""
2051 2054
2052 @Routing.route("/summary") 2055 @Routing.route("/summary")
2053 @_data_decorator 2056 @_data_decorator
2054 def summary(self, input): 2057 def summary(self, input_payload):
2055 """Get a summary of resource from class URI. 2058 """Get a summary of resource from class URI.
2056 2059
2057 This function returns only items have View permission 2060 This function returns only items have View permission
2058 class_name should be valid already 2061 class_name should be valid already
2059 2062
2060 Args: 2063 Args:
2061 class_name (string): class name of the resource (Ex: issue, msg) 2064 class_name (string): class name of the resource (Ex: issue, msg)
2062 input (list): the submitted form of the user 2065 input_payload (list): the submitted form of the user
2063 2066
2064 Returns: 2067 Returns:
2065 int: http status code 200 (OK) 2068 int: http status code 200 (OK)
2066 list: 2069 list:
2067 """ 2070 """
2401 "Acceptable mime types are: */*, %s") % 2404 "Acceptable mime types are: */*, %s") %
2402 (self.client.request.headers.get('Accept'), 2405 (self.client.request.headers.get('Accept'),
2403 ", ".join(sorted( 2406 ", ".join(sorted(
2404 self.__accepted_content_type.keys()))))) 2407 self.__accepted_content_type.keys())))))
2405 2408
2406 def dispatch(self, method, uri, input): 2409 def dispatch(self, method, uri, input_payload):
2407 """format and process the request""" 2410 """format and process the request"""
2408 output = None 2411 output = None
2409 2412
2410 # Before we do anything has the user hit the rate limit. 2413 # Before we do anything has the user hit the rate limit.
2411 2414
2523 self.client.setHeader( 2526 self.client.setHeader(
2524 "Allow", 2527 "Allow",
2525 "OPTIONS, GET, POST, PUT, DELETE, PATCH" 2528 "OPTIONS, GET, POST, PUT, DELETE, PATCH"
2526 ) 2529 )
2527 2530
2528 # Is there an input.value with format json data? 2531 # Is there an input_payload.value with format json data?
2529 # If so turn it into an object that emulates enough 2532 # If so turn it into an object that emulates enough
2530 # of the FieldStorge methods/props to allow a response. 2533 # of the FieldStorge methods/props to allow a response.
2531 content_type_header = headers.get('Content-Type', None) 2534 content_type_header = headers.get('Content-Type', None)
2532 # python2 is str type, python3 is bytes 2535 # python2 is str type, python3 is bytes
2533 if type(input.value) in (str, bytes) and content_type_header: 2536 if type(input_payload.value) in (str, bytes) and content_type_header:
2534 # the structure of a content-type header 2537 # the structure of a content-type header
2535 # is complex: mime-type; options(charset ...) 2538 # is complex: mime-type; options(charset ...)
2536 # for now we just accept application/json. 2539 # for now we just accept application/json.
2537 # FIXME there should be a function: 2540 # FIXME there should be a function:
2538 # parse_content_type_header(content_type_header) 2541 # parse_content_type_header(content_type_header)
2542 # That way we could handle stuff like: 2545 # That way we could handle stuff like:
2543 # application/vnd.roundup-foo+json; charset=UTF8 2546 # application/vnd.roundup-foo+json; charset=UTF8
2544 # for example. 2547 # for example.
2545 if content_type_header.lower() == "application/json": 2548 if content_type_header.lower() == "application/json":
2546 try: 2549 try:
2547 input = SimulateFieldStorageFromJson(b2s(input.value)) 2550 input_payload = SimulateFieldStorageFromJson(b2s(input_payload.value))
2548 except ValueError as msg: 2551 except ValueError as msg:
2549 output = self.error_obj(400, msg) 2552 output = self.error_obj(400, msg)
2550 else: 2553 else:
2551 if method.upper() == "PATCH": 2554 if method.upper() == "PATCH":
2552 self.client.setHeader("Accept-Patch", 2555 self.client.setHeader("Accept-Patch",
2556 "Unable to process input of type %s" % 2559 "Unable to process input of type %s" %
2557 content_type_header) 2560 content_type_header)
2558 2561
2559 # check for pretty print 2562 # check for pretty print
2560 try: 2563 try:
2561 pretty_output = not input['@pretty'].value.lower() == "false" 2564 pretty_output = not input_payload['@pretty'].value.lower() == "false"
2562 # Can also return a TypeError ("not indexable") 2565 # Can also return a TypeError ("not indexable")
2563 # In case the FieldStorage could not parse the result 2566 # In case the FieldStorage could not parse the result
2564 except (KeyError, TypeError): 2567 except (KeyError, TypeError):
2565 pretty_output = True 2568 pretty_output = True
2566 2569
2567 # check for runtime statistics 2570 # check for runtime statistics
2568 try: 2571 try:
2569 # self.report_stats initialized to False 2572 # self.report_stats initialized to False
2570 self.report_stats = input['@stats'].value.lower() == "true" 2573 self.report_stats = input_payload['@stats'].value.lower() == "true"
2571 # Can also return a TypeError ("not indexable") 2574 # Can also return a TypeError ("not indexable")
2572 # In case the FieldStorage could not parse the result 2575 # In case the FieldStorage could not parse the result
2573 except (KeyError, TypeError): 2576 except (KeyError, TypeError):
2574 pass 2577 pass
2575 2578
2580 try: 2583 try:
2581 # FIXME: the version priority here is different 2584 # FIXME: the version priority here is different
2582 # from accept header. accept mime type in url 2585 # from accept header. accept mime type in url
2583 # takes priority over Accept header. Opposite here. 2586 # takes priority over Accept header. Opposite here.
2584 if not self.api_version: 2587 if not self.api_version:
2585 self.api_version = int(input['@apiver'].value) 2588 self.api_version = int(input_payload['@apiver'].value)
2586 # Can also return a TypeError ("not indexable") 2589 # Can also return a TypeError ("not indexable")
2587 # In case the FieldStorage could not parse the result 2590 # In case the FieldStorage could not parse the result
2588 except (KeyError, TypeError): 2591 except (KeyError, TypeError):
2589 self.api_version = None 2592 self.api_version = None
2590 except ValueError: 2593 except ValueError:
2591 output = self.error_obj(406, msg % input['@apiver'].value) 2594 output = self.error_obj(406, msg % input_payload['@apiver'].value)
2592 2595
2593 # by this time the API version is set. Error if we don't 2596 # by this time the API version is set. Error if we don't
2594 # support it? 2597 # support it?
2595 if self.api_version is None: 2598 if self.api_version is None:
2596 # FIXME: do we need to raise an error if client did not specify 2599 # FIXME: do we need to raise an error if client did not specify
2600 self.api_version = self.__default_api_version 2603 self.api_version = self.__default_api_version
2601 elif self.api_version not in self.__supported_api_versions: 2604 elif self.api_version not in self.__supported_api_versions:
2602 output = self.error_obj(406, msg % self.api_version) 2605 output = self.error_obj(406, msg % self.api_version)
2603 2606
2604 # sadly del doesn't work on FieldStorage which can be the type of 2607 # sadly del doesn't work on FieldStorage which can be the type of
2605 # input. So we have to ignore keys starting with @ at other 2608 # input_payload. So we have to ignore keys starting with @ at other
2606 # places in the code. 2609 # places in the code.
2607 # else: 2610 # else:
2608 # del(input['@apiver']) 2611 # del(input_payload['@apiver'])
2609 2612
2610 # Call the appropriate method 2613 # Call the appropriate method
2611 try: 2614 try:
2612 # If output was defined by a prior error 2615 # If output was defined by a prior error
2613 # condition skip call 2616 # condition skip call
2614 if not output: 2617 if not output:
2615 output = Routing.execute(self, uri, method, input) 2618 output = Routing.execute(self, uri, method, input_payload)
2616 except NotFound as msg: 2619 except NotFound as msg:
2617 output = self.error_obj(404, msg) 2620 output = self.error_obj(404, msg)
2618 except Reject as msg: 2621 except Reject as msg:
2619 output = self.error_obj(405, msg.args[0]) 2622 output = self.error_obj(405, msg.args[0])
2620 self.client.setHeader("Allow", msg.args[1]) 2623 self.client.setHeader("Allow", msg.args[1])

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