Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit29e0bae

Browse files
author
Gauvain Pocentek
committed
Rework the manager and object classes
Add new RESTObject and RESTManager base class, linked to a bunch ofMixin class to implement the actual CRUD methods.Object are generated by the managers, and special cases are handled inthe derivated classes.Both ways (old and new) can be used together, migrate only a few v4objects to the new method as a POC.TODO: handle managers on generated objects (have to deal with attributesin the URLs).
1 parentb7298de commit29e0bae

File tree

3 files changed

+399
-106
lines changed

3 files changed

+399
-106
lines changed

‎gitlab/__init__.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -644,9 +644,12 @@ def http_request(self, verb, path, query_data={}, post_data={},
644644
opts=self._get_session_opts(content_type='application/json')
645645
result=self.session.request(verb,url,json=post_data,
646646
params=params,stream=streamed,**opts)
647-
ifnot (200<=result.status_code<300):
648-
raiseGitlabHttpError(response_code=result.status_code)
649-
returnresult
647+
if200<=result.status_code<300:
648+
returnresult
649+
650+
651+
raiseGitlabHttpError(response_code=result.status_code,
652+
error_message=result.content)
650653

651654
defhttp_get(self,path,query_data={},streamed=False,**kwargs):
652655
"""Make a GET request to the Gitlab server.
@@ -748,7 +751,7 @@ def http_put(self, path, query_data={}, post_data={}, **kwargs):
748751
GitlabHttpError: When the return code is not 2xx
749752
GitlabParsingError: IF the json data could not be parsed
750753
"""
751-
result=self.hhtp_request('put',path,query_data=query_data,
754+
result=self.http_request('put',path,query_data=query_data,
752755
post_data=post_data,**kwargs)
753756
try:
754757
returnresult.json()
@@ -808,6 +811,9 @@ def _query(self, url, query_data={}, **kwargs):
808811
def__iter__(self):
809812
returnself
810813

814+
def__len__(self):
815+
returnself._total_pages
816+
811817
def__next__(self):
812818
returnself.next()
813819

@@ -819,6 +825,6 @@ def next(self):
819825
exceptIndexError:
820826
ifself._next_url:
821827
self._query(self._next_url)
822-
returnself._data[self._current]
828+
returnself.next()
823829

824830
raiseStopIteration

‎gitlab/base.py

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,3 +531,317 @@ def __eq__(self, other):
531531

532532
def__ne__(self,other):
533533
returnnotself.__eq__(other)
534+
535+
536+
classSaveMixin(object):
537+
"""Mixin for RESTObject's that can be updated."""
538+
defsave(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+
forattrinrequired:
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+
classRESTObject(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+
returnself.__dict__['_updated_attrs'][name]
583+
exceptKeyError:
584+
try:
585+
returnself.__dict__['_attrs'][name]
586+
exceptKeyError:
587+
raiseAttributeError(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+
ifself._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+
defget_id(self):
606+
ifself._id_attrisNone:
607+
returnNone
608+
returngetattr(self,self._id_attr)
609+
610+
611+
classRESTObjectList(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+
returnself
632+
633+
def__len__(self):
634+
returnlen(self._list)
635+
636+
def__next__(self):
637+
returnself.next()
638+
639+
defnext(self):
640+
data=self._list.next()
641+
returnself._obj_cls(self.manager,data)
642+
643+
644+
classGetMixin(object):
645+
defget(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+
returnself._obj_cls(self,server_data)
661+
662+
663+
classGetWithoutIdMixin(object):
664+
defget(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+
returnself._obj_cls(self,server_data)
678+
679+
680+
classListMixin(object):
681+
deflist(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+
ifisinstance(obj,list):
698+
return [self._obj_cls(self,item)foriteminobj]
699+
else:
700+
returnRESTObjectList(self,self._obj_cls,obj)
701+
702+
703+
classGetFromListMixin(ListMixin):
704+
defget(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+
forobjingen:
719+
ifstr(obj.get_id())==str(id):
720+
returnobj
721+
722+
723+
classRetrieveMixin(ListMixin,GetMixin):
724+
pass
725+
726+
727+
classCreateMixin(object):
728+
def_check_missing_attrs(self,data):
729+
required,optional=self.get_create_attrs()
730+
missing= []
731+
forattrinrequired:
732+
ifattrnotindata:
733+
missing.append(attr)
734+
continue
735+
ifmissing:
736+
raiseAttributeError("Missing attributes: %s"%", ".join(missing))
737+
738+
defget_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+
ifhasattr(self,'_create_attrs'):
746+
return (self._create_attrs['required'],
747+
self._create_attrs['optional'])
748+
return (tuple(),tuple())
749+
750+
defcreate(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+
ifhasattr(self,'_sanitize_data'):
764+
data=self._sanitize_data(data,'create')
765+
server_data=self.gitlab.http_post(self._path,post_data=data,**kwargs)
766+
returnself._obj_cls(self,server_data)
767+
768+
769+
classUpdateMixin(object):
770+
def_check_missing_attrs(self,data):
771+
required,optional=self.get_update_attrs()
772+
missing= []
773+
forattrinrequired:
774+
ifattrnotindata:
775+
missing.append(attr)
776+
continue
777+
ifmissing:
778+
raiseAttributeError("Missing attributes: %s"%", ".join(missing))
779+
780+
defget_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+
ifhasattr(self,'_update_attrs'):
788+
return (self._update_attrs['required'],
789+
self._update_attrs['optional'])
790+
return (tuple(),tuple())
791+
792+
defupdate(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+
ifidisNone:
805+
path=self._path
806+
else:
807+
path='%s/%s'% (self._path,id)
808+
809+
self._check_missing_attrs(new_data)
810+
ifhasattr(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+
returnserver_data
815+
816+
817+
classDeleteMixin(object):
818+
defdelete(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+
classCRUDMixin(GetMixin,ListMixin,CreateMixin,UpdateMixin,DeleteMixin):
830+
pass
831+
832+
833+
classRESTManager(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

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp