Mercurial > p > roundup > code
comparison roundup/rest.py @ 5597:de9933cfcfc4 REST-rebased
Added routing decorator
committer: Ralf Schlatterbeck <rsc@runtux.com>
| author | Chau Nguyen <dangchau1991@yahoo.com> |
|---|---|
| date | Wed, 30 Jan 2019 10:26:35 +0100 |
| parents | 55fa81de6a57 |
| children | be81e8cca38c |
comparison
equal
deleted
inserted
replaced
| 5596:55fa81de6a57 | 5597:de9933cfcfc4 |
|---|---|
| 10 import json | 10 import json |
| 11 import pprint | 11 import pprint |
| 12 import sys | 12 import sys |
| 13 import time | 13 import time |
| 14 import traceback | 14 import traceback |
| 15 import xml | 15 import re |
| 16 | |
| 16 from roundup import hyperdb | 17 from roundup import hyperdb |
| 17 from roundup import date | 18 from roundup import date |
| 18 from roundup.exceptions import * | 19 from roundup.exceptions import * |
| 20 from roundup.cgi.exceptions import * | |
| 19 | 21 |
| 20 | 22 |
| 21 def _data_decorator(func): | 23 def _data_decorator(func): |
| 22 """Wrap the returned data into an object.""" | 24 """Wrap the returned data into an object.""" |
| 23 def format_object(self, *args, **kwargs): | 25 def format_object(self, *args, **kwargs): |
| 24 # get the data / error from function | 26 # get the data / error from function |
| 25 try: | 27 try: |
| 26 code, data = func(self, *args, **kwargs) | 28 code, data = func(self, *args, **kwargs) |
| 29 except NotFound, msg: | |
| 30 code = 404 | |
| 31 data = msg | |
| 27 except IndexError, msg: | 32 except IndexError, msg: |
| 28 code = 404 | 33 code = 404 |
| 29 data = msg | 34 data = msg |
| 30 except Unauthorised, msg: | 35 except Unauthorised, msg: |
| 31 code = 403 | 36 code = 403 |
| 131 result.append((media_type, dict(media_params), q)) | 136 result.append((media_type, dict(media_params), q)) |
| 132 result.sort(lambda x, y: -cmp(x[2], y[2])) | 137 result.sort(lambda x, y: -cmp(x[2], y[2])) |
| 133 return result | 138 return result |
| 134 | 139 |
| 135 | 140 |
| 141 class Routing(object): | |
| 142 __route_map = {} | |
| 143 __var_to_regex = re.compile(r"<:(\w+)>") | |
| 144 url_to_regex = r"([\w.\-~!$&'()*+,;=:@\%%]+)" | |
| 145 | |
| 146 @classmethod | |
| 147 def route(cls, rule, methods='GET'): | |
| 148 """A decorator that is used to register a view function for a | |
| 149 given URL rule: | |
| 150 @self.route('/') | |
| 151 def index(): | |
| 152 return 'Hello World' | |
| 153 | |
| 154 rest/ will be added to the beginning of the url string | |
| 155 | |
| 156 Args: | |
| 157 rule (string): the URL rule | |
| 158 methods (string or tuple or list): the http method | |
| 159 """ | |
| 160 # strip the '/' character from rule string | |
| 161 rule = rule.strip('/') | |
| 162 | |
| 163 # add 'rest/' to the rule string | |
| 164 if not rule.startswith('rest/'): | |
| 165 rule = '^rest/' + rule + '$' | |
| 166 | |
| 167 if isinstance(methods, basestring): # convert string to tuple | |
| 168 methods = (methods,) | |
| 169 methods = set(item.upper() for item in methods) | |
| 170 | |
| 171 # convert a rule to a compiled regex object | |
| 172 # so /data/<:class>/<:id> will become | |
| 173 # /data/([charset]+)/([charset]+) | |
| 174 # and extract the variable names to a list [(class), (id)] | |
| 175 func_vars = cls.__var_to_regex.findall(rule) | |
| 176 rule = re.compile(cls.__var_to_regex.sub(cls.url_to_regex, rule)) | |
| 177 | |
| 178 # then we decorate it: | |
| 179 # route_map[regex][method] = func | |
| 180 def decorator(func): | |
| 181 rule_route = cls.__route_map.get(rule, {}) | |
| 182 func_obj = { | |
| 183 'func': func, | |
| 184 'vars': func_vars | |
| 185 } | |
| 186 for method in methods: | |
| 187 rule_route[method] = func_obj | |
| 188 cls.__route_map[rule] = rule_route | |
| 189 return func | |
| 190 return decorator | |
| 191 | |
| 192 @classmethod | |
| 193 def execute(cls, instance, path, method, input): | |
| 194 # format the input | |
| 195 path = path.strip('/').lower() | |
| 196 method = method.upper() | |
| 197 | |
| 198 # find the rule match the path | |
| 199 # then get handler match the method | |
| 200 for path_regex in cls.__route_map: | |
| 201 match_obj = path_regex.match(path) | |
| 202 if match_obj: | |
| 203 try: | |
| 204 func_obj = cls.__route_map[path_regex][method] | |
| 205 except KeyError: | |
| 206 raise Reject('Method %s not allowed' % method) | |
| 207 | |
| 208 # retrieve the vars list and the function caller | |
| 209 list_vars = func_obj['vars'] | |
| 210 func = func_obj['func'] | |
| 211 | |
| 212 # zip the varlist into a dictionary, and pass it to the caller | |
| 213 args = dict(zip(list_vars, match_obj.groups())) | |
| 214 args['input'] = input | |
| 215 return func(instance, **args) | |
| 216 raise NotFound('Nothing matches the given URI') | |
| 217 | |
| 218 | |
| 136 class RestfulInstance(object): | 219 class RestfulInstance(object): |
| 137 """The RestfulInstance performs REST request from the client""" | 220 """The RestfulInstance performs REST request from the client""" |
| 138 | 221 |
| 139 __default_patch_op = "replace" # default operator for PATCH method | 222 __default_patch_op = "replace" # default operator for PATCH method |
| 140 __accepted_content_type = { | 223 __accepted_content_type = { |
| 278 else: | 361 else: |
| 279 raise UsageError('PATCH Operation %s is not allowed' % op) | 362 raise UsageError('PATCH Operation %s is not allowed' % op) |
| 280 | 363 |
| 281 return result | 364 return result |
| 282 | 365 |
| 366 @Routing.route("/data/<:class_name>", 'GET') | |
| 283 @_data_decorator | 367 @_data_decorator |
| 284 def get_collection(self, class_name, input): | 368 def get_collection(self, class_name, input): |
| 285 """GET resource from class URI. | 369 """GET resource from class URI. |
| 286 | 370 |
| 287 This function returns only items have View permission | 371 This function returns only items have View permission |
| 295 int: http status code 200 (OK) | 379 int: http status code 200 (OK) |
| 296 list: list of reference item in the class | 380 list: list of reference item in the class |
| 297 id: id of the object | 381 id: id of the object |
| 298 link: path to the object | 382 link: path to the object |
| 299 """ | 383 """ |
| 384 if class_name not in self.db.classes: | |
| 385 raise NotFound('Class %s not found' % class_name) | |
| 300 if not self.db.security.hasPermission( | 386 if not self.db.security.hasPermission( |
| 301 'View', self.db.getuid(), class_name | 387 'View', self.db.getuid(), class_name |
| 302 ): | 388 ): |
| 303 raise Unauthorised('Permission to view %s denied' % class_name) | 389 raise Unauthorised('Permission to view %s denied' % class_name) |
| 304 | 390 |
| 346 result = result[page_start:page_end] | 432 result = result[page_start:page_end] |
| 347 | 433 |
| 348 self.client.setHeader("X-Count-Total", str(len(result))) | 434 self.client.setHeader("X-Count-Total", str(len(result))) |
| 349 return 200, result | 435 return 200, result |
| 350 | 436 |
| 437 @Routing.route("/data/<:class_name>/<:item_id>", 'GET') | |
| 351 @_data_decorator | 438 @_data_decorator |
| 352 def get_element(self, class_name, item_id, input): | 439 def get_element(self, class_name, item_id, input): |
| 353 """GET resource from object URI. | 440 """GET resource from object URI. |
| 354 | 441 |
| 355 This function returns only properties have View permission | 442 This function returns only properties have View permission |
| 366 id: id of the object | 453 id: id of the object |
| 367 type: class name of the object | 454 type: class name of the object |
| 368 link: link to the object | 455 link: link to the object |
| 369 attributes: a dictionary represent the attributes of the object | 456 attributes: a dictionary represent the attributes of the object |
| 370 """ | 457 """ |
| 458 if class_name not in self.db.classes: | |
| 459 raise NotFound('Class %s not found' % class_name) | |
| 371 if not self.db.security.hasPermission( | 460 if not self.db.security.hasPermission( |
| 372 'View', self.db.getuid(), class_name, itemid=item_id | 461 'View', self.db.getuid(), class_name, itemid=item_id |
| 373 ): | 462 ): |
| 374 raise Unauthorised( | 463 raise Unauthorised( |
| 375 'Permission to view %s%s denied' % (class_name, item_id) | 464 'Permission to view %s%s denied' % (class_name, item_id) |
| 392 'attributes': dict(result) | 481 'attributes': dict(result) |
| 393 } | 482 } |
| 394 | 483 |
| 395 return 200, result | 484 return 200, result |
| 396 | 485 |
| 486 @Routing.route("/data/<:class_name>/<:item_id>/<:attr_name>", 'GET') | |
| 397 @_data_decorator | 487 @_data_decorator |
| 398 def get_attribute(self, class_name, item_id, attr_name, input): | 488 def get_attribute(self, class_name, item_id, attr_name, input): |
| 399 """GET resource from attribute URI. | 489 """GET resource from attribute URI. |
| 400 | 490 |
| 401 This function returns only attribute has View permission | 491 This function returns only attribute has View permission |
| 413 id: id of the object | 503 id: id of the object |
| 414 type: class name of the attribute | 504 type: class name of the attribute |
| 415 link: link to the attribute | 505 link: link to the attribute |
| 416 data: data of the requested attribute | 506 data: data of the requested attribute |
| 417 """ | 507 """ |
| 508 if class_name not in self.db.classes: | |
| 509 raise NotFound('Class %s not found' % class_name) | |
| 418 if not self.db.security.hasPermission( | 510 if not self.db.security.hasPermission( |
| 419 'View', self.db.getuid(), class_name, attr_name, item_id | 511 'View', self.db.getuid(), class_name, attr_name, item_id |
| 420 ): | 512 ): |
| 421 raise Unauthorised( | 513 raise Unauthorised( |
| 422 'Permission to view %s%s %s denied' % | 514 'Permission to view %s%s %s denied' % |
| 433 'data': data | 525 'data': data |
| 434 } | 526 } |
| 435 | 527 |
| 436 return 200, result | 528 return 200, result |
| 437 | 529 |
| 530 @Routing.route("/data/<:class_name>", 'POST') | |
| 438 @_data_decorator | 531 @_data_decorator |
| 439 def post_collection(self, class_name, input): | 532 def post_collection(self, class_name, input): |
| 440 """POST a new object to a class | 533 """POST a new object to a class |
| 441 | 534 |
| 442 If the item is successfully created, the "Location" header will also | 535 If the item is successfully created, the "Location" header will also |
| 450 int: http status code 201 (Created) | 543 int: http status code 201 (Created) |
| 451 dict: a reference item to the created object | 544 dict: a reference item to the created object |
| 452 id: id of the object | 545 id: id of the object |
| 453 link: path to the object | 546 link: path to the object |
| 454 """ | 547 """ |
| 548 if class_name not in self.db.classes: | |
| 549 raise NotFound('Class %s not found' % class_name) | |
| 455 if not self.db.security.hasPermission( | 550 if not self.db.security.hasPermission( |
| 456 'Create', self.db.getuid(), class_name | 551 'Create', self.db.getuid(), class_name |
| 457 ): | 552 ): |
| 458 raise Unauthorised('Permission to create %s denied' % class_name) | 553 raise Unauthorised('Permission to create %s denied' % class_name) |
| 459 | 554 |
| 493 'id': item_id, | 588 'id': item_id, |
| 494 'link': link | 589 'link': link |
| 495 } | 590 } |
| 496 return 201, result | 591 return 201, result |
| 497 | 592 |
| 498 @_data_decorator | 593 @Routing.route("/data/<:class_name>/<:item_id>", 'PUT') |
| 499 def post_element(self, class_name, item_id, input): | |
| 500 """POST to an object of a class is not allowed""" | |
| 501 raise Reject('POST to an item is not allowed') | |
| 502 | |
| 503 @_data_decorator | |
| 504 def post_attribute(self, class_name, item_id, attr_name, input): | |
| 505 """POST to an attribute of an object is not allowed""" | |
| 506 raise Reject('POST to an attribute is not allowed') | |
| 507 | |
| 508 @_data_decorator | |
| 509 def put_collection(self, class_name, input): | |
| 510 """PUT a class is not allowed""" | |
| 511 raise Reject('PUT a class is not allowed') | |
| 512 | |
| 513 @_data_decorator | 594 @_data_decorator |
| 514 def put_element(self, class_name, item_id, input): | 595 def put_element(self, class_name, item_id, input): |
| 515 """PUT a new content to an object | 596 """PUT a new content to an object |
| 516 | 597 |
| 517 Replace the content of the existing object | 598 Replace the content of the existing object |
| 528 type: class name of the object | 609 type: class name of the object |
| 529 link: link to the object | 610 link: link to the object |
| 530 attributes: a dictionary represent only changed attributes of | 611 attributes: a dictionary represent only changed attributes of |
| 531 the object | 612 the object |
| 532 """ | 613 """ |
| 614 if class_name not in self.db.classes: | |
| 615 raise NotFound('Class %s not found' % class_name) | |
| 533 class_obj = self.db.getclass(class_name) | 616 class_obj = self.db.getclass(class_name) |
| 534 | 617 |
| 535 props = self.props_from_args(class_obj, input.value, item_id) | 618 props = self.props_from_args(class_obj, input.value, item_id) |
| 536 for p in props.iterkeys(): | 619 for p in props.iterkeys(): |
| 537 if not self.db.security.hasPermission( | 620 if not self.db.security.hasPermission( |
| 553 'link': self.base_path + class_name + item_id, | 636 'link': self.base_path + class_name + item_id, |
| 554 'attribute': result | 637 'attribute': result |
| 555 } | 638 } |
| 556 return 200, result | 639 return 200, result |
| 557 | 640 |
| 641 @Routing.route("/data/<:class_name>/<:item_id>/<:attr_name>", 'PUT') | |
| 558 @_data_decorator | 642 @_data_decorator |
| 559 def put_attribute(self, class_name, item_id, attr_name, input): | 643 def put_attribute(self, class_name, item_id, attr_name, input): |
| 560 """PUT an attribute to an object | 644 """PUT an attribute to an object |
| 561 | 645 |
| 562 Args: | 646 Args: |
| 572 type: class name of the object | 656 type: class name of the object |
| 573 link: link to the object | 657 link: link to the object |
| 574 attributes: a dictionary represent only changed attributes of | 658 attributes: a dictionary represent only changed attributes of |
| 575 the object | 659 the object |
| 576 """ | 660 """ |
| 661 if class_name not in self.db.classes: | |
| 662 raise NotFound('Class %s not found' % class_name) | |
| 577 if not self.db.security.hasPermission( | 663 if not self.db.security.hasPermission( |
| 578 'Edit', self.db.getuid(), class_name, attr_name, item_id | 664 'Edit', self.db.getuid(), class_name, attr_name, item_id |
| 579 ): | 665 ): |
| 580 raise Unauthorised( | 666 raise Unauthorised( |
| 581 'Permission to edit %s%s %s denied' % | 667 'Permission to edit %s%s %s denied' % |
| 602 'attribute': result | 688 'attribute': result |
| 603 } | 689 } |
| 604 | 690 |
| 605 return 200, result | 691 return 200, result |
| 606 | 692 |
| 693 @Routing.route("/data/<:class_name>", 'DELETE') | |
| 607 @_data_decorator | 694 @_data_decorator |
| 608 def delete_collection(self, class_name, input): | 695 def delete_collection(self, class_name, input): |
| 609 """DELETE all objects in a class | 696 """DELETE all objects in a class |
| 610 | 697 |
| 611 Args: | 698 Args: |
| 616 int: http status code 200 (OK) | 703 int: http status code 200 (OK) |
| 617 dict: | 704 dict: |
| 618 status (string): 'ok' | 705 status (string): 'ok' |
| 619 count (int): number of deleted objects | 706 count (int): number of deleted objects |
| 620 """ | 707 """ |
| 708 if class_name not in self.db.classes: | |
| 709 raise NotFound('Class %s not found' % class_name) | |
| 621 if not self.db.security.hasPermission( | 710 if not self.db.security.hasPermission( |
| 622 'Delete', self.db.getuid(), class_name | 711 'Delete', self.db.getuid(), class_name |
| 623 ): | 712 ): |
| 624 raise Unauthorised('Permission to delete %s denied' % class_name) | 713 raise Unauthorised('Permission to delete %s denied' % class_name) |
| 625 | 714 |
| 642 'count': count | 731 'count': count |
| 643 } | 732 } |
| 644 | 733 |
| 645 return 200, result | 734 return 200, result |
| 646 | 735 |
| 736 @Routing.route("/data/<:class_name>/<:item_id>", 'DELETE') | |
| 647 @_data_decorator | 737 @_data_decorator |
| 648 def delete_element(self, class_name, item_id, input): | 738 def delete_element(self, class_name, item_id, input): |
| 649 """DELETE an object in a class | 739 """DELETE an object in a class |
| 650 | 740 |
| 651 Args: | 741 Args: |
| 656 Returns: | 746 Returns: |
| 657 int: http status code 200 (OK) | 747 int: http status code 200 (OK) |
| 658 dict: | 748 dict: |
| 659 status (string): 'ok' | 749 status (string): 'ok' |
| 660 """ | 750 """ |
| 751 if class_name not in self.db.classes: | |
| 752 raise NotFound('Class %s not found' % class_name) | |
| 661 if not self.db.security.hasPermission( | 753 if not self.db.security.hasPermission( |
| 662 'Delete', self.db.getuid(), class_name, itemid=item_id | 754 'Delete', self.db.getuid(), class_name, itemid=item_id |
| 663 ): | 755 ): |
| 664 raise Unauthorised( | 756 raise Unauthorised( |
| 665 'Permission to delete %s %s denied' % (class_name, item_id) | 757 'Permission to delete %s %s denied' % (class_name, item_id) |
| 671 'status': 'ok' | 763 'status': 'ok' |
| 672 } | 764 } |
| 673 | 765 |
| 674 return 200, result | 766 return 200, result |
| 675 | 767 |
| 768 @Routing.route("/data/<:class_name>/<:item_id>/<:attr_name>", 'DELETE') | |
| 676 @_data_decorator | 769 @_data_decorator |
| 677 def delete_attribute(self, class_name, item_id, attr_name, input): | 770 def delete_attribute(self, class_name, item_id, attr_name, input): |
| 678 """DELETE an attribute in a object by setting it to None or empty | 771 """DELETE an attribute in a object by setting it to None or empty |
| 679 | 772 |
| 680 Args: | 773 Args: |
| 686 Returns: | 779 Returns: |
| 687 int: http status code 200 (OK) | 780 int: http status code 200 (OK) |
| 688 dict: | 781 dict: |
| 689 status (string): 'ok' | 782 status (string): 'ok' |
| 690 """ | 783 """ |
| 784 if class_name not in self.db.classes: | |
| 785 raise NotFound('Class %s not found' % class_name) | |
| 691 if not self.db.security.hasPermission( | 786 if not self.db.security.hasPermission( |
| 692 'Edit', self.db.getuid(), class_name, attr_name, item_id | 787 'Edit', self.db.getuid(), class_name, attr_name, item_id |
| 693 ): | 788 ): |
| 694 raise Unauthorised( | 789 raise Unauthorised( |
| 695 'Permission to delete %s%s %s denied' % | 790 'Permission to delete %s%s %s denied' % |
| 714 'status': 'ok' | 809 'status': 'ok' |
| 715 } | 810 } |
| 716 | 811 |
| 717 return 200, result | 812 return 200, result |
| 718 | 813 |
| 719 @_data_decorator | 814 @Routing.route("/data/<:class_name>/<:item_id>", 'PATCH') |
| 720 def patch_collection(self, class_name, input): | |
| 721 """PATCH a class is not allowed""" | |
| 722 raise Reject('PATCH a class is not allowed') | |
| 723 | |
| 724 @_data_decorator | 815 @_data_decorator |
| 725 def patch_element(self, class_name, item_id, input): | 816 def patch_element(self, class_name, item_id, input): |
| 726 """PATCH an object | 817 """PATCH an object |
| 727 | 818 |
| 728 Patch an element using 3 operators | 819 Patch an element using 3 operators |
| 742 type: class name of the object | 833 type: class name of the object |
| 743 link: link to the object | 834 link: link to the object |
| 744 attributes: a dictionary represent only changed attributes of | 835 attributes: a dictionary represent only changed attributes of |
| 745 the object | 836 the object |
| 746 """ | 837 """ |
| 838 if class_name not in self.db.classes: | |
| 839 raise NotFound('Class %s not found' % class_name) | |
| 747 try: | 840 try: |
| 748 op = input['op'].value.lower() | 841 op = input['op'].value.lower() |
| 749 except KeyError: | 842 except KeyError: |
| 750 op = self.__default_patch_op | 843 op = self.__default_patch_op |
| 751 class_obj = self.db.getclass(class_name) | 844 class_obj = self.db.getclass(class_name) |
| 777 'link': self.base_path + class_name + item_id, | 870 'link': self.base_path + class_name + item_id, |
| 778 'attribute': result | 871 'attribute': result |
| 779 } | 872 } |
| 780 return 200, result | 873 return 200, result |
| 781 | 874 |
| 875 @Routing.route("/data/<:class_name>/<:item_id>/<:attr_name>", 'PATCH') | |
| 782 @_data_decorator | 876 @_data_decorator |
| 783 def patch_attribute(self, class_name, item_id, attr_name, input): | 877 def patch_attribute(self, class_name, item_id, attr_name, input): |
| 784 """PATCH an attribute of an object | 878 """PATCH an attribute of an object |
| 785 | 879 |
| 786 Patch an element using 3 operators | 880 Patch an element using 3 operators |
| 801 type: class name of the object | 895 type: class name of the object |
| 802 link: link to the object | 896 link: link to the object |
| 803 attributes: a dictionary represent only changed attributes of | 897 attributes: a dictionary represent only changed attributes of |
| 804 the object | 898 the object |
| 805 """ | 899 """ |
| 900 if class_name not in self.db.classes: | |
| 901 raise NotFound('Class %s not found' % class_name) | |
| 806 try: | 902 try: |
| 807 op = input['op'].value.lower() | 903 op = input['op'].value.lower() |
| 808 except KeyError: | 904 except KeyError: |
| 809 op = self.__default_patch_op | 905 op = self.__default_patch_op |
| 810 | 906 |
| 840 'link': self.base_path + class_name + item_id, | 936 'link': self.base_path + class_name + item_id, |
| 841 'attribute': result | 937 'attribute': result |
| 842 } | 938 } |
| 843 return 200, result | 939 return 200, result |
| 844 | 940 |
| 941 @Routing.route("/data/<:class_name>", 'OPTIONS') | |
| 845 @_data_decorator | 942 @_data_decorator |
| 846 def options_collection(self, class_name, input): | 943 def options_collection(self, class_name, input): |
| 847 """OPTION return the HTTP Header for the class uri | 944 """OPTION return the HTTP Header for the class uri |
| 848 | 945 |
| 849 Returns: | 946 Returns: |
| 850 int: http status code 204 (No content) | 947 int: http status code 204 (No content) |
| 851 body (string): an empty string | 948 body (string): an empty string |
| 852 """ | 949 """ |
| 950 if class_name not in self.db.classes: | |
| 951 raise NotFound('Class %s not found' % class_name) | |
| 853 return 204, "" | 952 return 204, "" |
| 854 | 953 |
| 954 @Routing.route("/data/<:class_name>/<:item_id>", 'OPTIONS') | |
| 855 @_data_decorator | 955 @_data_decorator |
| 856 def options_element(self, class_name, item_id, input): | 956 def options_element(self, class_name, item_id, input): |
| 857 """OPTION return the HTTP Header for the object uri | 957 """OPTION return the HTTP Header for the object uri |
| 858 | 958 |
| 859 Returns: | 959 Returns: |
| 860 int: http status code 204 (No content) | 960 int: http status code 204 (No content) |
| 861 body (string): an empty string | 961 body (string): an empty string |
| 862 """ | 962 """ |
| 963 if class_name not in self.db.classes: | |
| 964 raise NotFound('Class %s not found' % class_name) | |
| 863 self.client.setHeader( | 965 self.client.setHeader( |
| 864 "Accept-Patch", | 966 "Accept-Patch", |
| 865 "application/x-www-form-urlencoded, multipart/form-data" | 967 "application/x-www-form-urlencoded, multipart/form-data" |
| 866 ) | 968 ) |
| 867 return 204, "" | 969 return 204, "" |
| 868 | 970 |
| 971 @Routing.route("/data/<:class_name>/<:item_id>/<:attr_name>", 'OPTIONS') | |
| 869 @_data_decorator | 972 @_data_decorator |
| 870 def option_attribute(self, class_name, item_id, attr_name, input): | 973 def option_attribute(self, class_name, item_id, attr_name, input): |
| 871 """OPTION return the HTTP Header for the attribute uri | 974 """OPTION return the HTTP Header for the attribute uri |
| 872 | 975 |
| 873 Returns: | 976 Returns: |
| 874 int: http status code 204 (No content) | 977 int: http status code 204 (No content) |
| 875 body (string): an empty string | 978 body (string): an empty string |
| 876 """ | 979 """ |
| 980 if class_name not in self.db.classes: | |
| 981 raise NotFound('Class %s not found' % class_name) | |
| 877 self.client.setHeader( | 982 self.client.setHeader( |
| 878 "Accept-Patch", | 983 "Accept-Patch", |
| 879 "application/x-www-form-urlencoded, multipart/form-data" | 984 "application/x-www-form-urlencoded, multipart/form-data" |
| 880 ) | 985 ) |
| 881 return 204, "" | 986 return 204, "" |
| 882 | 987 |
| 988 @Routing.route("/summary") | |
| 883 @_data_decorator | 989 @_data_decorator |
| 884 def summary(self, input): | 990 def summary(self, input): |
| 885 """Get a summary of resource from class URI. | 991 """Get a summary of resource from class URI. |
| 886 | 992 |
| 887 This function returns only items have View permission | 993 This function returns only items have View permission |
| 981 self.client.setHeader( | 1087 self.client.setHeader( |
| 982 "Access-Control-Allow-Methods", | 1088 "Access-Control-Allow-Methods", |
| 983 "HEAD, OPTIONS, GET, PUT, DELETE, PATCH" | 1089 "HEAD, OPTIONS, GET, PUT, DELETE, PATCH" |
| 984 ) | 1090 ) |
| 985 | 1091 |
| 986 # PATH is split to multiple pieces | |
| 987 # 0 - rest | |
| 988 # 1 - data | |
| 989 # 2 - resource | |
| 990 # 3 - attribute | |
| 991 uri_split = uri.lower().split("/") | |
| 992 | |
| 993 # Call the appropriate method | 1092 # Call the appropriate method |
| 994 if len(uri_split) == 2 and uri_split[1] == 'summary': | 1093 try: |
| 995 output = self.summary(input) | 1094 output = Routing.execute(self, uri, method, input) |
| 996 elif 4 >= len(uri_split) > 2 and uri_split[1] == 'data': | 1095 except NotFound, msg: |
| 997 resource_uri = uri_split[2] | 1096 output = self.error_obj(404, msg) |
| 998 try: | 1097 except Reject, msg: |
| 999 class_name, item_id = hyperdb.splitDesignator(resource_uri) | 1098 output = self.error_obj(405, msg) |
| 1000 except hyperdb.DesignatorError: | |
| 1001 class_name = resource_uri | |
| 1002 item_id = None | |
| 1003 | |
| 1004 if class_name not in self.db.classes: | |
| 1005 output = self.error_obj(404, "Not found") | |
| 1006 elif item_id is None: | |
| 1007 if len(uri_split) == 3: | |
| 1008 output = getattr( | |
| 1009 self, "%s_collection" % method.lower() | |
| 1010 )(class_name, input) | |
| 1011 else: | |
| 1012 output = self.error_obj(404, "Not found") | |
| 1013 else: | |
| 1014 if len(uri_split) == 3: | |
| 1015 output = getattr( | |
| 1016 self, "%s_element" % method.lower() | |
| 1017 )(class_name, item_id, input) | |
| 1018 else: | |
| 1019 output = getattr( | |
| 1020 self, "%s_attribute" % method.lower() | |
| 1021 )(class_name, item_id, uri_split[3], input) | |
| 1022 else: | |
| 1023 output = self.error_obj(404, "Not found") | |
| 1024 | 1099 |
| 1025 # Format the content type | 1100 # Format the content type |
| 1026 if data_type.lower() == "json": | 1101 if data_type.lower() == "json": |
| 1027 self.client.setHeader("Content-Type", "application/json") | 1102 self.client.setHeader("Content-Type", "application/json") |
| 1028 if pretty_output: | 1103 if pretty_output: |
