diff --git a/ChangeLog b/ChangeLog index d65ba934f..9da1715be 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +Version 0.4 + + * Fix strings encoding (Closes #6) + * Allow to get a project commit (GitLab 6.1) + * ProjectMergeRequest: fix Note() method + * Gitlab 6.1 methods: diff, blob (commit), tree, blob (project) + * Add support for Gitlab 6.1 group members + Version 0.3 * Use PRIVATE-TOKEN header for passing the auth token diff --git a/gitlab.py b/gitlab.py index 22e5a3b26..c6dfed2a4 100644 --- a/gitlab.py +++ b/gitlab.py @@ -18,9 +18,10 @@ import json import requests +import sys __title__ = 'python-gitlab' -__version__ = '0.3' +__version__ = '0.4' __author__ = 'Gauvain Pocentek' __email__ = 'gauvain@pocentek.net' __license__ = 'LGPL3' @@ -74,7 +75,8 @@ 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): + def __init__(self, url, private_token=None, + email=None, password=None, ssl_verify=True): """Stores informations about the server url: the URL of the Gitlab server @@ -121,12 +123,8 @@ def setUrl(self, url): def setToken(self, token): """Sets the private token for authentication""" - if token: - self.private_token = token - self.headers = {"PRIVATE-TOKEN": token} - else: - self.private_token = None - self.headers = {} + self.private_token = token if token else None + self.headers = {"PRIVATE-TOKEN": token} if token else None def setCredentials(self, email, password): """Sets the email/login and password for authentication""" @@ -136,52 +134,48 @@ def setCredentials(self, email, password): def rawGet(self, path): url = '%s%s' % (self._url, path) try: - r = requests.get(url, headers=self.headers, verify=self.ssl_verify) + return requests.get(url, + headers=self.headers, + verify=self.ssl_verify) except: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % self._url) - return r - def rawPost(self, path, data): url = '%s%s' % (self._url, path) try: - r = requests.post(url, data, - headers=self.headers, - verify=self.ssl_verify) + return requests.post(url, data, + headers=self.headers, + verify=self.ssl_verify) except: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % self._url) - return r - def rawPut(self, path): url = '%s%s' % (self._url, path) try: - r = requests.put(url, headers=self.headers, verify=self.ssl_verify) + return requests.put(url, + headers=self.headers, + verify=self.ssl_verify) except: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % self._url) - return r - def list(self, obj_class, **kwargs): missing = [] for k in obj_class.requiredListAttrs: if k not in kwargs: - missing.append (k) + missing.append(k) if missing: - raise GitlabListError('Missing attribute(s): %s' % \ - ", ".join(missing)) + raise GitlabListError('Missing attribute(s): %s' % + ", ".join(missing)) - url = obj_class._url - if kwargs: - url = obj_class._url % kwargs + url = obj_class._url % kwargs url = '%s%s' % (self._url, url) if kwargs: url += "?%s" % ("&".join( - ["%s=%s" % (k, v) for k, v in kwargs.items()])) + ["%s=%s" % (k, v) for k, v in kwargs.items()])) try: r = requests.get(url, headers=self.headers, verify=self.ssl_verify) @@ -193,7 +187,7 @@ 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()] + 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'): @@ -210,24 +204,16 @@ def get(self, obj_class, id=None, **kwargs): missing = [] for k in obj_class.requiredGetAttrs: if k not in kwargs: - missing.append (k) + missing.append(k) if missing: - raise GitlabListError('Missing attribute(s): %s' % \ - ", ".join(missing)) + raise GitlabListError('Missing attribute(s): %s' % + ", ".join(missing)) - url = obj_class._url - if kwargs: - url = obj_class._url % kwargs + url = obj_class._url % kwargs if id is not None: - try: - url = '%s%s/%d' % \ - (self._url, url, id) - except TypeError: # id might be a str (ProjectBranch) - url = '%s%s/%s' % \ - (self._url, url, id) + url = '%s%s/%s' % (self._url, url, str(id)) else: - url = '%s%s' % \ - (self._url, url) + url = '%s%s' % (self._url, url) try: r = requests.get(url, headers=self.headers, verify=self.ssl_verify) @@ -246,11 +232,12 @@ def get(self, obj_class, id=None, **kwargs): def delete(self, obj): url = obj._url % obj.__dict__ - url = '%s%s/%d' % \ - (self._url, url, obj.id) + url = '%s%s/%s' % (self._url, url, str(obj.id)) try: - r = requests.delete(url, headers=self.headers, verify=self.ssl_verify) + r = requests.delete(url, + headers=self.headers, + verify=self.ssl_verify) except: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % self._url) @@ -267,18 +254,18 @@ def create(self, obj): missing = [] for k in obj.requiredCreateAttrs: if k not in obj.__dict__: - missing.append (k) + missing.append(k) if missing: - raise GitlabCreateError('Missing attribute(s): %s' % \ - ", ".join(missing)) + raise GitlabCreateError('Missing attribute(s): %s' % + ", ".join(missing)) url = obj._url % obj.__dict__ url = '%s%s' % (self._url, url) try: - # TODO: avoid too much work on the server side by filtering the - # __dict__ keys - r = requests.post(url, obj.__dict__, headers=self.headers, verify=self.ssl_verify) + r = requests.post(url, obj.__dict__, + headers=self.headers, + verify=self.ssl_verify) except: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % self._url) @@ -292,17 +279,20 @@ def create(self, obj): def update(self, obj): url = obj._url % obj.__dict__ - url = '%s%s/%d' % \ - (self._url, url, obj.id) + url = '%s%s/%s' % (self._url, url, str(obj.id)) # build a dict of data that can really be sent to server d = {} for k, v in obj.__dict__.items(): - if type(v) in (int, str, unicode, bool): + if type(v) in (int, str, bool): d[k] = str(v) + elif type(v) == unicode: + d[k] = str(v.encode(sys.stdout.encoding, "replace")) try: - r = requests.put(url, d, headers=self.headers, verify=self.ssl_verify) + r = requests.put(url, d, + headers=self.headers, + verify=self.ssl_verify) except: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % self._url) @@ -424,17 +414,16 @@ def _getListOrObject(self, cls, id, **kwargs): if id is None: if not cls.canList: raise GitlabGetError - return cls.list(self.gitlab, **kwargs) + elif isinstance(id, dict): if not cls.canCreate: raise GitlabCreateError - return cls(self.gitlab, id, **kwargs) + else: if not cls.canGet: raise GitlabGetError - return cls(self.gitlab, id, **kwargs) def _getObject(self, k, v): @@ -508,18 +497,22 @@ def short_print(self, depth=0): id = self.__dict__[self.idAttr] print("%s%s: %s" % (" " * depth * 2, self.idAttr, id)) if self.shortPrintAttr: - print ("%s%s: %s" % (" " * depth * 2, - self.shortPrintAttr.replace('_', '-'), - self.__dict__[self.shortPrintAttr])) + print("%s%s: %s" % (" " * depth * 2, + self.shortPrintAttr.replace('_', '-'), + self.__dict__[self.shortPrintAttr])) @staticmethod def _obj_to_str(obj): if isinstance(obj, dict): - s = ", ".join(["%s: %s" % (x, GitlabObject._obj_to_str(y)) for (x, y) in obj.items()]) + s = ", ".join(["%s: %s" % + (x, GitlabObject._obj_to_str(y)) + for (x, y) in obj.items()]) return "{ %s }" % s elif isinstance(obj, list): s = ", ".join([GitlabObject._obj_to_str(x) for x in obj]) - return "[ %s ]" %s + return "[ %s ]" % s + elif isinstance(obj, unicode): + return obj.encode(sys.stdout.encoding, "replace") else: return str(obj) @@ -530,7 +523,8 @@ def pretty_print(self, depth=0): if k == self.idAttr: continue v = self.__dict__[k] - pretty_k = k.replace('_', '-') + pretty_k = k.replace('_', '-').encode(sys.stdout.encoding, + "replace") if isinstance(v, GitlabObject): if depth == 0: print("%s:" % pretty_k) @@ -577,15 +571,28 @@ def Key(self, id=None, **kwargs): return CurrentUserKey(self.gitlab, id) +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'] + shortPrintAttr = 'username' + + class Group(GitlabObject): _url = '/groups' _constructorTypes = {'projects': 'Project'} requiredCreateAttrs = ['name', 'path'] shortPrintAttr = 'name' + def Member(self, id=None, **kwargs): + return self._getListOrObject(GroupMember, id, + group_id=self.id, + **kwargs) + def transfer_project(self, id): - url = '/groups/%d/projects/%d' % \ - (self.id, id) + url = '/groups/%d/projects/%d' % (self.id, id) r = self.gitlab.rawPost(url, None) if r.status_code != 201: raise GitlabTransferProjectError() @@ -620,10 +627,8 @@ class ProjectBranch(GitlabObject): def protect(self, protect=True): url = self._url % {'project_id': self.project_id} - if protect: - url = "%s/%s/protect" % (url, self.name) - else: - url = "%s/%s/unprotect" % (url, self.name) + action = 'protect' if protect else 'unprotect' + url = "%s/%s/%s" % (url, self.name, action) r = self.gitlab.rawPut(url) if r.status_code == 200: @@ -640,13 +645,31 @@ def unprotect(self): class ProjectCommit(GitlabObject): _url = '/projects/%(project_id)s/repository/commits' - canGet = False canDelete = False canUpdate = False canCreate = False requiredListAttrs = ['project_id'] shortPrintAttr = 'title' + def diff(self): + url = '/projects/%(project_id)s/repository/commits/%(commit_id)s/diff' % \ + {'project_id': self.project_id, 'commit_id': self.id} + r = self.gitlab.rawGet(url) + if r.status_code == 200: + return r.json() + + raise GitlabGetError + + def blob(self, filepath): + url = '/projects/%(project_id)s/repository/blobs/%(commit_id)s' % \ + {'project_id': self.project_id, 'commit_id': self.id} + url += '?filepath=%s' % filepath + r = self.gitlab.rawGet(url) + if r.status_code == 200: + return r.content + + raise GitlabGetError + class ProjectKey(GitlabObject): _url = '/projects/%(project_id)s/keys' @@ -739,12 +762,13 @@ class ProjectMergeRequest(GitlabObject): canDelete = False requiredListAttrs = ['project_id'] requiredGetAttrs = ['project_id'] - requiredCreateAttrs = ['project_id', 'source_branch', 'target_branch', 'title'] + requiredCreateAttrs = ['project_id', 'source_branch', + 'target_branch', 'title'] optionalCreateAttrs = ['assignee_id'] def Note(self, id=None, **kwargs): return self._getListOrObject(ProjectMergeRequestNote, id, - project_id=self.id, + project_id=self.project_id, merge_request_id=self.id, **kwargs) @@ -861,6 +885,24 @@ def Tag(self, id=None, **kwargs): project_id=self.id, **kwargs) + def tree(self, path='', ref_name=''): + url = "%s/%s/repository/tree" % (self._url, self.id) + url += '?path=%s&ref_name=%s' % (path, ref_name) + r = self.gitlab.rawGet(url) + if r.status_code == 200: + return r.json() + + raise GitlabGetError + + def blob(self, sha, filepath): + url = "%s/%s/repository/blobs/%s" % (self._url, self.id, sha) + url += '?filepath=%s' % (filepath) + r = self.gitlab.rawGet(url) + if r.status_code == 200: + return r.content + + raise GitlabGetError + class TeamMember(GitlabObject): _url = '/user_teams/%(team_id)s/members'