diff --git a/AUTHORS b/AUTHORS index b086f42b0..6553ec6a7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,7 +1,8 @@ -Author ------- +Authors +------- Gauvain Pocentek +Mika Mäenpää Contributors ------------ @@ -13,3 +14,4 @@ Koen Smets Mart Sõmermaa Diego Giovane Pasqualin Crestez Dan Leonard +Patrick Miller diff --git a/ChangeLog b/ChangeLog index dc2633a9d..6ed622f06 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +Version 0.8 + + * Better python 2.6 and python 3 support + * Timeout support in HTTP requests + * Gitlab.get() raised GitlabListError instead of GitlabGetError + * Support api-objects which don't have id in api response + * Add ProjectLabel and ProjectFile classes + * Moved url attributes to separate list + * Added list for delete attributes + Version 0.7 * Fix license classifier in setup.py diff --git a/README.md b/README.md index bee94e823..00e701773 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ Here's an example of the syntax: [global] default = local ssl_verify = true +timeout = 5 [local] url = http://10.0.3.2:8080 @@ -93,6 +94,9 @@ authentication is supported (not user/password). The `ssl_verify` option defines if the server SSL certificate should be validated (use false for self signed certificates, only useful with https). +The `timeout` option defines after how many seconds a request to the Gitlab +server should be abandonned. + Choosing a different server than the default one can be done at run time: ````` diff --git a/gitlab b/gitlab index 18885006e..433299574 100755 --- a/gitlab +++ b/gitlab @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . +from __future__ import print_function, division, absolute_import + import os import sys import re @@ -131,13 +133,7 @@ def usage(): if gitlab.GitlabObject in getmro(o) and o != gitlab.GitlabObject: classes.append(o) - def s(a, b): - if a.__name__ < b.__name__: - return -1 - elif a.__name__ > b.__name__: - return 1 - - classes.sort(cmp=s) + classes.sort(key=lambda x: x.__name__) for cls in classes: print(" %s" % clsToWhat(cls)) @@ -145,7 +141,7 @@ def usage(): def do_auth(): try: gl = gitlab.Gitlab(gitlab_url, private_token=gitlab_token, - ssl_verify=ssl_verify) + ssl_verify=ssl_verify, timeout=timeout) gl.auth() except: die("Could not connect to GitLab (%s)" % gitlab_url) @@ -251,6 +247,7 @@ def do_project_owned(): ssl_verify = True +timeout = 60 gitlab_id = None verbose = False @@ -329,6 +326,15 @@ try: except: pass +try: + timeout = config.getboolean('global', 'timeout') +except: + pass +try: + timeout = config.getboolean(gitlab_id, 'timeout') +except: + pass + try: what = args.pop(0) action = args.pop(0) diff --git a/gitlab.py b/gitlab.py index 9ac179cea..3416ebdee 100644 --- a/gitlab.py +++ b/gitlab.py @@ -15,12 +15,18 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . +from __future__ import print_function, division, absolute_import + +import six + import json import requests import sys +from itertools import chain + __title__ = 'python-gitlab' -__version__ = '0.7' +__version__ = '0.8' __author__ = 'Gauvain Pocentek' __email__ = 'gauvain@pocentek.net' __license__ = 'LGPL3' @@ -75,15 +81,19 @@ class GitlabAuthenticationError(Exception): class Gitlab(object): """Represents a GitLab server connection""" def __init__(self, url, private_token=None, - email=None, password=None, ssl_verify=True): + email=None, password=None, ssl_verify=True, timeout=None): """Stores informations about the server url: the URL of the Gitlab server private_token: the user private token email: the user email/login password: the user password (associated with email) + ssl_verify: (Passed to requests-library) + timeout: (Passed to requests-library). Timeout to use for requests to + gitlab server. Float or tuple(Float,Float). """ self._url = '%s/api/v3' % url + self.timeout = timeout self.setToken(private_token) self.email = email self.password = password @@ -122,6 +132,15 @@ def setUrl(self, url): """Updates the gitlab URL""" self._url = '%s/api/v3' % url + def constructUrl(self, id_, obj, parameters): + args = _sanitize_dict(parameters) + url = obj._url % args + if id_ is not None: + url = '%s%s/%s' % (self._url, url, str(id_)) + else: + url = '%s%s' % (self._url, url) + return url + def setToken(self, token): """Sets the private token for authentication""" self.private_token = token if token else None @@ -141,7 +160,8 @@ def rawGet(self, path, **kwargs): try: return requests.get(url, headers=self.headers, - verify=self.ssl_verify) + verify=self.ssl_verify, + timeout=self.timeout) except: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % self._url) @@ -151,7 +171,8 @@ def rawPost(self, path, data=None): try: return requests.post(url, data, headers=self.headers, - verify=self.ssl_verify) + verify=self.ssl_verify, + timeout=self.timeout) except: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % self._url) @@ -162,7 +183,8 @@ def rawPut(self, path): try: return requests.put(url, headers=self.headers, - verify=self.ssl_verify) + verify=self.ssl_verify, + timeout=self.timeout) except: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % self._url) @@ -173,29 +195,34 @@ def rawDelete(self, path): try: return requests.delete(url, headers=self.headers, - verify=self.ssl_verify) + verify=self.ssl_verify, + timeout=self.timeout) except: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % self._url) def list(self, obj_class, **kwargs): missing = [] - for k in obj_class.requiredListAttrs: + for k in chain(obj_class.requiredUrlAttrs, + obj_class.requiredListAttrs): if k not in kwargs: missing.append(k) if missing: raise GitlabListError('Missing attribute(s): %s' % ", ".join(missing)) - args = _sanitize_dict(kwargs) - url = obj_class._url % args - url = '%s%s' % (self._url, url) - if args: - url += "?%s" % ("&".join( - ["%s=%s" % (k, v) for k, v in args.items()])) + url = self.constructUrl(id_=None, obj=obj_class, parameters=kwargs) + + # Remove attributes that are used in url so that there is only + # url-parameters left + params = kwargs.copy() + for attribute in obj_class.requiredUrlAttrs: + del params[attribute] try: - r = requests.get(url, headers=self.headers, verify=self.ssl_verify) + r = requests.get(url, params=kwargs, headers=self.headers, + verify=self.ssl_verify, + timeout=self.timeout) except: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % self._url) @@ -204,14 +231,19 @@ def list(self, obj_class, **kwargs): cls = obj_class if obj_class._returnClass: cls = obj_class._returnClass - l = [cls(self, item) for item in r.json() if item is not None] - if kwargs: - for k, v in kwargs.items(): - if k in ('page', 'per_page'): - continue - for obj in l: - obj.__dict__[k] = str(v) - return l + + cls_kwargs = kwargs.copy() + + # Add _created manually, because we are not creating objects + # through normal path + cls_kwargs['_created'] = True + + # Remove parameters from kwargs before passing it to constructor + for key in ['page', 'per_page']: + if key in cls_kwargs: + del cls_kwargs[key] + + return [cls(self, item, **cls_kwargs) for item in r.json() if item is not None] elif r.status_code == 401: raise GitlabAuthenticationError(r.json()['message']) else: @@ -219,21 +251,25 @@ def list(self, obj_class, **kwargs): def get(self, obj_class, id=None, **kwargs): missing = [] - for k in obj_class.requiredGetAttrs: + for k in chain(obj_class.requiredUrlAttrs, + obj_class.requiredGetAttrs): if k not in kwargs: missing.append(k) if missing: - raise GitlabListError('Missing attribute(s): %s' % - ", ".join(missing)) + raise GitlabGetError('Missing attribute(s): %s' % + ", ".join(missing)) - url = obj_class._url % _sanitize_dict(kwargs) - if id is not None: - url = '%s%s/%s' % (self._url, url, str(id)) - else: - url = '%s%s' % (self._url, url) + url = self.constructUrl(id_=id, obj=obj_class, parameters=kwargs) + + # Remove attributes that are used in url so that there is only + # url-parameters left + params = kwargs.copy() + for attribute in obj_class.requiredUrlAttrs: + del params[attribute] try: - r = requests.get(url, headers=self.headers, verify=self.ssl_verify) + r = requests.get(url, params=params, headers=self.headers, + verify=self.ssl_verify, timeout=self.timeout) except: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % self._url) @@ -248,14 +284,28 @@ def get(self, obj_class, id=None, **kwargs): raise GitlabGetError('%d: %s' % (r.status_code, r.text)) def delete(self, obj): - args = _sanitize_dict(obj.__dict__) - url = obj._url % args - url = '%s%s/%s' % (self._url, url, args['id']) + params = obj.__dict__.copy() + missing = [] + for k in chain(obj.requiredUrlAttrs, obj.requiredDeleteAttrs): + if k not in params: + missing.append(k) + if missing: + raise GitlabDeleteError('Missing attribute(s): %s' % + ", ".join(missing)) + + url = self.constructUrl(id_=obj.id, obj=obj, parameters=params) + + # Remove attributes that are used in url so that there is only + # url-parameters left + for attribute in obj.requiredUrlAttrs: + del params[attribute] try: r = requests.delete(url, + params=params, headers=self.headers, - verify=self.ssl_verify) + verify=self.ssl_verify, + timeout=self.timeout) except: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % self._url) @@ -270,25 +320,24 @@ def delete(self, obj): def create(self, obj): missing = [] - for k in obj.requiredCreateAttrs: + for k in chain(obj.requiredUrlAttrs, obj.requiredCreateAttrs): if k not in obj.__dict__: missing.append(k) if missing: raise GitlabCreateError('Missing attribute(s): %s' % ", ".join(missing)) - args = _sanitize_dict(obj.__dict__) - url = obj._url % args - url = '%s%s' % (self._url, url) + url = self.constructUrl(id_=None, obj=obj, parameters=obj.__dict__) - for k, v in obj.__dict__.items(): + for k, v in list(obj.__dict__.items()): if type(v) == bool: obj.__dict__[k] = 1 if v else 0 try: r = requests.post(url, obj.__dict__, headers=self.headers, - verify=self.ssl_verify) + verify=self.ssl_verify, + timeout=self.timeout) except: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % self._url) @@ -301,24 +350,29 @@ def create(self, obj): raise GitlabCreateError('%d: %s' % (r.status_code, r.text)) def update(self, obj): - args = _sanitize_dict(obj.__dict__) - url = obj._url % args - url = '%s%s/%s' % (self._url, url, str(obj.id)) - + missing = [] + for k in chain(obj.requiredUrlAttrs, obj.requiredCreateAttrs): + if k not in obj.__dict__: + missing.append(k) + if missing: + raise GitlabUpdateError('Missing attribute(s): %s' % + ", ".join(missing)) + url = self.constructUrl(id_=obj.id, obj=obj, parameters=obj.__dict__) # build a dict of data that can really be sent to server d = {} - for k, v in obj.__dict__.items(): + for k, v in list(obj.__dict__.items()): if type(v) in (int, str): d[k] = str(v) elif type(v) == bool: d[k] = 1 if v else 0 - elif type(v) == unicode: + elif six.PY2 and type(v) == six.text_type: d[k] = str(v.encode(self.gitlab_encoding, "replace")) try: r = requests.put(url, d, headers=self.headers, - verify=self.ssl_verify) + verify=self.ssl_verify, + timeout=self.timeout) except: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % self._url) @@ -462,26 +516,30 @@ def _get_display_encoding(): def _sanitize(value): - if type(value) in (str, unicode): + if isinstance(value, six.string_types): return value.replace('/', '%2F') return value def _sanitize_dict(src): - return {k: _sanitize(v) for k, v in src.items()} + return dict((k, _sanitize(v)) for k, v in src.items()) class GitlabObject(object): _url = None _returnClass = None _constructorTypes = None + # Tells if _getListOrObject should return list or object when id is None + getListWhenNoId = True canGet = True canList = True canCreate = True canUpdate = True canDelete = True + requiredUrlAttrs = [] requiredListAttrs = [] requiredGetAttrs = [] + requiredDeleteAttrs = [] requiredCreateAttrs = [] optionalCreateAttrs = [] idAttr = 'id' @@ -498,16 +556,18 @@ def list(cls, gl, **kwargs): return gl.list(cls, **kwargs) def _getListOrObject(self, cls, id, **kwargs): - if id is None: + if id is None and cls.getListWhenNoId: if not cls.canList: - raise GitlabGetError + raise GitlabListError return cls.list(self.gitlab, **kwargs) - + elif id is None and not cls.getListWhenNoId: + if not cls.canGet: + raise GitlabGetError + return cls(self.gitlab, id, **kwargs) elif isinstance(id, dict): if not cls.canCreate: raise GitlabCreateError return cls(self.gitlab, id, **kwargs) - else: if not cls.canGet: raise GitlabGetError @@ -525,10 +585,10 @@ def _setFromDict(self, data): self.__dict__[k] = [] for i in v: self.__dict__[k].append(self._getObject(k, i)) - elif v: - self.__dict__[k] = self._getObject(k, v) - else: # None object + elif v is None: self.__dict__[k] = None + else: + self.__dict__[k] = self._getObject(k, v) def _create(self): if not self.canCreate: @@ -536,6 +596,7 @@ def _create(self): json = self.gitlab.create(self) self._setFromDict(json) + self._created = True def _update(self): if not self.canUpdate: @@ -545,7 +606,7 @@ def _update(self): self._setFromDict(json) def save(self): - if hasattr(self, 'id'): + if self._created: self._update() else: self._create() @@ -554,16 +615,20 @@ def delete(self): if not self.canDelete: raise NotImplementedError - if not hasattr(self, 'id'): + if not self._created: raise GitlabDeleteError return self.gitlab.delete(self) def __init__(self, gl, data=None, **kwargs): + self._created = False self.gitlab = gl - if data is None or type(data) in [int, str, unicode]: + if data is None or isinstance(data, six.integer_types) or\ + isinstance(data, six.string_types): data = self.gitlab.get(self.__class__, data, **kwargs) + # Object is created because we got it from api + self._created = True self._setFromDict(data) @@ -571,6 +636,12 @@ def __init__(self, gl, data=None, **kwargs): for k, v in kwargs.items(): self.__dict__[k] = v + # Special handling for api-objects that don't have id-number in api + # responses. Currently only Labels and Files + if not hasattr(self, "id"): + self.id = None + + def __str__(self): return '%s => %s' % (type(self), str(self.__dict__)) @@ -598,7 +669,7 @@ def _obj_to_str(obj): elif isinstance(obj, list): s = ", ".join([GitlabObject._obj_to_str(x) for x in obj]) return "[ %s ]" % s - elif isinstance(obj, unicode): + elif six.PY2 and isinstance(obj, six.text_type): return obj.encode(_get_display_encoding(), "replace") else: return str(obj) @@ -607,11 +678,14 @@ def pretty_print(self, depth=0): id = self.__dict__[self.idAttr] print("%s%s: %s" % (" " * depth * 2, self.idAttr, id)) for k in sorted(self.__dict__.keys()): - if k == self.idAttr: + if k == self.idAttr or k == 'id': + continue + if k[0] == '_': continue v = self.__dict__[k] pretty_k = k.replace('_', '-') - pretty_k = pretty_k.encode(_get_display_encoding(), "replace") + if six.PY2: + pretty_k = pretty_k.encode(_get_display_encoding(), "replace") if isinstance(v, GitlabObject): if depth == 0: print("%s:" % pretty_k) @@ -631,19 +705,20 @@ def json(self): class UserKey(GitlabObject): _url = '/users/%(user_id)s/keys' canGet = False - canList = True canUpdate = False - canDelete = True - requiredCreateAttrs = ['user_id', 'title', 'key'] + requiredUrlAttrs = ['user_id'] + requiredCreateAttrs = ['title', 'key'] class User(GitlabObject): _url = '/users' shortPrintAttr = 'username' - requiredCreateAttrs = ['email', 'password', 'username', 'name'] - optionalCreateAttrs = ['skype', 'linkedin', 'twitter', 'projects_limit', - 'extern_uid', 'provider', 'bio', 'admin', - 'can_create_group'] + # FIXME: password is required for create but not for update + requiredCreateAttrs = ['email', 'username', 'name'] + optionalCreateAttrs = ['password', 'skype', 'linkedin', 'twitter', + 'projects_limit', 'extern_uid', 'provider', + 'bio', 'admin', 'can_create_group', 'website_url'] + def Key(self, id=None, **kwargs): return self._getListOrObject(UserKey, id, @@ -667,23 +742,20 @@ class CurrentUser(GitlabObject): shortPrintAttr = 'username' def Key(self, id=None, **kwargs): - if id is None: - return CurrentUserKey.list(self.gitlab, **kwargs) - else: - return CurrentUserKey(self.gitlab, id) - + return self._getListOrObject(CurrentUserKey, id, **kwargs) class GroupMember(GitlabObject): _url = '/groups/%(group_id)s/members' canGet = False canUpdate = False - requiredCreateAttrs = ['group_id', 'user_id', 'access_level'] - requiredDeleteAttrs = ['group_id', 'user_id'] + requiredUrlAttrs = ['group_id'] + requiredCreateAttrs = ['access_level', 'user_id'] shortPrintAttr = 'username' class Group(GitlabObject): _url = '/groups' + canUpdate = False _constructorTypes = {'projects': 'Project'} requiredCreateAttrs = ['name', 'path'] shortPrintAttr = 'name' @@ -726,12 +798,12 @@ class Issue(GitlabObject): class ProjectBranch(GitlabObject): _url = '/projects/%(project_id)s/repository/branches' + _constructorTypes = {'author': 'User', "committer": "User"} + idAttr = 'name' canUpdate = False - requiredGetAttrs = ['project_id'] - requiredListAttrs = ['project_id'] - requiredCreateAttrs = ['project_id', 'branch_name', 'ref'] - requiredDeleteAttrs = ['project_id'] + requiredUrlAttrs = ['project_id'] + requiredCreateAttrs = ['branch_name', 'ref'] _constructorTypes = {'commit': 'ProjectCommit'} def protect(self, protect=True): @@ -757,7 +829,7 @@ class ProjectCommit(GitlabObject): canDelete = False canUpdate = False canCreate = False - requiredListAttrs = ['project_id'] + requiredUrlAttrs = ['project_id'] shortPrintAttr = 'title' def diff(self): @@ -783,9 +855,8 @@ def blob(self, filepath): class ProjectKey(GitlabObject): _url = '/projects/%(project_id)s/keys' canUpdate = False - requiredListAttrs = ['project_id'] - requiredGetAttrs = ['project_id'] - requiredCreateAttrs = ['project_id', 'title', 'key'] + requiredUrlAttrs = ['project_id'] + requiredCreateAttrs = ['title', 'key'] class ProjectEvent(GitlabObject): @@ -794,15 +865,16 @@ class ProjectEvent(GitlabObject): canDelete = False canUpdate = False canCreate = False - requiredListAttrs = ['project_id'] + requiredUrlAttrs = ['project_id'] shortPrintAttr = 'target_title' class ProjectHook(GitlabObject): _url = '/projects/%(project_id)s/hooks' - requiredListAttrs = ['project_id'] - requiredGetAttrs = ['project_id'] - requiredCreateAttrs = ['project_id', 'url'] + requiredUrlAttrs = ['project_id'] + requiredCreateAttrs = ['url'] + optionalCreateAttrs = ['push_events', 'issues_events', + 'merge_requests_events', 'tag_push_events'] shortPrintAttr = 'url' @@ -811,9 +883,8 @@ class ProjectIssueNote(GitlabObject): _constructorTypes = {'author': 'User'} canUpdate = False canDelete = False - requiredListAttrs = ['project_id', 'issue_id'] - requiredGetAttrs = ['project_id', 'issue_id'] - requiredCreateAttrs = ['project_id', 'body'] + requiredUrlAttrs = ['project_id', 'issue_id'] + requiredCreateAttrs = ['body'] class ProjectIssue(GitlabObject): @@ -821,11 +892,11 @@ class ProjectIssue(GitlabObject): _constructorTypes = {'author': 'User', 'assignee': 'User', 'milestone': 'ProjectMilestone'} canDelete = False - requiredListAttrs = ['project_id'] - requiredGetAttrs = ['project_id'] - requiredCreateAttrs = ['project_id', 'title'] + requiredUrlAttrs = ['project_id'] + requiredCreateAttrs = ['title'] optionalCreateAttrs = ['description', 'assignee_id', 'milestone_id', 'labels'] + shortPrintAttr = 'title' def Note(self, id=None, **kwargs): @@ -837,9 +908,8 @@ def Note(self, id=None, **kwargs): class ProjectMember(GitlabObject): _url = '/projects/%(project_id)s/members' - requiredListAttrs = ['project_id'] - requiredGetAttrs = ['project_id'] - requiredCreateAttrs = ['project_id', 'user_id', 'access_level'] + requiredUrlAttrs = ['project_id'] + requiredCreateAttrs = ['access_level', 'user_id'] shortPrintAttr = 'username' @@ -848,9 +918,8 @@ class ProjectNote(GitlabObject): _constructorTypes = {'author': 'User'} canUpdate = False canDelete = False - requiredListAttrs = ['project_id'] - requiredGetAttrs = ['project_id'] - requiredCreateAttrs = ['project_id', 'body'] + requiredUrlAttrs = ['project_id'] + requiredCreateAttrs = ['body'] class ProjectTag(GitlabObject): @@ -859,29 +928,27 @@ class ProjectTag(GitlabObject): canGet = False canDelete = False canUpdate = False - canCreate = False - requiredListAttrs = ['project_id'] + requiredUrlAttrs = ['project_id'] + requiredCreateAttrs = ['tag_name', 'ref'] + optionalCreateattrs = ['message'] shortPrintAttr = 'name' class ProjectMergeRequestNote(GitlabObject): _url = '/projects/%(project_id)s/merge_requests/%(merge_request_id)s/notes' _constructorTypes = {'author': 'User'} - canGet = False - canCreate = False canUpdate = False canDelete = False - requiredListAttrs = ['project_id', 'merge_request_id'] + requiredUrlAttrs = ['project_id', 'merge_request_id'] + requiredCreateAttrs = ['body'] class ProjectMergeRequest(GitlabObject): _url = '/projects/%(project_id)s/merge_requests' _constructorTypes = {'author': 'User', 'assignee': 'User'} canDelete = False - requiredListAttrs = ['project_id'] - requiredGetAttrs = ['project_id'] - requiredCreateAttrs = ['project_id', 'source_branch', - 'target_branch', 'title'] + requiredUrlAttrs = ['project_id'] + requiredCreateAttrs = ['source_branch', 'target_branch', 'title'] optionalCreateAttrs = ['assignee_id'] def Note(self, id=None, **kwargs): @@ -894,29 +961,49 @@ def Note(self, id=None, **kwargs): class ProjectMilestone(GitlabObject): _url = '/projects/%(project_id)s/milestones' canDelete = False - requiredListAttrs = ['project_id'] - requiredGetAttrs = ['project_id'] - requiredCreateAttrs = ['project_id', 'title'] + requiredUrlAttrs = ['project_id'] + requiredCreateAttrs = ['title'] optionalCreateAttrs = ['description', 'due_date', 'state_event'] shortPrintAttr = 'title' +class ProjectLabel(GitlabObject): + _url = '/projects/%(project_id)s/labels' + requiredUrlAttrs = ['project_id'] + idAttr = 'name' + requiredDeleteAttrs = ['name'] + requiredCreateAttrs = ['name', 'color'] + # FIXME: new_name is only valid with update + optionalCreateAttrs = ['new_name'] + + +class ProjectFile(GitlabObject): + _url = '/projects/%(project_id)s/repository/files' + canList = False + requiredUrlAttrs = ['project_id'] + requiredGetAttrs = ['file_path', 'ref'] + requiredCreateAttrs = ['file_path', 'branch_name', 'content', + 'commit_message'] + optionalCreateAttrs = ['encoding'] + requiredDeleteAttrs = ['branch_name', 'commit_message'] + getListWhenNoId = False + shortPrintAttr = 'name' + + class ProjectSnippetNote(GitlabObject): _url = '/projects/%(project_id)s/snippets/%(snippet_id)s/notes' _constructorTypes = {'author': 'User'} canUpdate = False canDelete = False - requiredListAttrs = ['project_id', 'snippet_id'] - requiredGetAttrs = ['project_id', 'snippet_id'] - requiredCreateAttrs = ['project_id', 'snippet_id', 'body'] + requiredUrlAttrs = ['project_id', 'snippet_id'] + requiredCreateAttrs = ['body'] class ProjectSnippet(GitlabObject): _url = '/projects/%(project_id)s/snippets' _constructorTypes = {'author': 'User'} - requiredListAttrs = ['project_id'] - requiredGetAttrs = ['project_id'] - requiredCreateAttrs = ['project_id', 'title', 'file_name', 'code'] + requiredUrlAttrs = ['project_id'] + requiredCreateAttrs = ['title', 'file_name', 'code'] optionalCreateAttrs = ['lifetime'] shortPrintAttr = 'title' @@ -944,7 +1031,8 @@ class UserProject(GitlabObject): canDelete = False canList = False canGet = False - requiredCreateAttrs = ['name', 'user_id'] + requiredUrlAttrs = ['user_id'] + requiredCreateAttrs = ['name'] optionalCreateAttrs = ['default_branch', 'issues_enabled', 'wall_enabled', 'merge_requests_enabled', 'wiki_enabled', 'snippets_enabled', 'public', 'visibility_level', @@ -955,12 +1043,12 @@ class Project(GitlabObject): _url = '/projects' _constructorTypes = {'owner': 'User', 'namespace': 'Group'} canUpdate = False - canDelete = False requiredCreateAttrs = ['name'] optionalCreateAttrs = ['default_branch', 'issues_enabled', 'wall_enabled', 'merge_requests_enabled', 'wiki_enabled', 'snippets_enabled', 'public', 'visibility_level', - 'namespace_id', 'description'] + 'namespace_id', 'description', 'path', 'import_url'] + shortPrintAttr = 'path' def Branch(self, id=None, **kwargs): @@ -1018,6 +1106,16 @@ def Snippet(self, id=None, **kwargs): project_id=self.id, **kwargs) + def Label(self, id=None, **kwargs): + return self._getListOrObject(ProjectLabel, id, + project_id=self.id, + **kwargs) + + def File(self, id=None, **kwargs): + return self._getListOrObject(ProjectFile, id, + project_id=self.id, + **kwargs) + def Tag(self, id=None, **kwargs): return self._getListOrObject(ProjectTag, id, project_id=self.id, @@ -1079,10 +1177,8 @@ def delete_file(self, path, branch, message): class TeamMember(GitlabObject): _url = '/user_teams/%(team_id)s/members' canUpdate = False - requiredCreateAttrs = ['team_id', 'user_id', 'access_level'] - requiredDeleteAttrs = ['team_id'] - requiredGetAttrs = ['team_id'] - requiredListAttrs = ['team_id'] + requiredUrlAttrs = ['teamd_id'] + requiredCreateAttrs = ['access_level'] shortPrintAttr = 'username' @@ -1090,10 +1186,8 @@ class TeamProject(GitlabObject): _url = '/user_teams/%(team_id)s/projects' _constructorTypes = {'owner': 'User', 'namespace': 'Group'} canUpdate = False - requiredCreateAttrs = ['team_id', 'project_id', 'greatest_access_level'] - requiredDeleteAttrs = ['team_id', 'project_id'] - requiredGetAttrs = ['team_id'] - requiredListAttrs = ['team_id'] + requiredCreateAttrs = ['greatest_access_level'] + requiredUrlAttrs = ['team_id'] shortPrintAttr = 'name' diff --git a/requirements.txt b/requirements.txt index 65d5e04b2..af8843719 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ requests>1.0 +six diff --git a/setup.py b/setup.py index eeeeaf0d1..4330e6c2a 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ def get_version(): url='https://github.com/gpocentek/python-gitlab', py_modules=['gitlab'], scripts=['gitlab'], - install_requires=['requests'], + install_requires=['requests', 'six'], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Console',