diff options
Diffstat (limited to 'gitlab/base.py')
-rw-r--r-- | gitlab/base.py | 314 |
1 files changed, 314 insertions, 0 deletions
diff --git a/gitlab/base.py b/gitlab/base.py index 0d82cf1..2e26c64 100644 --- a/gitlab/base.py +++ b/gitlab/base.py @@ -531,3 +531,317 @@ class GitlabObject(object): def __ne__(self, other): return not self.__eq__(other) + + +class SaveMixin(object): + """Mixin for RESTObject's that can be updated.""" + def save(self, **kwargs): + """Saves the changes made to the object to the server. + + Args: + **kwargs: Extra option to send to the server (e.g. sudo) + + The object is updated to match what the server returns. + """ + updated_data = {} + required, optional = self.manager.get_update_attrs() + for attr in required: + # Get everything required, no matter if it's been updated + updated_data[attr] = getattr(self, attr) + # Add the updated attributes + updated_data.update(self._updated_attrs) + + # class the manager + obj_id = self.get_id() + server_data = self.manager.update(obj_id, updated_data, **kwargs) + self._updated_attrs = {} + self._attrs.update(server_data) + + +class RESTObject(object): + """Represents an object built from server data. + + It holds the attributes know from te server, and the updated attributes in + another. This allows smart updates, if the object allows it. + + You can redefine ``_id_attr`` in child classes to specify which attribute + must be used as uniq ID. None means that the object can be updated without + ID in the url. + """ + _id_attr = 'id' + + def __init__(self, manager, attrs): + self.__dict__.update({ + 'manager': manager, + '_attrs': attrs, + '_updated_attrs': {}, + }) + + def __getattr__(self, name): + try: + return self.__dict__['_updated_attrs'][name] + except KeyError: + try: + return self.__dict__['_attrs'][name] + except KeyError: + raise AttributeError(name) + + def __setattr__(self, name, value): + self.__dict__['_updated_attrs'][name] = value + + def __str__(self): + data = self._attrs.copy() + data.update(self._updated_attrs) + return '%s => %s' % (type(self), data) + + def __repr__(self): + if self._id_attr : + return '<%s %s:%s>' % (self.__class__.__name__, + self._id_attr, + self.get_id()) + else: + return '<%s>' % self.__class__.__name__ + + def get_id(self): + if self._id_attr is None: + return None + return getattr(self, self._id_attr) + + +class RESTObjectList(object): + """Generator object representing a list of RESTObject's. + + This generator uses the Gitlab pagination system to fetch new data when + required. + + Note: you should not instanciate such objects, they are returned by calls + to RESTManager.list() + + Args: + manager: Manager to attach to the created objects + obj_cls: Type of objects to create from the json data + _list: A GitlabList object + """ + def __init__(self, manager, obj_cls, _list): + self.manager = manager + self._obj_cls = obj_cls + self._list = _list + + def __iter__(self): + return self + + def __len__(self): + return len(self._list) + + def __next__(self): + return self.next() + + def next(self): + data = self._list.next() + return self._obj_cls(self.manager, data) + + +class GetMixin(object): + def get(self, id, **kwargs): + """Retrieve a single object. + + Args: + id (int or str): ID of the object to retrieve + **kwargs: Extra data to send to the Gitlab server (e.g. sudo) + + Returns: + object: The generated RESTObject. + + Raises: + GitlabGetError: If the server cannot perform the request. + """ + path = '%s/%s' % (self._path, id) + server_data = self.gitlab.http_get(path, **kwargs) + return self._obj_cls(self, server_data) + + +class GetWithoutIdMixin(object): + def get(self, **kwargs): + """Retrieve a single object. + + Args: + **kwargs: Extra data to send to the Gitlab server (e.g. sudo) + + Returns: + object: The generated RESTObject. + + Raises: + GitlabGetError: If the server cannot perform the request. + """ + server_data = self.gitlab.http_get(self._path, **kwargs) + return self._obj_cls(self, server_data) + + +class ListMixin(object): + def list(self, **kwargs): + """Retrieves a list of objects. + + Args: + **kwargs: Extra data to send to the Gitlab server (e.g. sudo). + If ``all`` is passed and set to True, the entire list of + objects will be returned. + + Returns: + RESTObjectList: Generator going through the list of objects, making + queries to the server when required. + If ``all=True`` is passed as argument, returns + list(RESTObjectList). + """ + + obj = self.gitlab.http_list(self._path, **kwargs) + if isinstance(obj, list): + return [self._obj_cls(self, item) for item in obj] + else: + return RESTObjectList(self, self._obj_cls, obj) + + +class GetFromListMixin(ListMixin): + def get(self, id, **kwargs): + """Retrieve a single object. + + Args: + id (int or str): ID of the object to retrieve + **kwargs: Extra data to send to the Gitlab server (e.g. sudo) + + Returns: + object: The generated RESTObject. + + Raises: + GitlabGetError: If the server cannot perform the request. + """ + gen = self.list() + for obj in gen: + if str(obj.get_id()) == str(id): + return obj + + +class RetrieveMixin(ListMixin, GetMixin): + pass + + +class CreateMixin(object): + def _check_missing_attrs(self, data): + required, optional = self.get_create_attrs() + missing = [] + for attr in required: + if attr not in data: + missing.append(attr) + continue + if missing: + raise AttributeError("Missing attributes: %s" % ", ".join(missing)) + + def get_create_attrs(self): + """Returns the required and optional arguments. + + Returns: + tuple: 2 items: list of required arguments and list of optional + arguments for creation (in that order) + """ + if hasattr(self, '_create_attrs'): + return (self._create_attrs['required'], + self._create_attrs['optional']) + return (tuple(), tuple()) + + def create(self, data, **kwargs): + """Created a new object. + + Args: + data (dict): parameters to send to the server to create the + resource + **kwargs: Extra data to send to the Gitlab server (e.g. sudo) + + Returns: + RESTObject: a new instance of the manage object class build with + the data sent by the server + """ + self._check_missing_attrs(data) + if hasattr(self, '_sanitize_data'): + data = self._sanitize_data(data, 'create') + server_data = self.gitlab.http_post(self._path, post_data=data, **kwargs) + return self._obj_cls(self, server_data) + + +class UpdateMixin(object): + def _check_missing_attrs(self, data): + required, optional = self.get_update_attrs() + missing = [] + for attr in required: + if attr not in data: + missing.append(attr) + continue + if missing: + raise AttributeError("Missing attributes: %s" % ", ".join(missing)) + + def get_update_attrs(self): + """Returns the required and optional arguments. + + Returns: + tuple: 2 items: list of required arguments and list of optional + arguments for update (in that order) + """ + if hasattr(self, '_update_attrs'): + return (self._update_attrs['required'], + self._update_attrs['optional']) + return (tuple(), tuple()) + + def update(self, id=None, new_data={}, **kwargs): + """Update an object on the server. + + Args: + id: ID of the object to update (can be None if not required) + new_data: the update data for the object + **kwargs: Extra data to send to the Gitlab server (e.g. sudo) + + Returns: + dict: The new object data (*not* a RESTObject) + """ + + if id is None: + path = self._path + else: + path = '%s/%s' % (self._path, id) + + self._check_missing_attrs(new_data) + if hasattr(self, '_sanitize_data'): + data = self._sanitize_data(new_data, 'update') + server_data = self.gitlab.http_put(self._path, post_data=data, + **kwargs) + return server_data + + +class DeleteMixin(object): + def delete(self, id, **kwargs): + """Deletes an object on the server. + + Args: + id: ID of the object to delete + **kwargs: Extra data to send to the Gitlab server (e.g. sudo) + """ + path = '%s/%s' % (self._path, id) + self.gitlab.http_delete(path, **kwargs) + + +class CRUDMixin(GetMixin, ListMixin, CreateMixin, UpdateMixin, DeleteMixin): + pass + + +class RESTManager(object): + """Base class for CRUD operations on objects. + + Derivated class must define ``_path`` and ``_obj_cls``. + + ``_path``: Base URL path on which requests will be sent (e.g. '/projects') + ``_obj_cls``: The class of objects that will be created + """ + + _path = None + _obj_cls = None + + def __init__(self, gl, parent_attrs={}): + self.gitlab = gl + self._parent_attrs = {} # for nested managers |