diff --git a/AUTHORS b/AUTHORS index 635ddd8d0..d6130439c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -9,3 +9,5 @@ Contributors Daniel Kimsey Erik Weatherwax Andrew Austin +Koen Smets +Mart Sõmermaa diff --git a/ChangeLog b/ChangeLog index 9da1715be..b9766ea52 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +Version 0.5 + + * Add SSH key for user + * Fix comments + * Add support for project events + * Support creation of projects for users + * Project: add methods for create/update/delete files + * Support projects listing: search, all, owned + * System hooks can't be updated + * Project.archive(): download tarball of the project + * Define new optional attributes for user creation + * Provide constants for access permissions in groups + Version 0.4 * Fix strings encoding (Closes #6) diff --git a/gitlab b/gitlab index 370cef170..4c8fb198c 100755 --- a/gitlab +++ b/gitlab @@ -33,8 +33,13 @@ camel_re = re.compile('(.)([A-Z])') extra_actions = { gitlab.ProjectBranch: { - 'protect': { 'requiredAttrs': ['id', 'project-id'] }, - 'unprotect': { 'requiredAttrs': ['id', 'project-id'] } + 'protect': {'requiredAttrs': ['id', 'project-id']}, + 'unprotect': {'requiredAttrs': ['id', 'project-id']} + }, + gitlab.Project: { + 'search': {'requiredAttrs': ['query']}, + 'owned': {'requiredAttrs': []}, + 'all': {'requiredAttrs': []} }, } @@ -203,6 +208,24 @@ def do_update(cls, d): return o +def do_project_search(d): + try: + return gl.search_projects(d['query']) + except: + die("Impossible to search projects (%s)" % str(e)) + +def do_project_all(): + try: + return gl.all_projects() + except Exception as e: + die("Impossible to list all projects (%s)" % str(e)) + +def do_project_owned(): + try: + return gl.owned_projects() + except: + die("Impossible to list owned projects (%s)" % str(e)) + ssl_verify = True gitlab_id = None @@ -338,6 +361,27 @@ elif action == "unprotect": o = do_get(cls, d) o.unprotect() +elif action == "search": + if cls != gitlab.Project: + die("%s objects don't support this request" % what) + + for o in do_project_search(d): + o.display(verbose) + +elif action == "owned": + if cls != gitlab.Project: + die("%s objects don't support this request" % what) + + for o in do_project_owned(): + o.display(verbose) + +elif action == "all": + if cls != gitlab.Project: + die("%s objects don't support this request" % what) + + for o in do_project_all(): + o.display(verbose) + else: die("Unknown action: %s. Use \"gitlab %s help\" to get details." % (action, what)) diff --git a/gitlab.py b/gitlab.py index c6dfed2a4..f827a5b72 100644 --- a/gitlab.py +++ b/gitlab.py @@ -21,7 +21,7 @@ import sys __title__ = 'python-gitlab' -__version__ = '0.4' +__version__ = '0.5' __author__ = 'Gauvain Pocentek' __email__ = 'gauvain@pocentek.net' __license__ = 'LGPL3' @@ -141,7 +141,7 @@ def rawGet(self, path): raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % self._url) - def rawPost(self, path, data): + def rawPost(self, path, data=None): url = '%s%s' % (self._url, path) try: return requests.post(url, data, @@ -162,6 +162,17 @@ def rawPut(self, path): raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % self._url) + def rawDelete(self, path): + url = '%s%s' % (self._url, path) + + try: + return requests.delete(url, + headers=self.headers, + verify=self.ssl_verify) + 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: @@ -315,9 +326,9 @@ def Hook(self, id=None, **kwargs): If id is None, returns a list of hooks. - If id is an integer, test the matching hook. + If id is an integer, tests the matching hook. - If id is a dict, create a new object using attributes provided. The + If id is a dict, creates a new object using attributes provided. The object is NOT saved on the server. Use the save() method on the object to write it on the server. """ @@ -328,42 +339,79 @@ def Project(self, id=None, **kwargs): If id is None, returns a list of projects. - If id is an integer, returns the matching project (or raise a + If id is an integer, returns the matching project (or raises a GitlabGetError if not found) - If id is a dict, create a new object using attributes provided. The + If id is a dict, creates a new object using attributes provided. The object is NOT saved on the server. Use the save() method on the object to write it on the server. """ return self._getListOrObject(Project, id, **kwargs) + def UserProject(self, id=None, **kwargs): + """Creates a project for a user. + + id must be a dict. + """ + return self._getListOrObject(UserProject, id, **kwargs) + + def _list_projects(self, url): + r = self.rawGet(url) + if r.status_code != 200: + raise GitlabListError + + l = [] + for o in r.json(): + l.append(Project(self, o)) + + return l + + def search_projects(self, query): + """Searches projects by name. + + Returns a list of matching projects. + """ + return self._list_projects("/projects/search/" + query) + + def all_projects(self): + """Lists all the projects (need admin rights).""" + return self._list_projects("/projects/all") + + def owned_projects(self): + """Lists owned projects.""" + return self._list_projects("/projects/owned") + def Group(self, id=None, **kwargs): - """Creates/gets/lists groups(s) known by the GitLab server. + """Creates/gets/lists group(s) known by the GitLab server. - If id is None, returns a list of projects. + If id is None, returns a list of groups. - If id is an integer, returns the matching project (or raise a + If id is an integer, returns the matching group (or raises a GitlabGetError if not found) - If id is a dict, create a new object using attributes provided. The + If id is a dict, creates a new object using attributes provided. The object is NOT saved on the server. Use the save() method on the object to write it on the server. """ return self._getListOrObject(Group, id, **kwargs) def Issue(self, id=None, **kwargs): - """Lists issues(s) known by the GitLab server.""" + """Lists issues(s) known by the GitLab server. + + Does not support creation or getting a single issue unlike other + methods in this class yet. + """ return self._getListOrObject(Issue, id, **kwargs) def User(self, id=None, **kwargs): """Creates/gets/lists users(s) known by the GitLab server. - If id is None, returns a list of projects. + If id is None, returns a list of users. - If id is an integer, returns the matching project (or raise a + If id is an integer, returns the matching user (or raises a GitlabGetError if not found) - If id is a dict, create a new object using attributes provided. The + If id is a dict, creates a new object using attributes provided. The object is NOT saved on the server. Use the save() method on the object to write it on the server. """ @@ -374,7 +422,7 @@ def Team(self, id=None, **kwargs): If id is None, returns a list of teams. - If id is an integer, returns the matching project (or raise a + If id is an integer, returns the matching team (or raises a GitlabGetError if not found) If id is a dict, create a new object using attributes provided. The @@ -541,12 +589,27 @@ def json(self): return json.dumps(self.__dict__, cls=jsonEncoder) +class UserKey(GitlabObject): + _url = '/users/%(user_id)s/keys' + canGet = False + canList = False + canUpdate = False + canDelete = False + requiredCreateAttrs = ['user_id', 'title', 'key'] + + class User(GitlabObject): _url = '/users' shortPrintAttr = 'username' requiredCreateAttrs = ['email', 'password', 'username', 'name'] optionalCreateAttrs = ['skype', 'linkedin', 'twitter', 'projects_limit', - 'extern_uid', 'provider', 'bio'] + 'extern_uid', 'provider', 'bio', 'admin', + 'can_create_group'] + + def Key(self, id=None, **kwargs): + return self._getListOrObject(UserKey, id, + user_id=self.id, + **kwargs) class CurrentUserKey(GitlabObject): @@ -586,6 +649,12 @@ class Group(GitlabObject): requiredCreateAttrs = ['name', 'path'] shortPrintAttr = 'name' + GUEST_ACCESS = 10 + REPORTER_ACCESS = 20 + DEVELOPER_ACCESS = 30 + MASTER_ACCESS = 40 + OWNER_ACCESS = 50 + def Member(self, id=None, **kwargs): return self._getListOrObject(GroupMember, id, group_id=self.id, @@ -600,6 +669,7 @@ def transfer_project(self, id): class Hook(GitlabObject): _url = '/hooks' + canUpdate = False requiredCreateAttrs = ['url'] shortPrintAttr = 'url' @@ -679,6 +749,16 @@ class ProjectKey(GitlabObject): requiredCreateAttrs = ['project_id', 'title', 'key'] +class ProjectEvent(GitlabObject): + _url = '/projects/%(project_id)s/events' + canGet = False + canDelete = False + canUpdate = False + canCreate = False + requiredListAttrs = ['project_id'] + shortPrintAttr = 'target_title' + + class ProjectHook(GitlabObject): _url = '/projects/%(project_id)s/hooks' requiredListAttrs = ['project_id'] @@ -819,6 +899,20 @@ def Note(self, id=None, **kwargs): **kwargs) +class UserProject(GitlabObject): + _url = '/projects/user/%(user_id)s' + _constructorTypes = {'owner': 'User', 'namespace': 'Group'} + canUpdate = False + canDelete = False + canList = False + canGet = False + requiredCreateAttrs = ['name', 'user_id'] + optionalCreateAttrs = ['default_branch', 'issues_enabled', 'wall_enabled', + 'merge_requests_enabled', 'wiki_enabled', + 'snippets_enabled', 'public', 'visibility_level', + 'description'] + + class Project(GitlabObject): _url = '/projects' _constructorTypes = {'owner': 'User', 'namespace': 'Group'} @@ -827,7 +921,8 @@ class Project(GitlabObject): requiredCreateAttrs = ['name'] optionalCreateAttrs = ['default_branch', 'issues_enabled', 'wall_enabled', 'merge_requests_enabled', 'wiki_enabled', - 'namespace_id'] + 'snippets_enabled', 'public', 'visibility_level', + 'namespace_id', 'description'] shortPrintAttr = 'path' def Branch(self, id=None, **kwargs): @@ -840,6 +935,16 @@ def Commit(self, id=None, **kwargs): project_id=self.id, **kwargs) + def Event(self, id=None, **kwargs): + return self._getListOrObject(ProjectEvent, id, + project_id=self.id, + **kwargs) + + def File(self, id=None, **kwargs): + return self._getListOrObject(ProjectFile, id, + project_id=self.id, + **kwargs) + def Hook(self, id=None, **kwargs): return self._getListOrObject(ProjectHook, id, project_id=self.id, @@ -903,6 +1008,40 @@ def blob(self, sha, filepath): raise GitlabGetError + def archive(self, sha=None): + url = '/projects/%s/repository/archive' % self.id + if sha: + url += '?sha=%s' % sha + r = self.gitlab.rawGet(url) + if r.status_code == 200: + return r.content + + raise GitlabGetError + + def create_file(self, path, branch, content, message): + url = "/projects/%s/repository/files" % self.id + url += "?file_path=%s&branch_name=%s&content=%s&commit_message=%s" % \ + (path, branch, content, message) + r = self.gitlab.rawPost(url) + if r.status_code != 201: + raise GitlabCreateError + + def update_file(self, path, branch, content, message): + url = "/projects/%s/repository/files" % self.id + url += "?file_path=%s&branch_name=%s&content=%s&commit_message=%s" % \ + (path, branch, content, message) + r = self.gitlab.rawPut(url) + if r.status_code != 200: + raise GitlabUpdateError + + def delete_file(self, path, branch, message): + url = "/projects/%s/repository/files" % self.id + url += "?file_path=%s&branch_name=%s&commit_message=%s" % \ + (path, branch, message) + r = self.gitlab.rawDelete(url) + if r.status_code != 200: + raise GitlabDeleteError + class TeamMember(GitlabObject): _url = '/user_teams/%(team_id)s/members' diff --git a/setup.py b/setup.py index 4783ffe82..4222b07e2 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -from distutils.core import setup +from setuptools import setup def get_version(): f = open('gitlab.py') @@ -22,6 +22,7 @@ def get_version(): url='https://github.com/gpocentek/python-gitlab', py_modules=['gitlab'], scripts=['gitlab'], + install_requires=['requests'], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Console',