diff options
Diffstat (limited to 'gitlab/base.py')
| -rw-r--r-- | gitlab/base.py | 163 |
1 files changed, 163 insertions, 0 deletions
diff --git a/gitlab/base.py b/gitlab/base.py index 0d82cf1..df25a36 100644 --- a/gitlab/base.py +++ b/gitlab/base.py @@ -531,3 +531,166 @@ class GitlabObject(object): def __ne__(self, other): return not self.__eq__(other) + + +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': {}, + '_module': importlib.import_module(self.__module__) + }) + self.__dict__['_parent_attrs'] = self.manager.parent_attrs + + # TODO(gpocentek): manage the creation of new objects from the received + # data (_constructor_types) + + self._create_managers() + + def __getattr__(self, name): + try: + return self.__dict__['_updated_attrs'][name] + except KeyError: + try: + return self.__dict__['_attrs'][name] + except KeyError: + try: + return self.__dict__['_parent_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 _create_managers(self): + managers = getattr(self, '_managers', None) + if managers is None: + return + + for attr, cls_name in self._managers: + cls = getattr(self._module, cls_name) + manager = cls(self.manager.gitlab, parent=self) + self.__dict__[attr] = manager + + def _update_attrs(self, new_attrs): + self.__dict__['_updated_attrs'] = {} + self.__dict__['_attrs'].update(new_attrs) + + def get_id(self): + """Returns the id of the resource.""" + 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): + """Creates an objects list from a GitlabList. + + You should not create objects of this type, but use managers list() + methods instead. + + Args: + manager: the RESTManager to attach to the objects + obj_cls: the class of the created objects + _list: the GitlabList holding the data + """ + 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 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=None): + """REST manager constructor. + + Args: + gl (Gitlab): :class:`~gitlab.Gitlab` connection to use to make + requests. + parent: REST object to which the manager is attached. + """ + self.gitlab = gl + self._parent = parent # for nested managers + self._computed_path = self._compute_path() + + @property + def parent_attrs(self): + return self._parent_attrs + + def _compute_path(self, path=None): + self._parent_attrs = {} + if path is None: + path = self._path + if self._parent is None or not hasattr(self, '_from_parent_attrs'): + return path + + data = {self_attr: getattr(self._parent, parent_attr) + for self_attr, parent_attr in self._from_parent_attrs.items()} + self._parent_attrs = data + return path % data + + @property + def path(self): + return self._computed_path |
