@@ -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
0 commit comments