| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent d809fef commit 993d576
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -644,9 +644,12 @@ def http_request(self, verb, path, query_data={}, post_data={}, | |||
| 644 | 644 | opts = self._get_session_opts(content_type='application/json') | |
| 645 | 645 | result = self.session.request(verb, url, json=post_data, | |
| 646 | 646 | params=params, stream=streamed, **opts) | |
| 647 | - if not (200 <= result.status_code < 300): | ||
| 648 | - raise GitlabHttpError(response_code=result.status_code) | ||
| 649 | - return result | ||
| 647 | + if 200 <= result.status_code < 300: | ||
| 648 | + return result | ||
| 649 | + | ||
| 650 | + | ||
| 651 | + raise GitlabHttpError(response_code=result.status_code, | ||
| 652 | + error_message=result.content) | ||
| 650 | 653 | ||
| 651 | 654 | def http_get(self, path, query_data={}, streamed=False, **kwargs): | |
| 652 | 655 | """Make a GET request to the Gitlab server. | |
@@ -748,7 +751,7 @@ def http_put(self, path, query_data={}, post_data={}, **kwargs): | |||
| 748 | 751 | GitlabHttpError: When the return code is not 2xx | |
| 749 | 752 | GitlabParsingError: IF the json data could not be parsed | |
| 750 | 753 | """ | |
| 751 | - result = self.hhtp_request('put', path, query_data=query_data, | ||
| 754 | + result = self.http_request('put', path, query_data=query_data, | ||
| 752 | 755 | post_data=post_data, **kwargs) | |
| 753 | 756 | try: | |
| 754 | 757 | return result.json() | |
@@ -808,6 +811,9 @@ def _query(self, url, query_data={}, **kwargs): | |||
| 808 | 811 | def __iter__(self): | |
| 809 | 812 | return self | |
| 810 | 813 | ||
| 814 | + def __len__(self): | ||
| 815 | + return self._total_pages | ||
| 816 | + | ||
| 811 | 817 | def __next__(self): | |
| 812 | 818 | return self.next() | |
| 813 | 819 | ||
@@ -819,6 +825,6 @@ def next(self): | |||
| 819 | 825 | except IndexError: | |
| 820 | 826 | if self._next_url: | |
| 821 | 827 | self._query(self._next_url) | |
| 822 | - return self._data[self._current] | ||
| 828 | + return self.next() | ||
| 823 | 829 | ||
| 824 | 830 | raise StopIteration | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -531,3 +531,317 @@ def __eq__(self, other): | |||
| 531 | 531 | ||
| 532 | 532 | def __ne__(self, other): | |
| 533 | 533 | return not self.__eq__(other) | |
| 534 | + | ||
| 535 | + | ||
| 536 | + class SaveMixin(object): | ||
| 537 | + """Mixin for RESTObject's that can be updated.""" | ||
| 538 | + def save(self, **kwargs): | ||
| 539 | + """Saves the changes made to the object to the server. | ||
| 540 | + | ||
| 541 | + Args: | ||
| 542 | + **kwargs: Extra option to send to the server (e.g. sudo) | ||
| 543 | + | ||
| 544 | + The object is updated to match what the server returns. | ||
| 545 | + """ | ||
| 546 | + updated_data = {} | ||
| 547 | + required, optional = self.manager.get_update_attrs() | ||
| 548 | + for attr in required: | ||
| 549 | + # Get everything required, no matter if it's been updated | ||
| 550 | + updated_data[attr] = getattr(self, attr) | ||
| 551 | + # Add the updated attributes | ||
| 552 | + updated_data.update(self._updated_attrs) | ||
| 553 | + | ||
| 554 | + # class the manager | ||
| 555 | + obj_id = self.get_id() | ||
| 556 | + server_data = self.manager.update(obj_id, updated_data, **kwargs) | ||
| 557 | + self._updated_attrs = {} | ||
| 558 | + self._attrs.update(server_data) | ||
| 559 | + | ||
| 560 | + | ||
| 561 | + class RESTObject(object): | ||
| 562 | + """Represents an object built from server data. | ||
| 563 | + | ||
| 564 | + It holds the attributes know from te server, and the updated attributes in | ||
| 565 | + another. This allows smart updates, if the object allows it. | ||
| 566 | + | ||
| 567 | + You can redefine ``_id_attr`` in child classes to specify which attribute | ||
| 568 | + must be used as uniq ID. None means that the object can be updated without | ||
| 569 | + ID in the url. | ||
| 570 | + """ | ||
| 571 | + _id_attr = 'id' | ||
| 572 | + | ||
| 573 | + def __init__(self, manager, attrs): | ||
| 574 | + self.__dict__.update({ | ||
| 575 | + 'manager': manager, | ||
| 576 | + '_attrs': attrs, | ||
| 577 | + '_updated_attrs': {}, | ||
| 578 | + }) | ||
| 579 | + | ||
| 580 | + def __getattr__(self, name): | ||
| 581 | + try: | ||
| 582 | + return self.__dict__['_updated_attrs'][name] | ||
| 583 | + except KeyError: | ||
| 584 | + try: | ||
| 585 | + return self.__dict__['_attrs'][name] | ||
| 586 | + except KeyError: | ||
| 587 | + raise AttributeError(name) | ||
| 588 | + | ||
| 589 | + def __setattr__(self, name, value): | ||
| 590 | + self.__dict__['_updated_attrs'][name] = value | ||
| 591 | + | ||
| 592 | + def __str__(self): | ||
| 593 | + data = self._attrs.copy() | ||
| 594 | + data.update(self._updated_attrs) | ||
| 595 | + return '%s => %s' % (type(self), data) | ||
| 596 | + | ||
| 597 | + def __repr__(self): | ||
| 598 | + if self._id_attr : | ||
| 599 | + return '<%s %s:%s>' % (self.__class__.__name__, | ||
| 600 | + self._id_attr, | ||
| 601 | + self.get_id()) | ||
| 602 | + else: | ||
| 603 | + return '<%s>' % self.__class__.__name__ | ||
| 604 | + | ||
| 605 | + def get_id(self): | ||
| 606 | + if self._id_attr is None: | ||
| 607 | + return None | ||
| 608 | + return getattr(self, self._id_attr) | ||
| 609 | + | ||
| 610 | + | ||
| 611 | + class RESTObjectList(object): | ||
| 612 | + """Generator object representing a list of RESTObject's. | ||
| 613 | + | ||
| 614 | + This generator uses the Gitlab pagination system to fetch new data when | ||
| 615 | + required. | ||
| 616 | + | ||
| 617 | + Note: you should not instanciate such objects, they are returned by calls | ||
| 618 | + to RESTManager.list() | ||
| 619 | + | ||
| 620 | + Args: | ||
| 621 | + manager: Manager to attach to the created objects | ||
| 622 | + obj_cls: Type of objects to create from the json data | ||
| 623 | + _list: A GitlabList object | ||
| 624 | + """ | ||
| 625 | + def __init__(self, manager, obj_cls, _list): | ||
| 626 | + self.manager = manager | ||
| 627 | + self._obj_cls = obj_cls | ||
| 628 | + self._list = _list | ||
| 629 | + | ||
| 630 | + def __iter__(self): | ||
| 631 | + return self | ||
| 632 | + | ||
| 633 | + def __len__(self): | ||
| 634 | + return len(self._list) | ||
| 635 | + | ||
| 636 | + def __next__(self): | ||
| 637 | + return self.next() | ||
| 638 | + | ||
| 639 | + def next(self): | ||
| 640 | + data = self._list.next() | ||
| 641 | + return self._obj_cls(self.manager, data) | ||
| 642 | + | ||
| 643 | + | ||
| 644 | + class GetMixin(object): | ||
| 645 | + def get(self, id, **kwargs): | ||
| 646 | + """Retrieve a single object. | ||
| 647 | + | ||
| 648 | + Args: | ||
| 649 | + id (int or str): ID of the object to retrieve | ||
| 650 | + **kwargs: Extra data to send to the Gitlab server (e.g. sudo) | ||
| 651 | + | ||
| 652 | + Returns: | ||
| 653 | + object: The generated RESTObject. | ||
| 654 | + | ||
| 655 | + Raises: | ||
| 656 | + GitlabGetError: If the server cannot perform the request. | ||
| 657 | + """ | ||
| 658 | + path = '%s/%s' % (self._path, id) | ||
| 659 | + server_data = self.gitlab.http_get(path, **kwargs) | ||
| 660 | + return self._obj_cls(self, server_data) | ||
| 661 | + | ||
| 662 | + | ||
| 663 | + class GetWithoutIdMixin(object): | ||
| 664 | + def get(self, **kwargs): | ||
| 665 | + """Retrieve a single object. | ||
| 666 | + | ||
| 667 | + Args: | ||
| 668 | + **kwargs: Extra data to send to the Gitlab server (e.g. sudo) | ||
| 669 | + | ||
| 670 | + Returns: | ||
| 671 | + object: The generated RESTObject. | ||
| 672 | + | ||
| 673 | + Raises: | ||
| 674 | + GitlabGetError: If the server cannot perform the request. | ||
| 675 | + """ | ||
| 676 | + server_data = self.gitlab.http_get(self._path, **kwargs) | ||
| 677 | + return self._obj_cls(self, server_data) | ||
| 678 | + | ||
| 679 | + | ||
| 680 | + class ListMixin(object): | ||
| 681 | + def list(self, **kwargs): | ||
| 682 | + """Retrieves a list of objects. | ||
| 683 | + | ||
| 684 | + Args: | ||
| 685 | + **kwargs: Extra data to send to the Gitlab server (e.g. sudo). | ||
| 686 | + If ``all`` is passed and set to True, the entire list of | ||
| 687 | + objects will be returned. | ||
| 688 | + | ||
| 689 | + Returns: | ||
| 690 | + RESTObjectList: Generator going through the list of objects, making | ||
| 691 | + queries to the server when required. | ||
| 692 | + If ``all=True`` is passed as argument, returns | ||
| 693 | + list(RESTObjectList). | ||
| 694 | + """ | ||
| 695 | + | ||
| 696 | + obj = self.gitlab.http_list(self._path, **kwargs) | ||
| 697 | + if isinstance(obj, list): | ||
| 698 | + return [self._obj_cls(self, item) for item in obj] | ||
| 699 | + else: | ||
| 700 | + return RESTObjectList(self, self._obj_cls, obj) | ||
| 701 | + | ||
| 702 | + | ||
| 703 | + class GetFromListMixin(ListMixin): | ||
| 704 | + def get(self, id, **kwargs): | ||
| 705 | + """Retrieve a single object. | ||
| 706 | + | ||
| 707 | + Args: | ||
| 708 | + id (int or str): ID of the object to retrieve | ||
| 709 | + **kwargs: Extra data to send to the Gitlab server (e.g. sudo) | ||
| 710 | + | ||
| 711 | + Returns: | ||
| 712 | + object: The generated RESTObject. | ||
| 713 | + | ||
| 714 | + Raises: | ||
| 715 | + GitlabGetError: If the server cannot perform the request. | ||
| 716 | + """ | ||
| 717 | + gen = self.list() | ||
| 718 | + for obj in gen: | ||
| 719 | + if str(obj.get_id()) == str(id): | ||
| 720 | + return obj | ||
| 721 | + | ||
| 722 | + | ||
| 723 | + class RetrieveMixin(ListMixin, GetMixin): | ||
| 724 | + pass | ||
| 725 | + | ||
| 726 | + | ||
| 727 | + class CreateMixin(object): | ||
| 728 | + def _check_missing_attrs(self, data): | ||
| 729 | + required, optional = self.get_create_attrs() | ||
| 730 | + missing = [] | ||
| 731 | + for attr in required: | ||
| 732 | + if attr not in data: | ||
| 733 | + missing.append(attr) | ||
| 734 | + continue | ||
| 735 | + if missing: | ||
| 736 | + raise AttributeError("Missing attributes: %s" % ", ".join(missing)) | ||
| 737 | + | ||
| 738 | + def get_create_attrs(self): | ||
| 739 | + """Returns the required and optional arguments. | ||
| 740 | + | ||
| 741 | + Returns: | ||
| 742 | + tuple: 2 items: list of required arguments and list of optional | ||
| 743 | + arguments for creation (in that order) | ||
| 744 | + """ | ||
| 745 | + if hasattr(self, '_create_attrs'): | ||
| 746 | + return (self._create_attrs['required'], | ||
| 747 | + self._create_attrs['optional']) | ||
| 748 | + return (tuple(), tuple()) | ||
| 749 | + | ||
| 750 | + def create(self, data, **kwargs): | ||
| 751 | + """Created a new object. | ||
| 752 | + | ||
| 753 | + Args: | ||
| 754 | + data (dict): parameters to send to the server to create the | ||
| 755 | + resource | ||
| 756 | + **kwargs: Extra data to send to the Gitlab server (e.g. sudo) | ||
| 757 | + | ||
| 758 | + Returns: | ||
| 759 | + RESTObject: a new instance of the manage object class build with | ||
| 760 | + the data sent by the server | ||
| 761 | + """ | ||
| 762 | + self._check_missing_attrs(data) | ||
| 763 | + if hasattr(self, '_sanitize_data'): | ||
| 764 | + data = self._sanitize_data(data, 'create') | ||
| 765 | + server_data = self.gitlab.http_post(self._path, post_data=data, **kwargs) | ||
| 766 | + return self._obj_cls(self, server_data) | ||
| 767 | + | ||
| 768 | + | ||
| 769 | + class UpdateMixin(object): | ||
| 770 | + def _check_missing_attrs(self, data): | ||
| 771 | + required, optional = self.get_update_attrs() | ||
| 772 | + missing = [] | ||
| 773 | + for attr in required: | ||
| 774 | + if attr not in data: | ||
| 775 | + missing.append(attr) | ||
| 776 | + continue | ||
| 777 | + if missing: | ||
| 778 | + raise AttributeError("Missing attributes: %s" % ", ".join(missing)) | ||
| 779 | + | ||
| 780 | + def get_update_attrs(self): | ||
| 781 | + """Returns the required and optional arguments. | ||
| 782 | + | ||
| 783 | + Returns: | ||
| 784 | + tuple: 2 items: list of required arguments and list of optional | ||
| 785 | + arguments for update (in that order) | ||
| 786 | + """ | ||
| 787 | + if hasattr(self, '_update_attrs'): | ||
| 788 | + return (self._update_attrs['required'], | ||
| 789 | + self._update_attrs['optional']) | ||
| 790 | + return (tuple(), tuple()) | ||
| 791 | + | ||
| 792 | + def update(self, id=None, new_data={}, **kwargs): | ||
| 793 | + """Update an object on the server. | ||
| 794 | + | ||
| 795 | + Args: | ||
| 796 | + id: ID of the object to update (can be None if not required) | ||
| 797 | + new_data: the update data for the object | ||
| 798 | + **kwargs: Extra data to send to the Gitlab server (e.g. sudo) | ||
| 799 | + | ||
| 800 | + Returns: | ||
| 801 | + dict: The new object data (*not* a RESTObject) | ||
| 802 | + """ | ||
| 803 | + | ||
| 804 | + if id is None: | ||
| 805 | + path = self._path | ||
| 806 | + else: | ||
| 807 | + path = '%s/%s' % (self._path, id) | ||
| 808 | + | ||
| 809 | + self._check_missing_attrs(new_data) | ||
| 810 | + if hasattr(self, '_sanitize_data'): | ||
| 811 | + data = self._sanitize_data(new_data, 'update') | ||
| 812 | + server_data = self.gitlab.http_put(self._path, post_data=data, | ||
| 813 | + **kwargs) | ||
| 814 | + return server_data | ||
| 815 | + | ||
| 816 | + | ||
| 817 | + class DeleteMixin(object): | ||
| 818 | + def delete(self, id, **kwargs): | ||
| 819 | + """Deletes an object on the server. | ||
| 820 | + | ||
| 821 | + Args: | ||
| 822 | + id: ID of the object to delete | ||
| 823 | + **kwargs: Extra data to send to the Gitlab server (e.g. sudo) | ||
| 824 | + """ | ||
| 825 | + path = '%s/%s' % (self._path, id) | ||
| 826 | + self.gitlab.http_delete(path, **kwargs) | ||
| 827 | + | ||
| 828 | + | ||
| 829 | + class CRUDMixin(GetMixin, ListMixin, CreateMixin, UpdateMixin, DeleteMixin): | ||
| 830 | + pass | ||
| 831 | + | ||
| 832 | + | ||
| 833 | + class RESTManager(object): | ||
| 834 | + """Base class for CRUD operations on objects. | ||
| 835 | + | ||
| 836 | + Derivated class must define ``_path`` and ``_obj_cls``. | ||
| 837 | + | ||
| 838 | + ``_path``: Base URL path on which requests will be sent (e.g. '/projects') | ||
| 839 | + ``_obj_cls``: The class of objects that will be created | ||
| 840 | + """ | ||
| 841 | + | ||
| 842 | + _path = None | ||
| 843 | + _obj_cls = None | ||
| 844 | + | ||
| 845 | + def __init__(self, gl, parent_attrs={}): | ||
| 846 | + self.gitlab = gl | ||
| 847 | + self._parent_attrs = {} # for nested managers | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments