Mercurial > p > roundup > code
comparison roundup/rest.py @ 5587:cb2b320fde16 REST-rebased
Added decorator to handle formatting output data
committer: Ralf Schlatterbeck <rsc@runtux.com>
| author | Chau Nguyen <dangchau1991@yahoo.com> |
|---|---|
| date | Wed, 30 Jan 2019 10:26:35 +0100 |
| parents | 53098db851f2 |
| children | 6b3a9655a7d9 |
comparison
equal
deleted
inserted
replaced
| 5586:8f2fbc88e155 | 5587:cb2b320fde16 |
|---|---|
| 87 except hyperdb.HyperdbValueError, msg: | 87 except hyperdb.HyperdbValueError, msg: |
| 88 raise UsageError(msg) | 88 raise UsageError(msg) |
| 89 | 89 |
| 90 return prop | 90 return prop |
| 91 | 91 |
| 92 @staticmethod | 92 def _data_decorator(func): |
| 93 def error_obj(status, msg, source=None): | 93 """Wrap the returned data into an object..""" |
| 94 """Wrap the error data into an object. This function is temporally and | 94 def format_object(self, *args, **kwargs): |
| 95 will be changed to a decorator later.""" | 95 try: |
| 96 result = { | 96 code, data = func(self, *args, **kwargs) |
| 97 'error': { | 97 except IndexError, msg: |
| 98 'status': status, | 98 code = 404 |
| 99 'msg': msg | 99 data = msg |
| 100 } | 100 except Unauthorised, msg: |
| 101 } | 101 code = 403 |
| 102 if source is not None: | 102 data = msg |
| 103 result['error']['source'] = source | 103 except (hyperdb.DesignatorError, UsageError), msg: |
| 104 | 104 code = 400 |
| 105 return result | 105 data = msg |
| 106 | 106 except (AttributeError, Reject), msg: |
| 107 @staticmethod | 107 code = 405 |
| 108 def data_obj(data): | 108 data = msg |
| 109 """Wrap the returned data into an object. This function is temporally | 109 except ValueError, msg: |
| 110 and will be changed to a decorator later.""" | 110 code = 409 |
| 111 result = { | 111 data = msg |
| 112 'data': data | 112 except NotImplementedError: |
| 113 } | 113 code = 402 # nothing to pay, just a mark for debugging purpose |
| 114 return result | 114 data = 'Method under development' |
| 115 | 115 except: |
| 116 exc, val, tb = sys.exc_info() | |
| 117 code = 400 | |
| 118 # if self.DEBUG_MODE in roundup_server | |
| 119 # else data = 'An error occurred. Please check...', | |
| 120 data = val | |
| 121 | |
| 122 # out to the logfile | |
| 123 print 'EXCEPTION AT', time.ctime() | |
| 124 traceback.print_exc() | |
| 125 | |
| 126 self.client.response_code = code | |
| 127 if code >= 400: # any error require error format | |
| 128 result = { | |
| 129 'error': { | |
| 130 'status': code, | |
| 131 'msg': data | |
| 132 } | |
| 133 } | |
| 134 else: | |
| 135 result = { | |
| 136 'data': data | |
| 137 } | |
| 138 return result | |
| 139 return format_object | |
| 140 | |
| 141 @_data_decorator | |
| 116 def get_collection(self, class_name, input): | 142 def get_collection(self, class_name, input): |
| 117 """GET resource from class URI. | 143 """GET resource from class URI. |
| 118 | 144 |
| 119 This function returns only items have View permission | 145 This function returns only items have View permission |
| 120 class_name should be valid already | 146 class_name should be valid already |
| 144 ) | 170 ) |
| 145 ] | 171 ] |
| 146 self.client.setHeader("X-Count-Total", str(len(result))) | 172 self.client.setHeader("X-Count-Total", str(len(result))) |
| 147 return 200, result | 173 return 200, result |
| 148 | 174 |
| 175 @_data_decorator | |
| 149 def get_element(self, class_name, item_id, input): | 176 def get_element(self, class_name, item_id, input): |
| 150 """GET resource from object URI. | 177 """GET resource from object URI. |
| 151 | 178 |
| 152 This function returns only properties have View permission | 179 This function returns only properties have View permission |
| 153 class_name and item_id should be valid already | 180 class_name and item_id should be valid already |
| 189 'attributes': dict(result) | 216 'attributes': dict(result) |
| 190 } | 217 } |
| 191 | 218 |
| 192 return 200, result | 219 return 200, result |
| 193 | 220 |
| 221 @_data_decorator | |
| 194 def get_attribute(self, class_name, item_id, attr_name, input): | 222 def get_attribute(self, class_name, item_id, attr_name, input): |
| 195 """GET resource from attribute URI. | 223 """GET resource from attribute URI. |
| 196 | 224 |
| 197 This function returns only attribute has View permission | 225 This function returns only attribute has View permission |
| 198 class_name should be valid already | 226 class_name should be valid already |
| 229 'data': data | 257 'data': data |
| 230 } | 258 } |
| 231 | 259 |
| 232 return 200, result | 260 return 200, result |
| 233 | 261 |
| 262 @_data_decorator | |
| 234 def post_collection(self, class_name, input): | 263 def post_collection(self, class_name, input): |
| 235 """POST a new object to a class | 264 """POST a new object to a class |
| 236 | 265 |
| 237 If the item is successfully created, the "Location" header will also | 266 If the item is successfully created, the "Location" header will also |
| 238 contain the link to the created object | 267 contain the link to the created object |
| 288 'id': item_id, | 317 'id': item_id, |
| 289 'link': link | 318 'link': link |
| 290 } | 319 } |
| 291 return 201, result | 320 return 201, result |
| 292 | 321 |
| 322 @_data_decorator | |
| 293 def post_element(self, class_name, item_id, input): | 323 def post_element(self, class_name, item_id, input): |
| 294 """POST to an object of a class is not allowed""" | 324 """POST to an object of a class is not allowed""" |
| 295 raise Reject('POST to an item is not allowed') | 325 raise Reject('POST to an item is not allowed') |
| 296 | 326 |
| 327 @_data_decorator | |
| 297 def post_attribute(self, class_name, item_id, attr_name, input): | 328 def post_attribute(self, class_name, item_id, attr_name, input): |
| 298 """POST to an attribute of an object is not allowed""" | 329 """POST to an attribute of an object is not allowed""" |
| 299 raise Reject('POST to an attribute is not allowed') | 330 raise Reject('POST to an attribute is not allowed') |
| 300 | 331 |
| 332 @_data_decorator | |
| 301 def put_collection(self, class_name, input): | 333 def put_collection(self, class_name, input): |
| 302 """PUT a class is not allowed""" | 334 """PUT a class is not allowed""" |
| 303 raise Reject('PUT a class is not allowed') | 335 raise Reject('PUT a class is not allowed') |
| 304 | 336 |
| 337 @_data_decorator | |
| 305 def put_element(self, class_name, item_id, input): | 338 def put_element(self, class_name, item_id, input): |
| 306 """PUT a new content to an object | 339 """PUT a new content to an object |
| 307 | 340 |
| 308 Replace the content of the existing object | 341 Replace the content of the existing object |
| 309 | 342 |
| 344 'link': self.base_path + class_name + item_id, | 377 'link': self.base_path + class_name + item_id, |
| 345 'attribute': result | 378 'attribute': result |
| 346 } | 379 } |
| 347 return 200, result | 380 return 200, result |
| 348 | 381 |
| 382 @_data_decorator | |
| 349 def put_attribute(self, class_name, item_id, attr_name, input): | 383 def put_attribute(self, class_name, item_id, attr_name, input): |
| 350 """PUT an attribute to an object | 384 """PUT an attribute to an object |
| 351 | 385 |
| 352 Args: | 386 Args: |
| 353 class_name (string): class name of the resource (Ex: issue, msg) | 387 class_name (string): class name of the resource (Ex: issue, msg) |
| 392 'attribute': result | 426 'attribute': result |
| 393 } | 427 } |
| 394 | 428 |
| 395 return 200, result | 429 return 200, result |
| 396 | 430 |
| 431 @_data_decorator | |
| 397 def delete_collection(self, class_name, input): | 432 def delete_collection(self, class_name, input): |
| 398 """DELETE all objects in a class | 433 """DELETE all objects in a class |
| 399 | 434 |
| 400 Args: | 435 Args: |
| 401 class_name (string): class name of the resource (Ex: issue, msg) | 436 class_name (string): class name of the resource (Ex: issue, msg) |
| 431 'count': count | 466 'count': count |
| 432 } | 467 } |
| 433 | 468 |
| 434 return 200, result | 469 return 200, result |
| 435 | 470 |
| 471 @_data_decorator | |
| 436 def delete_element(self, class_name, item_id, input): | 472 def delete_element(self, class_name, item_id, input): |
| 437 """DELETE an object in a class | 473 """DELETE an object in a class |
| 438 | 474 |
| 439 Args: | 475 Args: |
| 440 class_name (string): class name of the resource (Ex: issue, msg) | 476 class_name (string): class name of the resource (Ex: issue, msg) |
| 459 'status': 'ok' | 495 'status': 'ok' |
| 460 } | 496 } |
| 461 | 497 |
| 462 return 200, result | 498 return 200, result |
| 463 | 499 |
| 500 @_data_decorator | |
| 464 def delete_attribute(self, class_name, item_id, attr_name, input): | 501 def delete_attribute(self, class_name, item_id, attr_name, input): |
| 465 """DELETE an attribute in a object by setting it to None or empty | 502 """DELETE an attribute in a object by setting it to None or empty |
| 466 | 503 |
| 467 Args: | 504 Args: |
| 468 class_name (string): class name of the resource (Ex: issue, msg) | 505 class_name (string): class name of the resource (Ex: issue, msg) |
| 501 'status': 'ok' | 538 'status': 'ok' |
| 502 } | 539 } |
| 503 | 540 |
| 504 return 200, result | 541 return 200, result |
| 505 | 542 |
| 543 @_data_decorator | |
| 506 def patch_collection(self, class_name, input): | 544 def patch_collection(self, class_name, input): |
| 507 """PATCH a class is not allowed""" | 545 """PATCH a class is not allowed""" |
| 508 raise Reject('PATCH a class is not allowed') | 546 raise Reject('PATCH a class is not allowed') |
| 509 | 547 |
| 548 @_data_decorator | |
| 510 def patch_element(self, class_name, item_id, input): | 549 def patch_element(self, class_name, item_id, input): |
| 511 """PATCH an object | 550 """PATCH an object |
| 512 | 551 |
| 513 Patch an element using 3 operators | 552 Patch an element using 3 operators |
| 514 ADD : Append new value to the object's attribute | 553 ADD : Append new value to the object's attribute |
| 571 'link': self.base_path + class_name + item_id, | 610 'link': self.base_path + class_name + item_id, |
| 572 'attribute': result | 611 'attribute': result |
| 573 } | 612 } |
| 574 return 200, result | 613 return 200, result |
| 575 | 614 |
| 615 @_data_decorator | |
| 576 def patch_attribute(self, class_name, item_id, attr_name, input): | 616 def patch_attribute(self, class_name, item_id, attr_name, input): |
| 577 """PATCH an attribute of an object | 617 """PATCH an attribute of an object |
| 578 | 618 |
| 579 Patch an element using 3 operators | 619 Patch an element using 3 operators |
| 580 ADD : Append new value to the attribute | 620 ADD : Append new value to the attribute |
| 643 'link': self.base_path + class_name + item_id, | 683 'link': self.base_path + class_name + item_id, |
| 644 'attribute': result | 684 'attribute': result |
| 645 } | 685 } |
| 646 return 200, result | 686 return 200, result |
| 647 | 687 |
| 688 @_data_decorator | |
| 648 def options_collection(self, class_name, input): | 689 def options_collection(self, class_name, input): |
| 649 """OPTION return the HTTP Header for the class uri | 690 """OPTION return the HTTP Header for the class uri |
| 650 | 691 |
| 651 Returns: | 692 Returns: |
| 652 int: http status code 204 (No content) | 693 int: http status code 204 (No content) |
| 653 body (string): an empty string | 694 body (string): an empty string |
| 654 """ | 695 """ |
| 655 return 204, "" | 696 return 204, "" |
| 656 | 697 |
| 698 @_data_decorator | |
| 657 def options_element(self, class_name, item_id, input): | 699 def options_element(self, class_name, item_id, input): |
| 658 """OPTION return the HTTP Header for the object uri | 700 """OPTION return the HTTP Header for the object uri |
| 659 | 701 |
| 660 Returns: | 702 Returns: |
| 661 int: http status code 204 (No content) | 703 int: http status code 204 (No content) |
| 665 "Accept-Patch", | 707 "Accept-Patch", |
| 666 "application/x-www-form-urlencoded, multipart/form-data" | 708 "application/x-www-form-urlencoded, multipart/form-data" |
| 667 ) | 709 ) |
| 668 return 204, "" | 710 return 204, "" |
| 669 | 711 |
| 712 @_data_decorator | |
| 670 def option_attribute(self, class_name, item_id, attr_name, input): | 713 def option_attribute(self, class_name, item_id, attr_name, input): |
| 671 """OPTION return the HTTP Header for the attribute uri | 714 """OPTION return the HTTP Header for the attribute uri |
| 672 | 715 |
| 673 Returns: | 716 Returns: |
| 674 int: http status code 204 (No content) | 717 int: http status code 204 (No content) |
| 734 "HEAD, OPTIONS, GET, PUT, DELETE, PATCH" | 777 "HEAD, OPTIONS, GET, PUT, DELETE, PATCH" |
| 735 ) | 778 ) |
| 736 | 779 |
| 737 # Call the appropriate method | 780 # Call the appropriate method |
| 738 output = None | 781 output = None |
| 739 try: | 782 if resource_uri in self.db.classes: |
| 740 if resource_uri in self.db.classes: | 783 output = getattr( |
| 741 response_code, output = getattr( | 784 self, "%s_collection" % method.lower() |
| 742 self, "%s_collection" % method.lower() | 785 )(resource_uri, input) |
| 743 )(resource_uri, input) | 786 else: |
| 787 class_name, item_id = hyperdb.splitDesignator(resource_uri) | |
| 788 if len(uri_split) == 3: | |
| 789 output = getattr( | |
| 790 self, "%s_attribute" % method.lower() | |
| 791 )(class_name, item_id, uri_split[2], input) | |
| 744 else: | 792 else: |
| 745 class_name, item_id = hyperdb.splitDesignator(resource_uri) | 793 output = getattr( |
| 746 if len(uri_split) == 3: | 794 self, "%s_element" % method.lower() |
| 747 response_code, output = getattr( | 795 )(class_name, item_id, input) |
| 748 self, "%s_attribute" % method.lower() | 796 |
| 749 )(class_name, item_id, uri_split[2], input) | |
| 750 else: | |
| 751 response_code, output = getattr( | |
| 752 self, "%s_element" % method.lower() | |
| 753 )(class_name, item_id, input) | |
| 754 output = RestfulInstance.data_obj(output) | |
| 755 self.client.response_code = response_code | |
| 756 except IndexError, msg: | |
| 757 output = RestfulInstance.error_obj(404, msg) | |
| 758 self.client.response_code = 404 | |
| 759 except Unauthorised, msg: | |
| 760 output = RestfulInstance.error_obj(403, msg) | |
| 761 self.client.response_code = 403 | |
| 762 except (hyperdb.DesignatorError, UsageError), msg: | |
| 763 output = RestfulInstance.error_obj(400, msg) | |
| 764 self.client.response_code = 400 | |
| 765 except (AttributeError, Reject), msg: | |
| 766 output = RestfulInstance.error_obj(405, msg) | |
| 767 self.client.response_code = 405 | |
| 768 except ValueError, msg: | |
| 769 output = RestfulInstance.error_obj(409, msg) | |
| 770 self.client.response_code = 409 | |
| 771 except NotImplementedError: | |
| 772 output = RestfulInstance.error_obj(402, 'Method under development') | |
| 773 self.client.response_code = 402 | |
| 774 # nothing to pay, just a mark for debugging purpose | |
| 775 except: | |
| 776 # if self.DEBUG_MODE in roundup_server | |
| 777 # else msg = 'An error occurred. Please check...', | |
| 778 exc, val, tb = sys.exc_info() | |
| 779 output = RestfulInstance.error_obj(400, val) | |
| 780 self.client.response_code = 400 | |
| 781 | |
| 782 # out to the logfile, it would be nice if the server do it for me | |
| 783 print 'EXCEPTION AT', time.ctime() | |
| 784 traceback.print_exc() | |
| 785 finally: | |
| 786 if format_output.lower() == "json": | 797 if format_output.lower() == "json": |
| 787 self.client.setHeader("Content-Type", "application/json") | 798 self.client.setHeader("Content-Type", "application/json") |
| 788 if pretty_output: | 799 if pretty_output: |
| 789 indent = 4 | 800 indent = 4 |
| 790 else: | 801 else: |
